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.
However, when I change the Accept header to only request text/xml, I get an error:
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)
}