Home > Mobile >  How to write codec for HttpResponse(Akka) using circe or jackson or any other library in scala
How to write codec for HttpResponse(Akka) using circe or jackson or any other library in scala

Time:11-16

Hi I am trying to store Request Response of a client to a file.I was easily able to do it for HttpRequest

But when I am trying to write Encoder decoder for HttpResponse I am not able to understand how to write it for entity of HttpResponse in scala.

Heres the code for HTTP Request encoder decoder


val demo=HttpRequest(
  method= HttpMethods.GET,
uri="myUri",
headers=generateHeaders(Map.empty),
  entity="{\"customerReferenceIds\":[{\"customerId\":\"9600007934256702\",\"customerIdType\":\"CUSTOMER_ID\"}]}",
)

demo.asJson.spaces2

I was able to write encoder decoder for HttpRequest easily.

implicit val HttpRequestEncoder: Encoder[HttpRequest] = new Encoder[HttpRequest] {
  final def apply(x: HttpRequest): Json = Json.obj(
    ("method", Json.fromString(x.method.value)),
    ("Uri", Json.fromString(x.uri.toString())) ,
    ("headers", x.headers.map(y=>y.name->y.value).toMap.asJson),
    ("entity", Json.fromString(JsonUtil.toJson(x.entity)))
  )
}

implicit val HttpRequestDecoder: Decoder[HttpRequest] = new Decoder[HttpRequest] {
  final def apply(c: HCursor): Decoder.Result[HttpRequest] =
    for {
      method <- c.downField("method").as[String]
      url <- c.downField("Uri").as[String]
      header <- c.downField("headers").as[Map[String,String]]
      entity <- c.downField("entity").as[String]
    } yield {
      HttpRequest(
        method=HttpMethods.getForKeyCaseInsensitive(method).getOrElse(HttpMethods.GET),
          uri = url,
        headers=generateHeaders(header),
        entity= HttpEntity(ContentTypes.`application/json`,JsonUtil.toJson(entity))
      )
    }
}


I am trying to write for encoder decoder for HttpResponse .Doing something like


//HttpResponse
implicit val HttpResponseEncoder: Encoder[HttpResponse] = new Encoder[HttpResponse] {
  final def apply(x: HttpResponse): Json = {
    Json.obj(
      ("response", Json.fromString(JsonUtil.toJson(x.entity))),
      ("status", Json.fromInt(x.status.intValue()))
    )
  }
}

implicit val HttpResponseDecoder: Decoder[HttpResponse] = new Decoder[HttpResponse] {
  final def apply(c: HCursor): Decoder.Result[HttpResponse] =
    for {
      entity <- c.downField("response").as[String]
      status <- c.downField("status").as[Int]
    } yield {
      HttpResponse(
        status = StatusCode.int2StatusCode(status),
        entity = HttpEntity(ContentTypes.`application/json`, JsonUtil.toJson(entity))
      )
    }
}

Where entity is something like this in debugger enter image description here

CodePudding user response:

The entity filed of the HttpResponse is a stream so you have to work with some effects - Future or Stream, that Encoder can't handle.

If your entity is a json then I think the easiest way is using entity.toStrict(timeout). It returns Future[HttpEntity.Strict] which has a data method with a plain String.

So your logic might look like this:

val r: Future[EntityResponse] = 
 response
   .entity
   .toStrict(timeout)
   .map { e =>
     Json.obj(
       ("response", Json.fromString(JsonUtil.toJson(e.data))),
       ("status", Json.fromInt(response.status.intValue()))
     )
   }

There https://doc.akka.io/docs/akka-http/current/implications-of-streaming-http-entity.html you can find details about the http entity and how to work with it.

CodePudding user response:

For people looking for the answer: Here how I did it:

      val testconfig: Config                 = ConfigFactory.load()
      implicit val actorSystem2: ActorSystem = ActorSystem.create("loyalty-execution-api", testconfig)
    
      implicit val executionContext: ExecutionContext = actorSystem2.dispatcher
    
      implicit val HttpResponseEncoder: Encoder[HttpResponse] = new Encoder[HttpResponse] {
        import akka.http.scaladsl.unmarshalling
        final def apply(x: HttpResponse): Json = {
          val result = Unmarshal(x.entity).to[String].map { z =>
            Json.obj(
              ("response", Json.fromString(z)),
              ("status", Json.fromInt(x.status.intValue()))
            )
          }
          val r3     = Await.result(result, 20.seconds)
          r3
        }
      }
    
      implicit val HttpResponseDecoder: Decoder[HttpResponse] = new Decoder[HttpResponse] {
        final def apply(c: HCursor): Decoder.Result[HttpResponse] =
          for {
            entity <- c.downField("response").as[String]
            status <- c.downField("status").as[Int]
          } yield {
            HttpResponse(
              status = StatusCode.int2StatusCode(status),
              entity = HttpEntity(ContentTypes.`application/json`, JsonUtil.toJson(entity))
            )
          }
      }

Also you would need to unmarshall twice one for unmarshalling entity where you use the client responose and once in encoder so use this while making a client call:

       val httpResponseFuture = client
          .singleRequest(
            clientRequest,
            settings = httpSettings,
            connectionContext = httpsCtx
          )
          .flatMap { response =>
            response.entity.toStrict(10.seconds).map { allBodyInMemory =>
              response.withEntity(allBodyInMemory)
            }
  • Related