[akka-http] Need help overriding Content-Type; getting error "Resource representation is only available with these Content-Types: application/json"

1,479 views
Skip to first unread message

Matthew Adams

unread,
Sep 4, 2015, 4:02:01 PM9/4/15
to Akka User List
I'm trying to finish a little demo where my purpose is to respond to a GET to /ping with data whose format is dictated by the caller.

The idea is that the caller sends an HTTP GET to /ping with an Accept header that can be of their choosing, provided it includes what the endpoint I'm writing currently supports, which are application/json, text/xml, text/html, and text/plain.  My code looks at the Accept header and picks the first media type in the Accept header that is supported, then I return the data with that Content-Type and in the appropriate format.  The source code for the demo is at the bottom of this message.

For example, this works great (color & emphasis added):

$ curl -i -H 'Accept: application/json, text/xml' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Fri, 04 Sep 2015 16:33:12 GMT
Content-Type: application/json
Content-Length: 42

{
  "s": "MediaType is application/json"
}

However, when I change the Accept header to only request text/xml, I get an error:

$ curl -i -H 'Accept: text/xml' http://localhost:8080/ping
HTTP/1.1 406 Not Acceptable
Server: akka-http/2.3.12
Date: Fri, 04 Sep 2015 16:34:01 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 84

Resource representation is only available with these Content-Types:
application/json

It's as though akka-http's routing directives are forcing me to use only application/json, when I'd like to demonstrate that the client can get the media type they want through the Accept header.  How do I tell akka-http that I want to use an explicit Content-Type of my own choosing?

Here's the code.  Thanks in advance.

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.headers.Accept
import akka.stream.ActorMaterializer
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model._
import akka.http.scaladsl.marshalling._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import spray.json.DefaultJsonProtocol._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.server.{Rejection, Directive1, Directive}

object Main extends App {

  // start infrastructure
  implicit val system = ActorSystem("akka-http-routing")
  implicit val materializer = ActorMaterializer()

  val json = MediaTypes.`application/json`
  val xml = MediaTypes.`text/xml`
  val html = MediaTypes.`text/html`
  val text = MediaTypes.`text/plain`

  case class MissingAcceptHeaderMediaType(requiredMediaType: Seq[MediaType]) extends Rejection

  case class Pong(s: String = "")
  implicit val pjf = jsonFormat1(Pong)

  def firstIn[In, Among](in: Seq[In], among: Seq[Among], predicate: (In, Among) => Boolean): Option[In] =
    in collectFirst { case i if among exists { a => predicate(i, a) } => i }

  def firstAmong[In, Among](in: Seq[In], among: Seq[Among], predicate: (In, Among) => Boolean): Option[Among] =
    firstIn(in, among, predicate) match {
      case Some(i) => among collectFirst { case a if predicate(i, a) => a }
      case _ => None
    }

  def firstMediaType(in: Accept, among: Seq[MediaType]): Option[MediaType] = {
    firstAmong(in.mediaRanges, among, (r: MediaRange, t: MediaType) => r matches t)
  }

  def accept(requiredTypes: Seq[MediaType]): Directive1[MediaType] = headerValueByType[Accept]().flatMap {
    case v => firstMediaType(v, requiredTypes) match {
      case Some(mt) => extract { f => mt }
    }
    case _ => reject(MissingAcceptHeaderMediaType(requiredTypes))
  }

  val route =
    get {
      path("ping") {
        accept(List(json, xml, html, text)) { t =>
          val s = s"""MediaType is ${t}"""
          t match {
            case json => complete {
              Pong(s)
            }
            case xml => complete {
              <pong>
                {s}
              </pong>
            }
            case html => complete {
              <html>
                <body>
                  <pre>
                    {s}
                  </pre>
                </body>
              </html>
            }
            case _ => complete {
              Pong(s).toString
            }
          }
        }
      }
    }

  // start a new HTTP server on port 8080 with our route
  Http().bindAndHandle(route, "localhost", 8080)
}

Akka Team

unread,
Sep 8, 2015, 6:42:56 AM9/8/15
to Akka User List
Hi Matthew,

