Spray: Bringing RequestContext in scope results in timeout
Asked Answered
W

1

5

Hi scala and spray people!

I have a small annoying issue with extracting the HTTP 'Accept' header from the RequestContext and matching on it. On a normal route like so:

get {
  respondWithMediaType(`text/plain`) {
    complete ( "Hello World!" )
  }
}

It works like a charm. But whenever I bring the context into scope like so (as suggested in the documentation for directives):

get { context => {
  respondWithMediaType(`text/plain`) {
    complete ( "Hello World!" )
  }
} }

The result becomes the following error message:

The server was not able to produce a timely response to your request.

I am fairly new to Spray, but it looks really odd to me that bringing an (otherwise implicit) object into scope can have such a weird sideeffect. Does any of you have a clue on what is going on?

Worldlywise answered 27/12, 2013 at 8:58 Comment(2)
This is a common error and we should probably fix something in the documentation. Can you point me to the piece of documentation that suggested using the context like this?Heterozygous
Certainly: spray.io/documentation/1.1-SNAPSHOT/spray-routing/… It does not state anything about the respondWithMediaType though, that is my interpretation :)Worldlywise
H
8

Direct access to the RequestContext is rarely needed. In fact, you only need it if you want to write custom directives. Common tasks and extracting the usual bits of data can normally be handled using one of the predefined directives.

It seems what you want to do is manual content type negotiation. In fact, you don't have to do it manually as spray does content type automatically for common data structures. Your example can be shortened to

get {
  complete("Hello World!")
}

When complete is called with a string the response will always be of type text/plain. If the client would send a request with an Accept header that doesn't accept text/plain the request would already be rejected by the server.

If you want to customize the kinds of content types that can be provided from a Scala data-type you need to provide a custom Marshaller. See the documentation on how to achieve that.

Answering your original question why adding context => makes the request timeout: This is because the predefined directives already are of type RequestContext => Unit. So, writing

respondWithMediaType(`text/plain`) {
  complete("Hello World!")
}

is exactly equivalent to (i.e. automatically expanded to)

ctx => respondWithMediaType(`text/plain`) {
  complete("Hello World!")
}.apply(ctx)

So, if you add only ctx => manually, but don't add the apply call, an incoming request is never fed into the inner route and therefore never completed. The compiler doesn't catch this kind of error because the type of a route is RequestContext => Unit and so the variant with and the variant without the apply invocation are both valid. We are going to improve this in the future.

See the documentation for more info about how routes are built.

Finally, if you need to extract a header or its value you can use one of the predefined HeaderDirectives that simplify working with request headers a lot.

Heterozygous answered 27/12, 2013 at 11:4 Comment(2)
Thanks for the very elaborate answer! I should have thought about the apply in the end. Regarding the RequestContext the data types I would like to return are compatible with the marshallers. Admittedly my example was simplified a bit, but what I aim to do is to respond with different results depending on the Accept-header. And that seems exactly what the HeaderDirectives does. Thanks again :)Worldlywise
Actually, no, usually there's no reason to handle the Accept header yourself and in fact it's pretty hard to get content-type negotiation exactly right. If you want to support multiple content-types you can use the special Marshaller.oneOf to create a marshaller that can handle multiple data formats. See this example from the tests: github.com/spray/spray/commit/…Heterozygous

© 2022 - 2024 — McMap. All rights reserved.