Spray routing: How to respond with different content-types? - scala

In spray I would like to respond with different content-types, depending on the given Accept header. I've seen a couple of suggestions in the question by rompetroll, but I would like to hear if there are any canonical way of doing it (i. e. simple or already implemented).
In essence what I imagine should happen is something like:
path("somepath") {
get {
// Find whatever we would like to return (lazily)
...
// Marshall resource and complete depending on the `Accept` header
...
}
}
Thanks in advance.

See the tests in this commit.
I copied it here for reference:
case class Data(name: String, age: Int)
object Data {
import spray.json.DefaultJsonProtocol._
import spray.httpx.SprayJsonSupport._
// don't make those `implicit` or you will "ambiguous implicit" errors when compiling
val jsonMarshaller: Marshaller[Data] = jsonFormat2(Data.apply)
val xmlMarshaller: Marshaller[Data] =
Marshaller.delegate[Data, xml.NodeSeq](MediaTypes.`text/xml`) { (data: Data) ⇒
<data><name>{ data.name }</name><age>{ data.age }</age></data>
}
implicit val dataMarshaller: ToResponseMarshaller[Data] =
ToResponseMarshaller.oneOf(MediaTypes.`application/json`, MediaTypes.`text/xml`) (jsonMarshaller, xmlMarshaller)
}
You then using complete should suffice in your route, content-type negotiation is automatically taken care of:
get {
complete(Data("Ida", 83))
}

Spray is actually looking into the Accept header value and validates against it. So if route is returning application/json or text/plain and client accepts image/jpeg than spray will return 406 Not Acceptable. If client will request application/json ortext/plain from this route than he will receive repsonse with matching Content-Type.
The main trick here is to use correct marshallers for return objects.
You can read more about marshalling here.
Also you can override MediaType with respondWithMediaType directive, but I think it is better to use correct marshallers.

Related

Scala Finch post combinator: how to get the raw Request?

I need to log the raw request whenever I receive a particular post "Event" using Finch's post().
I have something like that:
val myEvent: Endpoint[String] = post("somepath" :: ipRangeEndpoint :: path[Long] :: jsonBody[Event]) {
(matchId: Long, event: Event) =>
(event match {
case _: Event.ToBeLogged =>
logger.debug(<REQUESTHERE>)
Ok("logged")
})
}
so let's say that, for a subset of cases, I need to log an incoming com.twitter.finagle.http.Request. How can I do that?
https://finagle.github.io/finch/user-guide.html#root-request
"It’s possible that Finch might be missing some of handy endpoints out of the box, especially that it’s evolved separately from Finagle. To overcome this and provide an extension point, there is a special endpoint instance, called root that returns a raw Finagle Request."

How to complete Akka Http response with Stream and Custom Status Code

