Chapter 3. Routing

This chapter covers

  • Defining a route
  • Choosing the right HTTP method
  • Matching path expressions
  • Exploring advanced routes

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]

1

We might not topple iTunes or Amazon Music, but we’ll have a good time name-checking our favorite artists.

3.1. Anatomy of a route

Routes are declared directly in the body of your Scalatra application. Let’s look at an example in figure 3.1.

Figure 3.1. Anatomy of a route

A Scalatra route is composed of three main components:

  • The HTTP method— This is required. HTTP supports only eight methods, and we’ll cover them all in section 3.2.
  • The route matcher— The route is matched by a single path expression. Route matchers are flexible in number and type. Matchers are discussed in section 3.3.
  • The action— This is also required, and it’s the block of code responsible for handling the request and generating a response. Actions will be discussed in chapter 4.

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.

3.2. Choosing the right method

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.

Tip

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.

3.2.1. The CRUD methods

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.

Table 3.1. CRUD-to-HTTP method mapping

CRUD operation

HTTP method

Create
  • POST
  • PUT
Read
  • GET
Update
  • PUT
  • PATCH
Delete
  • DELETE
Note

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

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.

Listing 3.1. Fetching an artist with get
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:

  • When you’re implementing a read-only operation, such as the R in CRUD— A GET action must not have any observable side effects. It’s fine to populate a cache from a GET action, but it would be inappropriate to modify the document being retrieved.
  • When the request can be submitted repeatedly— Building on the read-only nature, the application should be prepared to handle identical GET requests. It need not respond identically if an intervening event changes the application’s state. For web interfaces, ask yourself if your application is prepared for a user who leans on the F5 key.
  • When the response should be bookmarkable or indexed in search engines— Whenever you see a link in a search engine, it’s a GET request. Your bookmarks all represent GET requests. The QR code on the back of your company’s T-shirt generates a GET request. If the URL is meant to be shared, think GET.
Post

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.

Listing 3.2. Adding a new artist with 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:

  • When implementing create operations, such as the C in CRUD— In this usage, a POST action reads the request body according to its Content-Type header and adds a new resource to the application.
  • When the server is responsible for generating a URI for the created entity— It’s bad form to allow POSTs on resources that don’t already exist. A POST request often hits a parent URI and responds with a Location header giving the URI of the newly created resource. This is similar to autogenerating a key in a database when a record is inserted.
  • When you’re implementing a write operation, and nothing else seems to fit— POST is a good default choice, because it’s the only CRUD method that’s not idempotent. A client should be able to resubmit a GET, DELETE, or PUT request and expect the same result. A POST offers no such guarantee and is thus more flexible in its implementation. This is the reason why web browsers issue a warning when a POST request is resubmitted.
Warning

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

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.

Listing 3.3. Updating an 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:

  • When implementing update operations, such as the U in CRUD— All the same techniques used to read a POST request body are available to PUT. It’s not uncommon for a POST and a PUT action to share code.
  • When implementing create operations, such as the C in CRUD when the URI is known— Note in the CRUD mapping table that a create operation can be implemented as either a POST or a PUT. The correct choice depends on whether the resource’s URI is fully known to the client. Unlike a POST, a PUT can be executed on a new URI. Use PUT if the client assigns the identifier, or POST if the server does.
PATCH

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.

DELETE

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.

Listing 3.4. Removing an artist with delete

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:

  • When implementing delete operations, such as the D in CRUD— This is the most obvious of the CRUD methods. Indeed, it’s the only one whose CRUD name matches its HTTP name.
The perils of not following the rules

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.

3.2.2. The lesser-known methods

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.

HEAD

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.

Listing 3.5. Optimizing HEAD requests with an explicit HEAD
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.

Use HEAD in this case:

  • When the default implementation is suboptimal— Because a HEAD response can be derived from a GET, Scalatra gives it to you for free by calling GET with a null response writer.[2]

    2

    Actually, this functionality is provided by the underlying servlet container. Scalatra’s philosophy is to build on existing standards where possible.

Premature optimization

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.

OPTIONS

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]

3

If a music service doesn’t carry Frank Zappa, it’s not worth running.

Listing 3.6. Returning the supported methods with options

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:

  • When the default implementation is incorrect— By default, Scalatra searches its routing table, seeking a matching route for each HTTP method. Instead of executing the action, as with other methods, it constructs an Allow header with all the matching methods. In some cases, the user might want to customize the methods. A delete method may be supported for some resources matched by a given route, but others may be protected from deletion. A customized view can be generated by explicitly overriding the OPTIONS route.
  • For security— Every bit of unnecessary information a service exposes potentially gives an attacker another clue to break into your application. Options are considered rather harmless by most, but some users would prefer to not give this information away at all.

