Strange non-exclusive behaviour with method filters

238 views
Skip to first unread message

Alex Dean

unread,
Aug 17, 2011, 6:15:58 PM8/17/11
to spray-user
Hi,

I'm trying to define a set of different paths/methods for a given
resource type, basically as per the Longer Sample Code on the intro
page https://github.com/spray/spray/wiki.

However I'm seeing some strange behaviours in terms of multiple
exclusive routes being executed, and some code not being executed at
all. It's probably easiest to explain with a code sample - here is my
route:

class OrderResource extends Directives {

def route: Route =
// Paths not requiring an ID
path("orders") {
get {
Console.println("Get all")
_.complete("Get all")
} ~
post {
Console.println("Post all")
_.complete("Post all")
}
} ~
// ID-specific paths
path("orders" / LongNumber) { id =>
get {
Console.println("Get")
_.complete("Get")
} ~
put {
Console.println("Put")
_.complete("Put")
} ~
delete {
Console.println("Delete")
_.complete("Delete")
}
}
}

Now I run:

$ curl -v -X DELETE http://localhost:8080/orders/1

And get:

$ Delete

While my sbt console shows:

Get
Put
Delete

In other words, the println in all three exclusive method filters has
been executed.

By contrast if I run:

$ curl -v -X GET http://localhost:8080/orders/

I get:

$ Get all

But no printlns in my sbt console.

I'm really stumped by this - it was especially confusing in my
original code as instead of a println() I had some resource deletion
code under delete(), so basically each GET was also doing a DELETE!
Any ideas what could be going on?

Thanks,

Alex

Johannes Rudolph

unread,
Aug 18, 2011, 3:40:28 AM8/18/11
to spray...@googlegroups.com
Hi Alex,

On Thu, Aug 18, 2011 at 12:15 AM, Alex Dean
<alexand...@keplarllp.com> wrote:
>    path("orders" / LongNumber) { id =>
>      get {
>        Console.println("Get")
>        _.complete("Get")
>      } ~

I think the basic problem here is, that you have request handling
logic (`println`) inside the route *building* code. Because, that's
what the block which you pass to `get` is -- route building code which
returns a route which handles a request later on. The code that really
handles the request is the `_.complete()` part, so everything you want
to do to handle a request has to be done inside this function like `{
ctx => doSomething(); ctx.complete("Get") }`.

But you are right there seems to be something else going on regarding
your `printlns` only being executed in some cases.

HTH

--
Johannes

-----------------------------------------------
Johannes Rudolph
http://virtual-void.net

Mathias

unread,
Aug 18, 2011, 4:55:18 AM8/18/11
to spray...@googlegroups.com
Alex,

as Johannes already pointed out the problem is that your code executes "println" statements at other times than you expect.
Sprays rather compact route building DSL with all its function literals can be somewhat tricky, so let me go into some more detail to explain what's going on:

Assume you have the following route:

val route: Route = _.complete("yeah")

This is equivalent to

val route: Route = { ctx => ctx.complete("yeah") }

which is a function literal. The function defined by the literal is created at the time the "val" statement is reached but the code inside of the function is not executed until an actual request is injected into the route structure. This is all probably quite clear.
Not let's look at this slightly more complex structure:

val route = {
get {
_.complete("yeah")
}
}

This is equivalent to

val route = {
val x = { ctx => ctx.complete("yeah") }
get.apply(x)
}

First a function object is created from the literal. This function is passed to the apply function of the object returned by the "get" directive, which then creates the final route.

Now let's look at your code:

val route = get {


Console.println("Get all")
_.complete("Get all")
}

This is equivalent to

val route = {
val x = {
Console.println("Get all")
{ ctx => ctx.complete("yeah") }
}
get.apply(x)
}

You can now see whats going on. The "println" statement is executed when the route val is created, not when a request comes in!
In order to execute the "println" at request processing time it should therefore be _inside_ of the leaf-level route function literal:

val route = get { ctx =>
Console.println("Get all")
ctx.complete("Get all")
}

This will work as expected.

Now, why are you seeing the output you are seeing in your specific case?

Firstly, there should be "Get all" and "Post all" printouts in your log, just a lot further up, before the first request came in. Maybe you didn't see them because of that.
Secondly, the "Get", "Put" and "Delete" printouts are not executed at the time the whole route structure is built because they are inside of another function literal (the function "id => ..."). The route structure inside of your id-specific "orders" path directive depends on the extracted id. It is only being built when a request has come in and an id has been extracted. Therefore you are seeing the "Get", "Put" and "Delete" printouts at the time an id-specific orders request is being processed. In the end, only the right leaf-route is really being executed, which is why the HttpResponse is the one you are expecting.

I realize that all this might seem a bit confusing at first. However, once you understand where to put your code in order to have it be executed at the right time this whole logic is extremely powerful! You can for example construct routes dynamically depending on outer-level extractions if you want to.

Anyway, one key concept to remember is:
If you want to have code (like your "println" statements) be executed when the respective route is executed put it in the lowest-level function literal (the leaf-route). Then everything will be fine.
And of course: Remember that `_.complete("...")` is a function literal all by itself.

Cheers,
Mathias

---
mat...@spray.cc
http://www.spray.cc

Alex Dean

unread,
Aug 18, 2011, 8:20:27 PM8/18/11
to spray-user
Many many thanks Mathias and Johannes for the detailed answers.

I re-checked and sure enough the "Get all" and "Post all" were indeed
being println'ed at the very top of the stdout, right on app startup
(even before my custom ASCII ART app header ;-).

Mathias' explanation above makes a lot of sense - the idea that these
println's were being run as soon as the routes could be built (very
early in the case of the Get/Post alls; as soon as an id was available
for the other three) certainly behaves the errant behaviour I saw.

Thanks again for the explanation and the fix - it's working well for
me. I definitely need to spend a bit more time looking at Scala's
function literal syntax and more time looking at the Spray routing
code itself!

Many thanks,

Alex
> math...@spray.cchttp://www.spray.cc
>
> On 18.08.2011, at 00:15, Alex Dean wrote:
>
>
>
>
>
>
>
> > Hi,
>
> > I'm trying to define a set of different paths/methods for a given
> > resource type, basically as per the Longer Sample Code on the intro
> > pagehttps://github.com/spray/spray/wiki.
> > $ curl -v -X DELETEhttp://localhost:8080/orders/1
>
> > And get:
>
> > $ Delete
>
> > While my sbt console shows:
>
> > Get
> > Put
> > Delete
>
> > In other words, the println in all three exclusive method filters has
> > been executed.
>
> > By contrast if I run:
>
> > $ curl -v -X GEThttp://localhost:8080/orders/

Matt Fitzgerald

unread,
Aug 19, 2011, 2:48:04 AM8/19/11
to spray...@googlegroups.com
One of the single most informative posts in the spray group!   Although I haven't had Alex's problem, it has helped me to better understand the routing structure (and more about Scala in the process)!

Cheers,
  -Matt
Reply all
Reply to author
Forward
0 new messages