How can I use http request headers for content negotiation in a Mashaller?

Viewed 688

My app supports protobuf and JSON serialzation. For JSON serialization I use com.trueaccord.scalapb.json.JsonFormat, my dtos are generated from proto definitions.

The com.trueaccord serializer wraps option types to JSON objects which is causing issues for some clients so I want to be able to support org.json4s without braking the existing clients.

I would like to be able to pick a serializer based on a custom http header called JFORMAT. The idea is that if this header is sent I will use json4s otherwise I will use the trueaccord serializer.

I managed to create a Unmarshaller which can pick a request serializer based on a header value:

Unmarshaller.withMaterializer[HttpRequest, T](_ => implicit mat => {
  case request: HttpRequest =>
    val entity = request.entity
    entity.dataBytes.runFold(ByteString.empty)(_ ++ _).map(data => {
      entity.contentType match {
        case `applicationJsonContentType` =>
          val jsFormat = {
            val header = request.headers.find(h => h.name() == jsonFormatHeaderName)
            if (header.isEmpty) "1.0" else header.get.value()
          }

          val charBuffer = Unmarshaller.bestUnmarshallingCharsetFor(entity)
          val jsonText = data.decodeString(charBuffer.nioCharset().name())
          val dto = if(jsFormat == "2.0") {
            write[T](value)(formats) // New Formatter
          } else {
            JsonFormat.fromJsonString[T](jsonText) // Old Formatter
          }
          dto
        case `protobufContentType` =>
          companion.parseFrom(CodedInputStream.newInstance(data.asByteBuffer)) // Proto Formatter
        case _ =>
          throw UnsupportedContentTypeException(applicationJsonContentType, protobufContentType)
      }
    })

I want to do the same with my Marshaller which I use with Marshaller.oneOf and the JSON handling one looks like:

  Marshaller.withFixedContentType(contentType) { value =>
    val jsonText = JsonSerializer.toJsonString[T](value)
    HttpEntity(contentType, jsonText)
  }

Is there a way to construct a Mashaller which is aware of the request http headers? The Akka HTTP docs don't have any examples and I cannot make sense of the PredefinedToRequestMarshallers.

Do I need to combine multiple marshallers somehow or can I append some metadata to a context during the request serialization I can use later in the Marshaller? I want to avoid appending meta to my dto if possible or using a custom content type like application/vnd.api+json

There are lots of other useful info I could use from the request when I format the response like Accept-Encoding, custom headers like unique request id to create a correlation id, I could add JSONP support by reading the callback query parmeter, etc.

To clarify: I need a solution to use the Mashaller, subclass of it or a custom version created by a factory method or maybe multiple Marshallers chained together. Marshaller.withFixedContentType already using the Accept header so there must be a way. I added added bounty to reward a solution to a specific challenge. I am ware of hacks and workarounds and I asked the question because I need a clean solution solving a specific scenario.

1 Answers
Related