17

I'm trying out Akka-http and hopefully someone can shed light on a the following questions:

  1. How does one create different routes based on the accept: header in the request? For example, i want one code path to handle "json" and one to handle "xml" requests (with default to "json" if header is missing)

  2. In cases where I don't want the contentType to be inferred, how do i specify it? For example, in the code below I try to run the json through compactPrint() but this changes it to a string, hence "text/plain". I want to override that and tell the client it's still json.

My code is something like this;

...
path("api") {
          get {
              complete {
                getStuff.map[ToResponseMarshallable] {
                  case Right(r) if r.isEmpty => List[String]().toJson.compactPrint
                  case Right(r) => r.toJson.compactPrint
                  case Left(e) => BadRequest -> e
                }
              }
          }
        }
...

The response in this case is text/plain, since compactPrint creates a string. criticism very welcome. ;)

Will I Am
  • 2,614
  • 3
  • 35
  • 61
  • 1
    To make use of akka-http's automatic content negotiation feature you need to provide a marshaller for your source type that can marshal to several content-types. You can use `Marshaller.oneOf` to compose different marshallers where each marshaller knows how to marshal to just one content-type. Have you seen the documentation about marshaller at http://doc.akka.io/docs/akka-stream-and-http-experimental/1.0/scala/http/common/marshalling.html#Custom_Marshallers? – jrudolph Aug 26 '15 at 08:22
  • Thanks. I did end up going this route. – Will I Am Aug 28 '15 at 02:14
  • @jrudolph Link is dead – k0pernikus Jul 26 '19 at 12:36
  • https://doc.akka.io/docs/akka-http/current/common/marshalling.html#custom-marshallers – jrudolph Jul 26 '19 at 12:40

3 Answers3

11

You can define Content Type as follows,

complete {
           HttpResponse(entity = HttpEntity(ContentType(MediaTypes.`application/json`), """{"id":"1"}"""))
         }

You can create your custom directive as,

  def handleReq(json: String) = {
    (get & extract(_.request.acceptedMediaRanges)) {
      r =>
        val encoding: MediaRange =
          r.intersect(myEncodings).headOption
            .getOrElse(MediaTypes.`application/json`)
        complete {
          // check conditions here
         // HttpResponse(entity = HttpEntity(encoding.specimen, json)) //
        }
    }
  }

and use the directive in route as

val route = path("api"){ handleReq(json) }
S.Karthik
  • 1,389
  • 9
  • 21
  • Thank you. I was just hoping for a directive to do a rejection based on the header, not to encompass the actual processing. But anyway, I ended up implementing marshallers which automatically take care of serializing to XML or JSON, so I don't need this approach anymore. But thank you for your help,it helped me get past my frustration, and it is one way of implementing things. So I'll accept the answer. – Will I Am Aug 26 '15 at 21:58
  • here better use ContentTypes.`application/json` instead of ContentType(MediaTypes.`application/json`) – Roman Kazanovskyi Oct 28 '16 at 13:17
4

It seems that the accepted answer doesn't work anymore with akka-http v10.0.3.

This works though :

// the encodings I want, in the order of preference
val myEncodings = Seq(MediaRange(`application/xml`),MediaRange( `application/json`))

...
path("api") {
          (get & extract(_.request.headers)){ requestHeaders =>
              val mediaTypeNegotiator = new MediaTypeNegotiator(requestHeaders)
              val encoding = mediaTypeNegotiator
                     .acceptedMediaRanges
                     .intersect(myEncodings)
                     .headOption
                     .getOrElse(MediaRange(`application/json`))
              complete {
                     // check "encoding" here and make decision.
              }
          }
        }
...

you could also do

val myEncodings = Seq(MediaRange(`application/xml`),MediaRange( `application/json`))

path("api") {
      (get & extract(_.request.headers)){ requestHeaders =>
        complete {
          val mediaTypeNegotiator = new MediaTypeNegotiator(requestHeaders)
          if(mediaTypeNegotiator.accept(MediaTypes.`application/xml`)) {
            // respond with xml
          } else if(mediaTypeNegotiator.accept(MediaTypes.`application/json`)) {
            // respond with json
          } else {
            // respond with json by default or reject properly :
            reject(UnsupportedRequestContentTypeRejection(Set(MediaTypes.`application/xml`, MediaTypes.`application/json`)))
          }
      }
    }
}

Hopes this helps.

fchaillou
  • 626
  • 6
  • 5
1

A potential answer for question #1 seems to be this, but I'd like to do it via a custom directive or or something more elegant. Unfortunately the documentation for Akka-Http custom directives seems to be missing.

// the encodings I want, in the order of preference
val myEncodings = Seq(MediaRange(`application/xml`),MediaRange( `application/json`))

    ...
    path("api") {
              (get & extract(_.request.acceptedMediaRanges)){  
                  r => 
                    val encoding = 
                      r.intersect(myEncodings).headOption
                         .getOrElse(MediaRange(`application/json`))
                  complete {
                         // check "encoding" here and make decision.
                  }
              }
            }
    ...

Hoping someone can provide something cleaner.

Will I Am
  • 2,614
  • 3
  • 35
  • 61