I don't know what is wrong with this code, so you should probably file a bug. I would first recommend though to simplify the reproducer as much as you can (i.e. remove generalization/abstraction).

-Endre

--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.



--
Akka Team
Typesafe - Reactive apps on the JVM
Blog: letitcrash.com
Twitter: @akkateam

Matthew Adams

unread,
Sep 8, 2015, 10:44:25 AM9/8/15
to Akka User List
Ok, so this is interesting.  In an effort to reduce the amount of code demonstrating the bug, I ran the following (where I'm passing in a custom header X-REPRO-BUG) and everything worked.  I'll show the curl invocation first, then the code will follow.

What I don't understand is why the code I removed has any effect on the behavior; there are no differences in the imports, so the same implicits are present in both classes.

$ curl -i -H 'X-REPRO-BUG: xml' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 14:39:36 GMT
Content-Type: text/xml; charset=UTF-8
Content-Length: 33

<pong>
  MediaType is xml
</pong>

$ curl -i -H 'X-REPRO-BUG: html' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 14:40:02 GMT
Content-Type: text/xml; charset=UTF-8
Content-Length: 78

<html>
  <body>
    <pre>
      MediaType is html
    </pre>
  </body>
</html>

$ curl -i -H 'X-REPRO-BUG: json' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 14:40:23 GMT
Content-Type: application/json
Content-Length: 30

{
  "s": "MediaType is json"
}

$ curl -i -H 'X-REPRO-BUG: foo' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 14:40:40 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 22

Pong(MediaType is foo)

Here's the reduced source.

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.headers.Accept
import akka.stream.ActorMaterializer
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model._
import akka.http.scaladsl.marshalling._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import spray.json.DefaultJsonProtocol._
import spray.json.DefaultJsonProtocol
import akka.http.scaladsl.server.{Rejection, Directive1, Directive}

object Bug extends App {

  // start infrastructure
  implicit val system = ActorSystem("akka-http-routing")
  implicit val materializer = ActorMaterializer()

  case class Pong(s: String = "")
  implicit val pjf = jsonFormat1(Pong)

  val route =
    get {
      path("ping") {
        headerValueByName("X-REPRO-BUG") { t =>
          val s = s"""MediaType is ${t}"""
          t match {
            case "json" => complete {
              Pong(s)
            }
            case "xml" => complete {
<pong>
  {s}
</pong>
            }
            case "html" => complete {
<html>
  <body>
    <pre>
      {s}
    </pre>
  </body>
</html>
            }
            case _ => complete {
              Pong(s).toString
            }
          }
        }
      }
    }

  // start a new HTTP server on port 8080 with our route
  Http().bindAndHandle(route, "localhost", 8080)
}

Matthew Adams

unread,
Sep 8, 2015, 11:09:55 AM9/8/15
to Akka User List
...and even more interesting results.  If I add an Accept header to the reduced code, responses like this take place.

$ curl -i -H 'X-REPRO-BUG: xml' -H 'Accept: application/json' http://localhost:8080/ping
HTTP/1.1 406 Not Acceptable
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 15:03:44 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 124

Resource representation is only available with these Content-Types:
text/xml
application/xml
text/html
application/xhtml+xml

$ curl -i -H 'X-REPRO-BUG: xml' -H 'Accept: text/html' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 15:04:13 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 33

<pong>
  MediaType is xml
</pong>

(Note that the above is not html...)

$ curl -i -H 'X-REPRO-BUG: xml' -H 'Accept: text/xml' http://localhost:8080/ping
HTTP/1.1 200 OK
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 15:04:30 GMT
Content-Type: text/xml; charset=UTF-8
Content-Length: 33

<pong>
  MediaType is xml
</pong>

The presence of the Accept header is definitely influencing the behavior, it's just hard to infer exactly how it's influencing it...

Matthew Adams

unread,
Sep 8, 2015, 11:30:05 AM9/8/15
to Akka User List


On Tuesday, September 8, 2015 at 5:42:56 AM UTC-5, Akka Team wrote:
Reply all
Reply to author
Forward
0 new messages