Path Filters

Johannes Rudolph edited this page Aug 14, 2013 · 6 revisions

. . . Deprecation Note

This documentation is for release 0.9.0 (from 03/2012), which is built against Scala 2.9.1 and Akka 1.3.1 (see Requirements for more information). Most likely, this is not the place you want to look for information. Please turn to the main spray site at http://spray.io for more information about other available versions.

. . .

Path filter directives filter requests based on the unmatched part of the request URIs path component (i.e. without scheme, host and query part). They come in two flavors:

  • The path directive applies a match expression to the complete unmatched path of the request.
  • The pathPrefix directive does the same but tries to only match a path prefix.

Both directives take one argument of type PathMatcher (or rather a subtype of PathMatcher), which is usually specified as a filter expression written in a mini-DSL.

The PathMatcher DSL

Just like spray routes are built from directives as building blocks more complex "PathMatchers" are created by combining simpler "PathMatchers" with operators.

By default the following things can be used as "atomic" PathMatchers:

  1. Simple Strings They simply match themselves and extract no value.

  2. The PathElement object It matches one or more arbitrary characters except / and is therefore equivalent, but more efficient, than a regex match "[^/]+".r.

  3. Regular expressions (with zero or one capturing group) They match whatever they match and extract one String value, which is either the complete match (if the regex contains no capturing group) or the captured group (if the regex contains a capturing group).

  4. The objects IntNumber, LongNumber, HexIntNumber, HexLongNumber, DoubleNumber They match either an Int, Long or Double literal and extract its value.

  5. The Remaining object It matches all remaining, unmatched characters of the request path and extracts them as a String value.

  6. The JavaUUID object It matches the string representation of a java.util.UUID instance and extracts it.

  7. The result of the pathMatcher(Map[String, T]) method This method creates a PathMatcher from the given Map. If the unmatched path starts with one of the maps keys the matcher consumes this path prefix and extracts the corresponding map value.

"PathMatchers" can be chained with the / or ~ operators. The difference between the two is that / also matches a separating slash character whereas ~ simply connects two "PathMatchers" without any additional logic.

Note that each call to a path or pathPrefix directive implies a leading slash match, so you never have to start your PathMatcher argument with a slash.

Examples

The following route only responds to requests to paths beginning with "/hello" (note that you do not have to specify the initial slash / character):

pathPrefix("hello") { ... }

The following three routes all respond to requests to "/hello/bob":

path("hello/bob") { ... }

path("hello" / "bob") { ... }

pathPrefix("hello") {
  path("bob") { // the leading slash before "bob" is implied
    ...
  }
}

Regular expression matches (without capturing groups):

path("order" / "\\d+".r) { id =>
  // 'id' contains the string of matched digits
  _.complete("The order id is: " + id)
}

pathPrefix("employee" / PathElement) { employee =>
  path("salary" / "\\d\\d\\d\\d".r / "\\d\\d".r) { (year, month) =>
    ...          
  } ~
  path("tenure") {
    ...
  }
}

Regex match with capturing group:

path("order(\\d+)".r) { id =>
  ... // 'id' contains the string of matched digits    
}

Chaining several regex matches:

path("post-" ~ "\\d\\d\\d\\d".r ~ "-(\\d\\d)".r ~ "-(\\d\\d)".r) {
  (year, month, day) =>
  ... // responds for example to "/post-2011-03-23"          
}

Matching and extracting Int values:

pathPrefix("order") {
  path("i" ~ IntNumber) { intNum =>
    ...
  } ~
  path("h" ~ HexIntNumber) { intNum =>
    ...
  }
}

Custom PathMatchers

If you often have to match and extract custom objects from the request path it probably makes sense to create a custom PathMatcher for your type. In many cases the easiest way for this is to reuse a regex matcher and wrap it with a string-to-type conversion. You might want to use the implementation of the DoubleNumber matcher as a starting point:

/**
 * A PathMatcher that matches and extracts a Double value. The matched string representation is the pure decimal,
 * optionally signed form of a double value, i.e. without exponent.
 */
object DoubleNumber extends PathMatcher1[Double] {
  private val regexMatcher = new SimpleRegexMatcher("""[+-]?\d*\.?\d*""".r)

  def apply(path: String) = {
    // use 'flatMapValue' on the result of any PathMatcher1 to convert the extracted value to another type
    regexMatcher(path).flatMapValue { string =>
      try {
        Some(java.lang.Double.parseDouble(string))
      } catch {
        case _: NumberFormatException => None
      }
    }
  }
}