The following listing declares a route that forbids all requests with method option.

Listing 3.7. Forbidding all OPTIONS requests

Methods unsupported by Scalatra

There are two standard HTTP methods we haven’t discussed:

  • A TRACE request echoes the request back as the body of the response. The implementation of this method is rigidly defined by spec. Scalatra inherits a default implementation through the servlet container and thus doesn’t provide a method to customize the behavior.
  • CONNECT requests are used in SSL tunneling, which is not relevant to Scalatra. Scalatra handles these requests by issuing a 404 response. Like TRACE, this behavior is not overridable.

3.2.3. Overriding the methods

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.

Listing 3.8. Method override example

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. Using method-override conventions

Listing 3.9 shows an example of Scalatra’s stackable modifications, where additional behaviors can be composed onto the core with mixin traits.

3.3. Route matchers

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:

  • Path expressions (string)
  • Regular expressions
  • Boolean expressions

We’ll examine each of these in detail, and later show you how to create your own.

3.3.1. Path expressions

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.

Note

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.

Static path expressions

In the following listing, you’ll see a static path expression. This path expression declares a literal path to match.

Listing 3.10. Static path expression

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.

Listing 3.11. Routes with static path expressions
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.

Path parameters

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.

Listing 3.12. Path parameter example

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:

  • A path parameter is never an empty string. At least one character must be matched.
  • A path parameter matches everything up to the next special character: /, ?, or #.

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.

Table 3.2. Some examples of matching /artists/:name/info

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

URL encoding

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.

Optional parameters

A ? in a path expression makes the previous character or path parameter optional. The following listing demonstrates an optional trailing slash.

Listing 3.13. 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:

  • http://localhost:8080/artists
  • http://localhost:8080/artists/

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.

Warning

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.

Listing 3.14. Optional format suffix

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.

Table 3.3. Format examples

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.

Splat support

Path expressions also support splat parameters. Splat parameters are nicknamed for the * character that declares them.

Listing 3.15. Splat example

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.

3.3.2. Regular expressions

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:

  • For a year, we expect a four-digit number.
  • For a decade, we expect a four-digit number ending in 0, followed by an s.

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.

Listing 3.16. Regular expression route

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:

  • d matches a digit, 0–9.
  • {3} repeats the previous expression (d) exactly three times.
  • 0 is a literal 0.
  • () declares a capture group. The characters matched by the expression inside the capture group are made available to the route under the param name "captures". In this case, you capture exactly three digits followed by a zero.

What makes a regular expression in Scala?

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.

Table 3.4. Year and decade URI examples

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.

Table 3.5. Path expression–to–regular expression translation

Path expression

Regex equivalent

:param ([/*#?]+)
? ?
* (.*?)
() ()[a]

a

() are not special characters in a path expression. Only the characters already listed are special in a path expression.

3.3.3. Boolean expressions

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.

Formal definition of “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?

  • A path expression or regular expression returns Some extracted route parameters if the expression matches the request URI.
  • A Boolean expression returns Some empty map of route parameters if the expression evaluates to true. As already stated, Booleans don’t extract parameters. If the expression evaluates to false, the route matcher returns None, and the route doesn’t match.

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.

Listing 3.17. Matching mobile requests

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.

Warning

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.

3.4. Advanced route matching

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.

3.4.1. Conflict resolution

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.

4

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.

Listing 3.18. Article normalizer

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.

Warning

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.

Figure 3.2. Tracing a request for The_Rolling_Stones

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.

5

Redirects are stateless. Scalatra neither knows nor cares that the original request was rewritten.

3.4.2. Rails-style path expressions

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.

6

Technically, it’s the Ruby Rack::Mount::Strexp module.

Listing 3.19. Rails matcher

Listing 3.19 demonstrates the use of Rails-style path expressions. The implicit string2RouteMatcher method overrides the inherited ScalatraServlet.

What type are route matchers, anyway?

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.

Table 3.6. Rails-style format examples

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

3.5. Summary

  • Choosing the correct HTTP methods results in APIs that act in accordance with prevailing standards.
  • It’s important to choose intuitive URIs for application resources. You can use Scalatra’s route resolution to create concise code.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.134.133