Scalatra is often referred to as a web framework. This is fair. Scalatra ably handles requests from a web browser and responds with dynamically generated HTML. But web framework fails to tell the whole story. We prefer to call Scalatra an HTTP framework. This distinction is subtle but important.
Scalatra often serves applications unrelated to web clients. It’s used for RESTful services that may serve a mobile application or integrate internal systems. One user even uses Scalatra to provide an interface to his home heater. This is all made possible by Scalatra’s firm embrace of HTTP.
Routes are the glue that binds HTTP requests to the blocks of Scala code that implement your application logic. New Scala developers with HTTP experience should find immediate comfort in Scalatra’s HTTP DSL. If you’re new to HTTP, don’t despair. Although full coverage of HTTP is out of scope for this book, we’ll cover enough to empower you to make the right decisions when designing your HTTP API. In this chapter, we’ll demonstrate routing with a simple music service.[1]
We might not topple iTunes or Amazon Music, but we’ll have a good time name-checking our favorite artists.
Routes are declared directly in the body of your Scalatra application. Let’s look at an example in figure 3.1.
A Scalatra route is composed of three main components:
Now that you understand the syntax of a route, let’s go into detail about how to define it. We’ll start with a discussion of methods.
HTTP supports just eight methods. Making it simpler still, a couple of these methods derive their semantics from other methods. This controlled vocabulary allows clients and servers to have certain expectations of each other, regardless of the application.
Unfortunately, these expectations can’t be enforced by the Scala compiler. Choosing the right method and implementing it according to convention is a crucial part of writing an HTTP API.
Consumers of your API will tend to consume a lot of APIs and will have certain expectations. Your API may be combined with other APIs in a mashup. The principle of least astonishment applies to HTTP as much as it does to your lower-level code. Learn the conventions and follow them, and your API will stand a greater chance of success.
With few exceptions, application developers eventually need to deal with persistence. Whether that storage is a trusty old SQL engine or cutting-edge cloud storage, a CRUD pattern inevitably emerges in most applications. The mapping between CRUD and HTTP methods is not quite one-to-one (as shown in table 3.1), but it’s simple to implement a CRUD service over HTTP.
HTTP method |
|
---|---|
Create |
|
Read |
|
Update |
|
Delete |
|
Method names are uppercase in the HTTP protocol but, following the Scala Style Guide (http://docs.scala-lang.org/style/), always use lowercase in Scalatra code.
HTTP doesn’t know about rows in relational databases, documents in MongoDB, or any other data store’s implementation. When we speak of CRUD in a Scalatra service, we’re referring to resources. We’ll discuss how URIs are matched in section 3.3 and map them to the persistence layer in chapter 10. But first, we’ll discuss how the HTTP methods would be mapped to the CRUD operations of a resource that represents an artist in our music service.
GET is the most familiar of the HTTP methods. When a user enters an address in the browser’s location bar, a GET request is submitted. Simple hyperlinks generate a GET request. The class in the following listing creates a route that fetches information about an artist by name.
class RecordStore extends ScalatraServlet { get("/artists/:name/info") { Artist.find(params("name")) match { case Some(artist) => artist.toXml case None => status = 404 } } }
Don’t worry about the :name path expression or the action body for now. These will be discussed in detail in section 3.3.1 and chapter 4, respectively. The focus here is to get a feel for choosing the correct method.
Use GET in the following situations:
The default method of a web form is POST, but POST isn’t limited to web forms. The request body may contain any arbitrary data, described by the Content-Type header of the request. Other common POST bodies include JSON, XML, and images. Special handling for JSON bodies is discussed in chapter 5.
The following listing declares a route to add a new artist to the database via a POST.
A typical POST action parses the request body, creates a new resource on the server, and responds with a pointer to the new resource. All HTTP responses include a status code to inform the client of the result of the request.
The default is 200 OK, which is appropriate for successful GET requests. This POST resulted in the creation of a resource, so you respond with a Created action result. This sets the response status to 201 Created, along with a Location header. The header points to a URL at which your new artist can be fetched via a subsequent GET request. Action results are discussed in depth in section 4.6.3.
Use POST in the following cases:
HTML forms only support GET and POST, requiring scripting for a browser client to submit methods such as PUT or DELETE. Some corporate proxies rigidly reject anything but a GET or POST. In these circumstances, it’s tempting to treat all non-GET operations as POSTs. A better alternative is introduced in section 3.2.3.
PUT requests are most similar to POST. The PUT body should overwrite the resource at the specified URI. The following listing updates an existing artist with PUT.
PUT requests tend to look a lot like POSTs: you parse the input, modify the data store, and return. In fact, parsing the input is usually identical between PUT and PUT routes for a given type.
In contrast to the POST handler in listing 3.2, here you return a NoContent status. Instead of regurgitating a representation of the resource identical to the request body, NoContent signals to the client that the update was successful, and there’s nothing left to be said in the response body.
Use PUT in the following cases:
There is an RFC for PATCH requests. A PATCH is like PUT, but instead of overwriting the resource, it partially modifies the existing resource. PATCH should only be used for updates, not for creates.
PATCH is not part of the HTTP 1.1 specification, but it’s fully supported by Scalatra.
A DELETE request is structurally closest to a GET. It comes with no request body, but like a GET may use query strings and headers to refine itself. The following listing declares a route to delete an artist.
In most services, confirmation would be handled on the client side. It’s assumed that anybody who hits this URL is authorized (covered in chapter 11) and really means it.
Like the POST action in listing 3.2, here you return a NoContent on success. It’s useful to let the client know whether the operation was successful, so you introduce a conditional and respond with NotFound if no artist was found to delete.
Use DELETE in the following case:
Many web developers ignore anything beyond the familiar GET and POST. This can be a security risk if the rules aren’t followed.
In one infamous case, an application implemented delete with simple hyperlinks (GET requests). Much to the horror of the development team, they found their content was gone. The culprit? Google’s web spider, which dutifully followed every delete link on the page to index the site.
HTTP services are often consumed by clients that aren’t considered at the time of development. This is why it’s important to follow conventions and use the methods as they’re intended.
The vast majority of APIs need to worry themselves only with the CRUD methods discussed in the previous section. Still, Scalatra aims to be a complete HTTP framework, and it’s good to know about the other methods.
A HEAD request should be handled like an otherwise identical GET request, except that it shouldn’t return a body. The following listing declares special handling for a HEAD request to an artist.
class RecordStore extends ScalatraServlet { head("/artists/:name/info") { contentType = "text/json" if (Artist.exists(params("name"))) Ok() else NotFound() } }
Where the GET request in listing 3.1 needs to load the artist to render the info, a HEAD request only needs to verify its existence. You’re able to override the default behavior with a more efficient implementation.
Actually, this functionality is provided by the underlying servlet container. Scalatra’s philosophy is to build on existing standards where possible.
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
Donald Knuth
Most applications shouldn’t implement HEAD directly. Doing so is error-prone, and unless the GET action is known to be slow, it’s a form of premature optimization.
On the other hand, a client may execute a HEAD request to determine whether a previously fetched resource has been modified. This is especially common when the client is a caching proxy. If headers such as Last-Modified or Content-MD5 have changed, the client may execute a GET request for the full body. Recalculating a timestamp or especially a digest can be expensive, so if the resource is known to be unchanged on the server side, it may make sense to return these headers as cached values in a custom HEAD route.
An OPTIONS request, like HEAD, is implemented in terms of other methods. It’s expected to return an Allows header so clients understand which other methods are supported for a particular path.
The following listing shows that a call to delete Frank Zappa will not be supported by the delete call.[3]
If a music service doesn’t carry Frank Zappa, it’s not worth running.
OPTIONS requests on lesser artists will fall through to the default and return an Allow header of GET, HEAD, DELETE.
Use OPTIONS in these cases:
The following listing declares a route that forbids all requests with method option.
There are two standard HTTP methods we haven’t discussed:
The preceding sections describe the perfect world where the client speaks the entire HTTP standard. It won’t surprise veteran web programmers that implementations often fall short of the published standard.
Many web browsers support a subset of the HTTP methods GET and POST. This is adequate for web browsers that simply need to get pages and post form data. But as the line blurs between web APIs and websites, this is inadequate. You’d like to support browsers as first-class clients of our APIs.
This isn’t a new problem, and various ad hoc solutions have been developed. A popular one is to look for a special _method parameter in a POST request. If present, the request is interpreted with the parameter. This is a fair solution on the client side, but it quickly becomes tedious for a Scalatra application.
Note how the deleteArtist call is repeated in listing 3.8. Even if the delete logic is extracted, you’d still need to repeat the call in both the DELETE route and the POST’s “delete” condition.
What you want is a way to transparently rewrite the request before routing occurs. Scalatra provides this functionality out of the box with MethodOverrideSupport. In addition to the _method body parameter, MethodOverrideSupport observes the X-HTTP-Method-Override header, a convention adopted by many JavaScript frameworks.
Listing 3.9 shows an example of Scalatra’s stackable modifications, where additional behaviors can be composed onto the core with mixin traits.
If HTTP methods declare the type of action to be performed, then route matchers declare the resources on which the action is to be performed. They describe which requests an action runs on and extract parameters to further describe the request to the action.
Unlike the tightly constrained HTTP methods, route matchers can take many forms and match an unlimited number of resources. Three types of route matchers are supported out of the box:
We’ll examine each of these in detail, and later show you how to create your own.
The most common type of route matcher is the path expression. A path expression always starts with a / character and refers to the mount point of your application.
In this chapter, assume that your application is mounted at http://localhost:8080, and that your servlet is mounted to /* within your application. More complex deployments are covered in chapter 5.
Scalatra is a portmanteau of Scala and Sinatra, the Ruby framework that inspired it. Rubyists may find the path expression syntax familiar. This is not coincidental.
In the following listing, you’ll see a static path expression. This path expression declares a literal path to match.
With this route, a GET request to http://localhost:8080/artists fetches a list of all the artists in the system. A resource can be an individual item or a collection of items. Static routes are ideal for referring to a collection of a items—though the collection of artists may be dynamic, the collection maintains a static resource identifier. Static path expressions are an ideal fit for static URIs.
The /artists resource only links to the individual artists. Each artist is a resource with its own URI. The following listing adds a static route per artist.
class RecordStore extends ScalatraServlet { get("/artists/Bruce_Springsteen/info") { showArtist("Bruce Springsteen") } get("/artists/Charles_Mingus/info") { showArtist("Charles Mingus") } get("/artists/A_Tribe_Called_Quest/info") { showArtist("A Tribe Called Quest") } def showArtist(name: String) = { Artist.find(name) match { case Some(artist) => artist.toXml case None => NotFound() } } }
In listing 3.11, a GET request to http://localhost:8080/artists/Bruce_Springsteen/info would fetch information about Bruce Springsteen. It’s a nice start, but notice how repetitive the routes are. The action repeats a part of the request path, and each artist’s action is almost identical to the others. It would be difficult to maintain a fully stocked music shop this way. Further, you wouldn’t want to modify and redeploy your application every time an artist was added to your inventory. It would be nice if you could parameterize the paths.
Listing 3.12 introduces path parameters. A path parameter called name is declared by preceding it with a colon character. Instead of literally looking for "/:name", the colon signals Scalatra to capture that portion of the request path as a parameter, which is made available to the route.
The example in listing 3.12 is much cleaner. With dynamic names, you can add to the Artist data store without touching the Scalatra application. Working with path parameters is discussed in depth in chapter 4.
Path parameters are matched according to the following rules:
Table 3.2 shows some hypothetical URIs for the path expression /artists/:name/info, whether they’d match, and the value of the extracted parameter.
URI |
Matches? |
Name param |
---|---|---|
http://localhost:8080/artists/Radiohead/info | Yes | Radiohead |
http://localhost:8080/artists/AC/DC/info | No | |
http://localhost:8080/artists/AC%2FDC/info | Yes | AC/DC |
It’s possible to use special characters like / in a path parameter, but they must be percent-encoded (http://en.wikipedia.org/wiki/Percent-encoding). AC/DC doesn’t match in table 3.2 because the name parameter stops matching at /, leaving the literal /DC where /info is expected. By percent-encoding the / as %2F, the slash is absorbed as part of the path parameter, and it’s then decoded as / when exposed to the application as a param.
A ? in a path expression makes the previous character or path parameter optional. The following listing demonstrates an optional trailing slash.
class RecordStore extends ScalatraServlet { get("/artists/?") { <artists>${Artist.fetchAll().map(_.toXml)}</artists> } }
The "/artists/?" expression would match a request to both of these:
A trailing slash can be significant in a URI, but humans are apt to overlook it. If your application’s URI is likely to be typed by a person rather than provided by a machine, supporting trailing slashes is good practice.
In the literal URI, ? marks the beginning of the query string, which is not part of the path matched by Scalatra. In a path expression, ? marks the previous token as optional. It’s unrelated to matching the query string.
This technique is taken one step further in the next listing and is also applied to a path parameter.
Table 3.3 gives some examples of how the route in listing 3.14 would match various requests. In all the examples, the route matches, but only in some is a "json" format parameter defined.
URI |
Format param |
---|---|
http://localhost:8080/artists/Otis_Redding/info | undefined |
http://localhost:8080/artists/Otis_Redding/info.json | "json" |
http://localhost:8080/artists/Otis_Redding/info.xml | "xml" |
http://localhost:8080/artists/Otis_Redding/info. | undefined |
http://localhost:8080/artists/Otis_Redding/infojson | "json" |
The last two examples in table 3.3 may come as a surprise. Both the literal . and the parameter that follows are optional, and the matching of one doesn’t depend on the presence of the other. This is acceptable for many applications, but too permissive for some. Finer-grained control can be obtained with regular expression matching or Rails path pattern parsing.
Path expressions also support splat parameters. Splat parameters are nicknamed for the * character that declares them.
Listing 3.15 implements a download section of the site. In this route, you use a single /downloads/* route to serve files under a virtual filesystem. You could have used /downloads/:name, but recall that named parameters match up until the next / character in a URL. A splat parameter frees you from this restriction, so that you can match a path in an arbitrary directory structure.
Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.
Jamie Zawinski
Path expressions are a simple, satisfactory solution to the majority of routing needs, but they’re limited in what they can express. Regular expressions provide the needed expressivity at the expense of a steeper learning curve. A full tutorial on regular expressions is beyond the scope of this book, but we’ll show you how a not-too-scary regular expression can solve a rather complicated problem.
In our music shop, we’d like to create a route to show the best albums of either a year or an entire decade:
We could attempt to route these requests with a simple path expression like "/best-of/:when". The problem is that the same route would need to handle both the year and the decade list. Furthermore, if the parameter were neither a year nor a decade, the route would still match. We want to both capture and validate. Regular expressions excel at validating special syntax as part of the match.
Conceptually, we have two routes: one for years and one for decades. The following listing shows how to use regular expressions to express these as two separate routes.
In both expressions, /best-of/ does a simple literal match. (d{4}) and (d{3}0)s declare the syntax for a year and a decade, respectively. Let’s tear apart the latter:
We could just as well have written the regular expression """/best-of/(d{4})""".r as new Regex("/best-of/(\d{4})"), but the former is more idiomatic in Scala. How does it work?
Certain special characters in a string literal, such as and ", can be disabled by enclosing the string in triple quotes. This syntax comes in handy with regular expressions, which tend to contain several backslashes. But merely triple quoting still yields a string, not a regex.
Scala also provides an implicit method r on StringLike, which compiles the string into a regex. Without the .r call, the string would be interpreted by Scalatra as a path expression.
Table 3.4 shows how various URIs might be matched by the routes declared in listing 3.16.
URI |
Route matched |
param('captures') |
---|---|---|
http://localhost:8080/best-of/1967 | Year | 1967 |
http://localhost:8080/best-of/1960s | Decade | 1960 |
http://localhost:8080/best-of/1967s | None |
Internally, Scalatra compiles path expressions down to regular expressions. For those who grok regex, table 3.5 explains how Scalatra’s path expressions map to regular expressions.
Path expression |
Regex equivalent |
---|---|
:param | ([/*#?]+) |
? | ? |
* | (.*?) |
() | ()[a] |
() are not special characters in a path expression. Only the characters already listed are special in a path expression.
The third and final type of route matcher in Scalatra’s core is the Boolean expression. A Boolean expression is any arbitrary Scala expression that returns a Boolean value. The expression is evaluated at request time, with the current request in scope.
Unlike path expressions and regular expressions, a Boolean expression can’t extract any parameters. Its role is to act as a guard. So far, all of our routes have taken a single route matcher, but Scalatra supports any number of route matchers in a given route. Most commonly, a Boolean expression will act as a guard condition, adding an extra constraint to a path or regular expression. A route matches if, and only if, all of its route matchers match.
A route matcher returns an Option[Map[String, Seq[String]]. An Option can be Some map of extracted parameters, or None. A route matches if and only if all of its route matchers return Some. The extracted parameters are merged into a single map, and these are the route parameters passed to the action.
But how do our types translate into this Option?
Consistent with forall on an empty collection, a route with no matchers always matches. To match all POST requests, you might write a route with an empty list of route matchers (such as post()).
The following listing defines two routes: one for mobile users and one for everybody else.
The route code in listing 3.17 could just as well have been written as a single route:
get("/listen/*".r) { if (isMobile(request.getHeader("User-Agent"))) { StreamService.mobile(params("splat")) } else { StreamService.desktop(params("splat")) } }
The Boolean guard neatly keeps all the request-related logic together in the route matchers, and lets the route action focus on a single thing. But it’s fine to keep the branching logic in the action if you prefer. In Scalatra, as with Scala, there’s usually more than one way to do it.
Boolean expressions support arbitrary snippets of code, as long as the return type is correct. These snippets may be evaluated on each request as Scalatra attempts to find a matching route. It’s important that these expressions be free of side effects, or failed matches could change the state of your application. Your Boolean guards should always be read-only operations.
We’ve now taken a tour of the three primitive route matchers in Scalatra. In the next section, we’ll look at some more-advanced use cases.
By now, you should have a good grasp of the basic building blocks of a route. As your applications grow larger, you might run into routes that overlap, or find a corner case that just isn’t handled well by the basic routing structures. In this section, we’ll cover these more complicated scenarios.
In the spirit of guessable URIs, you’d like your users to be able to type an address to look up their favorite bands. But is it “Smashing Pumpkins” or “The Smashing Pumpkins”?[4] This is a common problem, but one you can solve easily with overlapping routes.
Check out the covers of Siamese Dream and Mellon Collie and the Infinite Sadness. Even the band doesn’t know.
The following listing defines a splat route that overlaps the info and album routes.
What happens when a request matches two routes? Scalatra looks for matching routes from the bottom of your application, and works its way to the top.
Scalatra routes applications from the bottom up. Sinatra, and most other frameworks inspired by Sinatra, route from the top down. This is to allow routes declared in a child class to override routes declared in a parent.
Figure 3.2 demonstrates how the client interacts with Scalatra given the music store in listing 3.18.
When a request comes in for /artists/The_Rolling_Stones/info, the top and bottom routes match. Because Scalatra routes from the bottom up, /artists/The_:name/* is matched. This triggers the redirect response. The client immediately requests /artists/Rolling_Stones/info. Scalatra, as always, matches the request from the bottom up.[5] The redirected request finally matches /artists/:name/info, which generates the desired response.
Redirects are stateless. Scalatra neither knows nor cares that the original request was rewritten.
As already mentioned, Scalatra is heavily influenced by the Sinatra framework, but Sinatra-style path expressions aren’t the only game in town. Ruby on Rails[6] uses an alternate syntax—one that lends itself a little better to the format example introduced in listing 3.14.
Technically, it’s the Ruby Rack::Mount::Strexp module.
Listing 3.19 demonstrates the use of Rails-style path expressions. The implicit string2RouteMatcher method overrides the inherited ScalatraServlet.
Route matchers extend the org.scalatra.RouteMatcher trait. ScalatraServlet inherits protected methods that perform implicit conversions of strings, regular expressions, and Boolean expressions to RouteMatcher. You can extend the DSL to support arbitrary types by creating your own implicit conversions to RouteMatcher.
The expression is very similar to the standard path expressions. Instead of using ? to make the previous token optional, the Rails style uses () to make its entire contents optional. This allows you to express that, if there is a period, the format must also be provided.
Table 3.6 takes the URIs from table 3.3 and shows how the Rails routes can lock down two typically undesirable matches.
URI |
Sinatra /artists/:name/info.?:format? |
Rails /artists/:name/info(.:format) |
---|---|---|
/artists/Otis_Redding/info | Yes | Yes |
/artists/Otis_Redding/info.json | Yes | Yes |
/artists/Otis_Redding/info.xml | Yes | Yes |
/artists/Otis_Redding/info. | Yes | No |
/artists/Otis_Redding/infojson | Yes | No |
18.227.134.133