I've got an akka-http application that uses akka-streams for data processing. So, it makes some sense to complete the request with Source[Result, _] to get backpressure across HTTP boundary for free.
Versions:
akka-http 10.0.7
akka-streams 2.5.2
akka 2.5.2
This is the simplified version of the code, and it works just fine.
pathEnd { post { entity(asSourceOf[Request]) { _ =>
complete {
Source.single("ok")
}
}}}
Since this enpoint is supposed to create and entity, instead of returning 200 OK to the requester I'd like to return 204 CREATED status code. However, I wasn't able to find a way to do that:
complete { Created -> source.single("ok") } fails compilation with Type mismatch, expected: ToResponseMarshallable, actual: (StatusCodes.Success, Source[String, NotUsed])
complete { source.single((Created, "ok")) } fails with Type mismatch, expected: ToResponseMarshallable, actual: Source[(StatusCodes.Success, String), NotUsed]
complete(Created) { Source.single("ok") } fails with Type mismatch, expected: RequestContext, actual: Source[String,NotUsed]
complete(Created, Source.signle("ok") fails with too many arguments for method complete(m: => ToResponseMarshallable)
It looks like custom marshaller might be a way to achieve that, but it'll basically mean I'll need one unmarshaller per endpoint, which isn't quite convenient or clear.
So, the question is, are there a (more convenient than custom unmarshaller) way to complete the request with Source[_,_] while also providing status code.
From the documentation:
complete(Created -> "bar")
If you want to provide some Source of data then construct the HttpResponse and pass it to complete:
import akka.http.scaladsl.model.HttpEntity.Chunked
import akka.http.scaladsl.model.ContentTypes
import akka.http.scaladsl.model.HttpEntity.ChunkStreamPart
complete {
val entity =
Chunked(ContentTypes.`text/plain(UTF-8)`,
Source.single("ok").map(ChunkStreamPart.apply))
HttpResponse(status = Created, entity=entity)
}
I hit this problem and took the approach of using mapResponse to override the status code. This is the simplest approach I've found.
mapResponse(_.copy(status = StatusCodes.Accepted)) {
complete {
Source.single("ok")
}
}
The drawback of Ramon's answer is that you become responsible for both marshalling the stream (to a ByteString), and for the content negotiation.

Extract Json from Spray POST as string, not by marshaling to entity

There is an existing question that has much of what I'm after:
Extracting Raw JSON as String inside a Spray POST route
But it stops short without explaining how to get the actual Json string representation out of the Directive[String]. I'm trying to send Json data to Kafka as a string (which the Kafka Producer serializes), so I'm trying to extract the Json in string form. I will do the marshalling to an entity at the other end in the Kafka consumer. The answer link above gets me close:
def rawJson = extract { _.request.entity.asString}
case "value2" => rawJson{ json =>// use the json }
But I end up with Directive[String]. How do I get the String out?
The example you referenced should work. You would use the rawJson directive they defined to wrap your inner route, and the json string would be made available within that inner route.
In the example below, personJson is a String, extracted by the body of the request via the rawJson directive, and made available to the inner route where the rest of the work is done.
def rawJson = extract { _.request.entity.asString}
val personRoute = {
(post & path("persons")){
rawJson{ personJson =>
onSuccess(personService.addPerson(person)){ personWithId =>
complete(StatusCodes.Created, personWithId)
}
}
}
I came up with the following syntax which accomplishes my need to extract the Json in String form. At first I thought it inefficient that I was unmarshaling and then remarshaling again, but then I realized that this provides a form of immediate Json validation. But there may be more efficient ways to do that.
The API is all Spray. handleWith uses an implicit conversion to the RawWeatherData case class.
path("weather"/"data"/"json") {
handleWith { rawRecord: RawWeatherData =>
val rawJsonStr = rawRecord.toJson.toString
kafkaJsonRecordIngest(rawJsonStr)
rawRecord
}
}

Request Content-Type in Play! Framework for REST webservices

I'm trying to implement a REST webservice with the Play! framework. I know how I can return a response in different formats (JSON, XML, HTML, ...) by specifying multiple templates. However, I didn't find any information on how you should process different Content-Types in a (e.g. POST) request (form encoded, JSON, XML, ...).
Is it possible to annotate a method to match only certain Content-Types (something like #Consumes)? Do I have to differentiate between the different request Content-Types with an if-clause in the controller method?
Have a look at the Play documentation for combining body parsers:
http://www.playframework.com/documentation/2.2.0/ScalaBodyParsers
If you want to constrain a post body to only xml or json you can write something along these lines:
val xmlOrJson = parse.using {
request =>
request.contentType.map(_.toLowerCase(Locale.ENGLISH)) match {
case Some("application/json") | Some("text/json") => play.api.mvc.BodyParsers.parse.json
case Some("application/xml") | Some("text/xml") => play.api.mvc.BodyParsers.parse.xml
case _ => play.api.mvc.BodyParsers.parse.error(Future.successful(UnsupportedMediaType("Invalid content type specified")))
}
}
def test = Action(xmlOrJson) {
request =>
request.body match {
case json: JsObject => Ok(json) //echo back posted json
case xml: NodeSeq => Ok(xml) //echo back posted XML
}
}
The xmlOrJson function looks at the content type request header to determine the body parser. If it is not xml or json then it returns the error body parser with an UnsupportedMediaType response (HTTP 415).
You then pass this function in as the body parser of any action you want to constrain to xml or json content. Within the action you can look at the body to determine if xml or json was parsed and process accordingly.
You don't do it through annotation, but rather through your routes file, or through an if statement in your action. Depends on your use case as to which one is best suited.
The following URL gives you some information on the routes file for content negotiation. http://www.playframework.org/documentation/1.2.4/routes#content-types
Example
GET /index.xml Application.index(format:'xml')
GET /index.json Application.indexJson(format:'json')
The above calls different actions, but you could call the same action with a different format value if you wish.
You may use default parser together with pattern matching in Play 2 with Scala. Something like this:
def myAction() = Action { req =>
req.body match {
case AnyContentAsFormUrlEncoded(m) =>
val f1 = m.get("field1").flatMap(_.headOption)
...
case AnyContentAsJson(j) =>
val f1 = (j \ "field1").asOpt[String]
...
case _ => //handle content types you don't support
...
}

How does Grails' content negotiation handle opposing types?

Grails supports content negotiation from 3 different sources:
Accept header
Request parameter (format)
URI extension
The question is, what does it do when it get content information from more than one place, especially when they don't agree with each other?
For example, what would happen if Grails recieved a request like the following:
URL: http://example.com/book/list.html?format=json
Accept: application/xml
The Accept header would resolve to xml, the URI extension would resolve to html, and the parameter would resolve to json.
What would this do:
import grails.converters.*
class BookController {
def list() {
def books = Book.list()
withFormat {
html bookList: books
xml { render books as XML }
json { render books as JSON }
}
}
}
For Grails 2.0.0RC3 the following will return the html block.
curl -v -H "Accept: application/xml" http://localhost:8080/book/book/list.html?format=json
The order of precedence is:
URI extension
format request param
Accept header
Note that in order to use the Accept header you must change the following parameter in the grails-app/conf/Config.groovy file (default is false):
grails.mime.use.accept.header = true