Routes
All web applications need a way to match up the incoming HTTP request with some code to execute on the server. In Scalatra, this is done using routes and actions.
If somebody makes a POST request to your application, at http://www.yourapp.org/articles
, you might want to invoke code on the server which will look at the information contained in the incoming request, and use it to create a new Article
object. The fact that it’s a POST request, and the request path is /articles
, are route information. The code that you execute is the action.
Actions are dealt with in the next guide.
A simple example
In Scalatra, a route is an HTTP method (GET, PUT, POST, or DELETE) paired with a URL matching pattern. If you set up your application using RESTful conventions, your controller might look something like this:
class Articles extends ScalatraServlet {
get("/articles/:id") { // <= this is a route matcher
// this is an action
// this action would show the article which has the specified :id
}
post("/articles") {
// submit/create an article
}
put("/articles/:id") {
// update the article which has the specified :id
}
delete("/articles/:id") {
// delete the article with the specified :id
}
}
Those four example routes, and the actions inside the route blocks, could be the basis of a simple blogging system. The examples just stub out the actions - in a real application, you’d replace the // comments
with code to save and retrieve models, and show HTML views.
Named parameters
Route patterns may include named parameters (see below for more on parameter handling):
get("/hello/:name") {
// Matches "GET /hello/foo" and "GET /hello/bar"
// params("name") is "foo" or "bar"
<p>Hello, {params("name")}</p>
}
Wildcards
Route patterns may also include wildcard parameters, accessible through the
splat
key.
get("/say/*/to/*") {
// Matches "GET /say/hello/to/world"
multiParams("splat") // == Seq("hello", "world")
}
get("/download/*.*") {
// Matches "GET /download/path/to/file.xml"
multiParams("splat") // == Seq("path/to/file", "xml")
}
Regular expressions
The route matcher may also be a regular expression. Capture groups are
accessible through the captures
key.
get("""^/f(.*)/b(.*)""".r) {
// Matches "GET /foo/bar"
multiParams("captures") // == Seq("oo", "ar")
}
It’s likely that you’ll want to anchor your regex (e.g. ^/this/that
) to make
sure that the matched URL pattern starts at the beginning of the incoming path.
There could be cases where you wouldn’t want this, but they’re hard to imagine.
Rails-like pattern matching
By default, route patterns parsing is based on Sinatra. Rails has a similar, but not identical, syntax, based on Rack::Mount’s Strexp. The path pattern parser is resolved implicitly, and may be overridden if you prefer an alternate syntax:
import org.scalatra._
class RailsLikeRouting extends ScalatraFilter {
implicit override def string2RouteMatcher(path: String) =
RailsPathPatternParser(path)
get("/:file(.:ext)") { // matched Rails-style }
}
Path patterns in the REPL
If you want to experiment with path patterns, it’s very easy in the REPL. Simply use a Scalatra project, like one created by our giter8 template.
$ cd [project root]
$ sbt
> console
scala> import org.scalatra.SinatraPathPatternParser
import org.scalatra.SinatraPathPatternParser
scala> val pattern = SinatraPathPatternParser("/foo/:bar")
pattern: PathPattern = PathPattern(^/foo/([^/?#]+)$,List(bar))
scala> pattern("/y/x") // doesn't match
res1: Option[MultiParams] = None
scala> pattern("/foo/x") // matches
res2: Option[MultiParams] = Some(Map(bar -> Vector(x)))
Alternatively, you may use the RailsPathPatternParser
in place of the
SinatraPathPatternParser
.
Conditions
Routes may include conditions. A condition is any expression that returns Boolean. Conditions are evaluated by-name each time the route matcher runs.
get("/foo") {
// Matches "GET /foo"
}
get("/foo", request.getRemoteHost == "127.0.0.1") {
// Overrides "GET /foo" for local users
}
Multiple conditions can be chained together. A route must match all conditions:
get("/foo", request.getRemoteHost == "127.0.0.1", request.getRemoteUser == "admin") {
// Only matches if you're the admin, and you're localhost
}
No path pattern is necessary. A route may consist of solely a condition:
get(isMaintenanceMode) {
<h1>Go away!</h1>
}
Enabling support for PUT and DELETE requests
Scalatra supports all of the HTTP verbs: GET
and POST
, which are supported by
browser clients, but also PUT
and DELETE
, which are not.
Many client libraries use non-standard but simple conventions to indicate
that they would like the request to be considered as a PUT
or DELETE
instead of
a POST: for example, jQuery adds a X-HTTP-METHOD-OVERRIDE
header to the request.
Other clients and frameworks often indicate the same thing by adding a
_method=put
or _method=delete
parameter to a POST body.
Scalatra will look for these conventions on incoming requests and transform
the request method automatically if you add the MethodOverride
trait into your
servlet or filter:
class MyFilter extends ScalatraFilter with MethodOverride {
// POST to "/foo/bar" with params "id=2" and "_method=put" will hit this route:
put("/foo/bar/:id") {
// update your resource here
}
}
Route order
The first matching route is invoked. Routes are matched from the bottom up, i.e. from the bottom of the Scala class defining your servlet to the top. Watch out! This is the opposite of Sinatra. Route definitions are executed as part of a Scala constructor; by matching from the bottom up, routes can be overridden in child classes.
Parameter handling
Incoming HTTP request parameters become available to your actions through
two methods: multiParams
and params
.
- multiParams
- a result of merging the standard request params (query
string or post params) with the route parameters extracted from the route
matchers of the current route. The default value for an unknown param is the
empty sequence. Keys return
Seq
uences of values. - params
- a special, simplified view of
multiParams
, containing only the head element for any known param, and returning the values as Strings.
A params example
As an example, let’s hit a URL with a GET like this:
/articles/52?foo=uno&bar=dos&baz=three&foo=anotherfoo
Look closely: there are two “foo” keys in there.
Assuming there’s a matching route at /articles/:id
, we get the following
results inside the action:
get("/articles/:id") {
params("id") // => "52"
params("foo") // => "uno" (discarding the second "foo" parameter value)
params("unknown") // => generates a NoSuchElementException
params.get("unknown") // => None - this is what Scala does with unknown keys in a Map
multiParams("id") // => Seq("52")
multiParams("foo") // => Seq("uno", "anotherfoo")
multiParams("unknown") // => an empty Seq
}
params.getOrElse
You can set defaults for parameter values easily by using params.getOrElse
.
Let’s say you wanted to require an :author name param, and set a :page value to 1 by default. If you don’t receive an :author, you want to stop execution. You could do it like this:
get("/articles-by/:author/:page") {
val author:String = params.getOrElse("author", halt(400))
val page:Int = params.getOrElse("page", "1").toInt
// now do stuff with your params
}
GET and POST params, and where’s my JSON?
Both GET and POST params end up in the params
bag - you shouldn’t need to read
anything off the request.body
.
If you put data directly into the POST body of your request, e.g.
'{"some":"object"}'
by itself as a JSON hash, then the JSON itself
becomes an empty key in the params
Map.
Filters
Scalatra offers a way for you to hook into the request chain of your
application via before
and after
filters, which both accept a
block to yield. Filters optionally take a URL pattern to match to the request.
before
The before
method will let you pass a block to be evaluated before each
and every route gets processed.
before() {
MyDb.connect
contentType="text/html"
}
get("/") {
val list = MyDb.findAll()
templateEngine.layout("index.ssp", list)
}
In this example, we’ve set up a before
filter to connect using a contrived
MyDb
module, and set the contentType
for all requests to text/html
.
after
The after
method lets you pass a block to be evaluated after each and
every route gets processed.
after() {
MyDb.disconnect
}
As you can see from this example, we’re asking the MyDB
module to
disconnect after the request has been processed.
Pattern matching
Filters optionally take a pattern to be matched against the requested URI
during processing. Here’s a quick example you could use to run a contrived
authenticate!
method before accessing any “admin” type requests.
before("/admin/*") {
basicAuth
}
after("/admin/*") {
user.logout
}
Processing order for actions, errors, and filters
Route actions, errors and filters run in the following order:
before
filters.- Routes and actions.
- If an exception is thrown during the
before
filter or route actions, it is passed to theerrorHandler
function, and its result becomes the action result. after
filters.- The response is rendered.
Handlers
Handlers are top-level methods available in Scalatra to take care of common HTTP routines.
Redirects
There is a handler for redirection:
get("/"){
redirect("/someplace/else")
}
This will return a redirect response, with HTTP status 302, pointing to
/someplace/else
.
Caution: redirect
is implemented as a HaltException
. You probably don’t
want to catch it in an action.
Although there’s no built-in handler for permanent redirects, if you’d like to do a 301 permanent redirect, you can do something like this:
halt(status = 301, headers = Map("Location" -> "http://example.org/"))
Halting
To immediately stop a request within a filter or route:
halt()
You can also specify the HTTP status:
halt(403)
Or the status and the body:
halt(403, <h1>Go away!</h1>)
Or even the HTTP status reason and headers. For more complex invocations, you can use named arguments:
halt(status = 403,
reason = "Forbidden",
headers = Map("X-Your-Mother-Was-A" -> "hamster",
"X-And-Your-Father-Smelt-Of" -> "Elderberries"),
body = <h1>Go away or I shall taunt you a second time!</h1>)
The reason
argument is ignored unless status
is not null. If you don’t pass
arguments for status
, reason
, or body
, those parts of the response will
be left unchanged.
Passing
A route can punt processing to the next matching route using pass()
. Remember,
unlike Sinatra, routes are matched from the bottom up.
get("/guess/*") {
"You missed!"
}
get("/guess/:who") {
params("who") match {
case "Frank" => "You got me!"
case _ => pass()
}
}
The route block is immediately exited and control continues with the next
matching route. If no matching route is found, the notFound
handler is
invoked.
Not Found (404)
The notFound
handler allows you to execute code when there is no matching
route for the current request’s URL.
The default behavior is:
notFound {
<h1>Not found. Bummer.</h1>
}
What happens next differs slightly based on whether you’ve set your application up using ScalatraServlet or ScalatraFilter.
ScalatraServlet
: sends a 404 responseScalatraFilter
: passes the request to the servlet filter chain, which may then throw a 404 or decide to do something different with it.
Routing FAQ
How can I make Scalatra ignore trailing slashes on routes?
If you’d like foo/bar
and foo/bar/
to be equivalent, simply append /?
to your URL matching pattern.
For example:
get("foo/bar/?") {
//...
}