3 notes

Holding the Parameter

HTTP requests: you want to serve them. You want to respond in the 200s. But some requests just don’t make the cut. They lack the attributes you’re looking for, or they have the attributes but in the wrong proportion. In short, some requests are just a bad match.

The Unfiltered web toolkit gives you two ways to handle request parameters. (Those two are in addition to the infinitely many ways you could handle the parameter map yourself, or even directly access the HttpServletRequest if you want to go there.) The first was really easy to build. It’s a natural extension of the pattern matching that Unfiltered uses generally:

object Number extends Params.Extract(
  "number", 
  Params.first ~> Params.int
)
class MyFilter extends unfiltered.Planify({
  case POST(Path("/hi", Params(Number(num, _), _))) => 
    ResponseString(num.toString)
})

And that would give you a filter that only responds when a parameter is supplied that’s called “number” and is an integer. It’s a little odd that the extractor must be defined in an object outside the partial function that defines request handling, but the real shortcoming of this approach is that you can’t easily define behavior for when the parameter is missing, or bad. You can nest pattern matching statements to catch some of the failure conditions, but it’s not going to be pretty (or complete).

For an internal service, or a prototype of an external one, descriptively responding to bad inputs may not be a concern. In that case, go to town with the parameter extractor object. It’s an easy and typesafe way to define and handle acceptable inputs. It offloads the effort of dealing with errorneous input to the client making the request.

But most services can’t just tell bad clients to bug off. For them, there’s QParam. It can map parameter failures to very specific error responses, no matter how many parameters fail.

For example!

This rather dense chunk of code will match on any GET request to “/even”, and respond with either a 200 or 400 depending on the validity of the parameters supplied. Its parameter requirements are defined in a for expression, whose yield statement is only evaluated if no errors have accumulated. After defining the requirements, you apply a set of parameters and provide a function to handle the error list if it is non-empty.

In this case we start with a parameter “number” which must parse into an int or the error object “nonnumber” will be recorded. (The type of error objects for an expression is inferred from the first error object supplied.) Secondly, it must satisfy the predicate { (_: Int) % 2 == 0 } or we’ll note that it was “odd”, and finally it must exist or else it is “missing”. If any requirement on a parameter fails, its remaining requirements are skipped and the next parameter is evaluated.

How does it work? It’s a variation of the state-transformer monad, apparently. I knew how I wanted parameter evaluation to work, and tried to write it “from scratch” but that didn’t produce anything useful. So I ripped off some code from ScalaQuery (yes—the SQL API) and that somehow did work. I thought my hack job was pretty slick and showed it to Chris League, who spoke to our Meetup group about these fancy topics recently. He promptly rewrote it. After some further negotiation, this is what we ended up with:

And so on.

In case you want to try this stuff out, we added some fun parameter gymnastics to Unfiltered’s template project. It’s a giter8 template, so if you have g8 on your path you can

$ g8 softprops/unfiltered

and be on your way. Of course, no one has g8 on their path yet, so you could be the first person in your timezone to make the exciting leap to the future of Scala applications installed and launched by sbt.

It’s been a busy summer.

  1. coderspiel posted this

}

blog comments powered by Disqus