Scala中如何编写Akka HttpResponse的Circe/Jackson编解码器
Akka HttpResponse编解码器中Entity部分的处理方案
我需要将客户端的请求响应存储到文件中,已成功实现HttpRequest的编解码器,但在Scala中为Akka的HttpResponse编写编解码器时,不清楚如何处理HttpResponse的Entity部分。
已实现的HttpRequest编解码器及示例
HttpRequest示例代码:
val demo = HttpRequest( method = HttpMethods.GET, uri = "myUri", headers = generateHeaders(Map.empty), entity = "{\"customerReferenceIds\":[{\"customerId\":\"9600007934256702\",\"customerIdType\":\"CUSTOMER_ID\"}]}", ) demo.asJson.spaces2
HttpRequest编解码器:
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)) ) } }
尝试的HttpResponse编解码器(存在Entity处理问题)
// 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)) ) } }
调试中观察到的Entity状态
调试器中显示Entity为DefaultFullHttpEntity类型,包含以下核心属性:
contentType:值为application/jsondata:ByteString类型的原始字节数据length:内容长度
当前data处于未解析的原始字节状态,直接序列化会导致内容无法正确转换为可读的JSON格式。
修正后的HttpResponse编解码器
核心思路
Akka的HttpEntity(尤其是DefaultFullHttpEntity)的内容存储为ByteString,需要先将其解码为字符串并保留contentType信息,才能正确序列化和反序列化。同时要处理无Entity的边界情况。
import akka.http.scaladsl.model.{HttpEntity, ContentType, HttpResponse, StatusCode, HttpMethods} import akka.util.ByteString import io.circe.{Encoder, Decoder, Json, HCursor} import scala.concurrent.duration._ // 先实现HttpEntity.Strict的编解码器 implicit val httpEntityEncoder: Encoder[HttpEntity.Strict] = Encoder.instance { entity => val charset = entity.contentType.charsetOption.getOrElse(java.nio.charset.StandardCharsets.UTF_8) Json.obj( "contentType" -> Json.fromString(entity.contentType.toString), "content" -> Json.fromString(entity.data.decodeString(charset)) ) } implicit val httpResponseEncoder: Encoder[HttpResponse] = Encoder.instance { response => // 将Entity转换为Strict类型(需指定超时) val strictEntity = response.entity.toStrict(5.seconds).map(_.asJson).getOrElse(Json.Null) Json.obj( "status" -> Json.fromInt(response.status.intValue), "headers" -> response.headers.map(h => h.name -> h.value).toMap.asJson, "entity" -> strictEntity ) } // 反解码器部分 implicit val httpEntityDecoder: Decoder[HttpEntity.Strict] = Decoder.instance { c => for { contentTypeStr <- c.downField("contentType").as[String] content <- c.downField("content").as[String] } yield { val contentType = ContentType.parse(contentTypeStr).right.get val charset = contentType.charsetOption.getOrElse(java.nio.charset.StandardCharsets.UTF_8) HttpEntity.Strict(contentType, ByteString.fromString(content, charset)) } } implicit val httpResponseDecoder: Decoder[HttpResponse] = Decoder.instance { c => for { statusCode <- c.downField("status").as[Int].map(StatusCode.int2StatusCode) headers <- c.downField("headers").as[Map[String, String]].map(generateHeaders) entity <- c.downField("entity").as[Option[HttpEntity.Strict]].map(_.getOrElse(HttpEntity.Empty)) } yield { HttpResponse( status = statusCode, headers = headers, entity = entity ) } }
关键说明
- Entity严格化:通过
toStrict将流式Entity转换为内存中的Strict Entity,确保能完整读取内容。 - 字符集一致性:序列化和反序列化时使用Entity自带的字符集,避免乱码。
- 边界情况处理:用
Option处理空Entity,避免无Entity时的序列化错误。 - 保留ContentType:序列化时存储ContentType信息,反序列化时据此还原正确的Entity类型。
内容的提问来源于stack exchange,提问作者Freez
相关产品推荐
相关产品推荐

