akka / akka/akka-http

Trouble with "Resource representation is only available with these Content-Types"

Open
#121 1 comment 0 reactions 0 assignees View on GitHub
1 - triaged t:docs t:marshalling
Dominant language
Scala
Stars
1.4k
Forks
584
Avg merge
14h 33m
Merged PRs (30d)
24

Description

**Issue by [matthewadams](https://github.com/matthewadams)**
_Tuesday Sep 08, 2015 at 15:29 GMT_
_Originally opened as https://github.com/akka/akka/issues/18425_

---

NB: discussion at https://groups.google.com/forum/#!topic/akka-user/kpdsQsQVZJQ

I'm trying to allow a client to control (via the `Accept` header) the `MediaType` used when a site is responding, provided that the `Accept` header contains one of a collection of supported `MediaType`s. For example, I've written an endpoint to support application/json, text/xml, text/html and text/plain, and I'd like the client to be able to express its preference for which format it would like via the `Accept` header, such that `Accept: text/xml` would cause the endpoint to use `Content-Type: text/xml` in the response and send the response as xml. Same deal with application/json, text/html & text/plain.

I saw the discussion at https://groups.google.com/forum/#!topic/akka-user/_054Z0G622w/discussion (where `respondWithMediaType` from Spray is considered an antipattern), so I wrote some code that I thought would work:

```
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 {

{s}

}
case html => complete {


                    {s}




}
case _ => complete {
Pong(s).toString
}
}
}
}
}

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

When requesting the endpoint with an `Accept` header with value `text/xml`, I get the following error:

```
$ curl -i -H 'Accept: text/xml' http://localhost:8080/ping
HTTP/1.1 406 Not Acceptable
Server: akka-http/2.3.12
Date: Tue, 08 Sep 2015 15:13:47 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 84

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

In an effort to reproduce the error with less code, I created the following class `Bug` (based on the above class `Main`):

```
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 {

{s}

}
case "html" => complete {


      {s}


}
case _ => complete {
Pong(s).toString
}
}
}
}
}

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

Interestingly, in the presence of my custom `X-REPRO-BUG` header and the absence of an `Accept` header, the `Bug` class works as expected. If, however, I add an `Accept` header, I get an error response:

```
$ 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:21:47 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
```

Contributor guide

Open the contributing guide

Research direction

Start with the Main and Bug reproductions in the issue and run them with the shown curl requests, comparing behavior with and without an Accept header. Trace the content negotiation and complete response handling; done means the supported XML, JSON, HTML, and plain-text responses are selected without an incorrect 406 response.

Written by the indexing model from the issue text.

Assessment

Tech stack
scala
Domain
api
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.