Spray(3)spray-routing
The spray-routing module provides a high-level, very flexible routing DSL for RESTful web services. Normally I will use it either on top of a spray-can HttpServer or inside of a servlet container together with spray-servlet.
Dependencies
spray-http, spray-httpx, spray-util, spray-caching, shapeless, Scalate, akka-actor
"io.spray" % "spray-routing" % "1.1-M7",
"com.typesafe" % "config" % "1.0.0"
Configuration
Just like Akka spray-routing relies on the type safe config library for configuration. The same configuration file with akka, application.conf(reference.conf).
Getting Started
SimpleRoutingApp
package com.sillycat.easyspray.external
import spray.routing.SimpleRoutingApp
import spray.http.MediaTypes._
import scala.concurrent.duration._
object EasyRoutingApp extends App with SimpleRoutingApp {
startServer(interface = "localhost", port = 8080) {
get {
path("") {
redirect("/hello")
} ~
path("hello") {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete {
<html>
<h1>Say hello to <em>spray</em> on <em>spray-can</em>!</h1>
<p>(<a href="/stop?method=post">stop server</a>)</p>
</html>
}
}
}
} ~
(post | parameter('method ! "post")) {
path("stop") {
complete {
system.scheduler.scheduleOnce(1 second span) {
system.shutdown()
}
"Shutting down in 1 second…"
}
}
}
}
}
Key Concepts
Big Picture
We have spray-can HttpServer and the spray-servlet connector servlet both an actor-level to response to incoming HTTP requests by simply replying with an HttpResponse.
We can do complete REST API by pattern-matching against the incoming HttpRequest. But it is not wise to do in that way.
As an alternative spray-routing provides a flexible DSL for expressing service behavior.
def receive = runRoute {
path("ping"){
get{
complete("PONG")
}
}
}
Routes
type Route = RequestContext => Unit
Generally when a route receives a request (or rather a RequestContext for it) it can do one of the three things:
1. Complete the request by calling requestContext.complete(…)
2. Reject the request by calling requestContext.reject(…)
3. Ignore the request
Constructing Routes
ctx => ctx.complete("Response") ----> complete("Response")
Composing Routes
Route transformation, Route filtering, Route chaining
The Routing Tree
varl route =
a{
b{
c{…} ~
d{…} ~
...
} ~
...
}
Directives
val route =
path("order" / IntNumber) { id =>
(get | put) { ctx =>
ctx.complete("Received " + ctx.request.method + " request for order " + id)
} }
Or
val getOrPut = get | put
val route =
(path("order" / IntNumber) & getOrPut) { id => ctx =>
ctx.complete("Received " + ctx.request.method + " request for order " + id) }
Rejections
The ~ operator was introduced, which connects two routes in a way that allows a second route to get a go at a request if the first route "rejected" it.
Exception Handling
import spray.routing.SimpleRoutingApp
import spray.http.MediaTypes._
import scala.concurrent.duration._
import spray.util.LoggingContext
import spray.routing.ExceptionHandler
import spray.util.LoggingContext
import spray.http.StatusCodes._
import spray.routing._
object EasyRoutingApp extends App with SimpleRoutingApp {
implicit def myExceptionHandler(implicit log: LoggingContext) =
ExceptionHandler.fromPF {
case e: ArithmeticException => ctx =>
log.warning("Request {} could not be handled normally", ctx.request)
ctx.complete(InternalServerError, "Bad numbers, bad result!!!")
}
…snip…
Timeout Handling
import spray.http._
import spray.routing._
class MyService extends Actor with HttpServiceActor {
def receive = handleTimeouts orElse runRoute(myRoute)
def myRoute: Route = `<my-route-definition>`
def handleTimeouts: Receive = {
case Timeout(x: HttpRequest) =>
sender ! HttpResponse(StatusCodes.InternalServerError, "Too late")
} }
References:
http://spray.io/documentation/spray-routing/
The spray-routing module provides a high-level, very flexible routing DSL for RESTful web services. Normally I will use it either on top of a spray-can HttpServer or inside of a servlet container together with spray-servlet.
Dependencies
spray-http, spray-httpx, spray-util, spray-caching, shapeless, Scalate, akka-actor
"io.spray" % "spray-routing" % "1.1-M7",
"com.typesafe" % "config" % "1.0.0"
Configuration
Just like Akka spray-routing relies on the type safe config library for configuration. The same configuration file with akka, application.conf(reference.conf).
Getting Started
SimpleRoutingApp
package com.sillycat.easyspray.external
import spray.routing.SimpleRoutingApp
import spray.http.MediaTypes._
import scala.concurrent.duration._
object EasyRoutingApp extends App with SimpleRoutingApp {
startServer(interface = "localhost", port = 8080) {
get {
path("") {
redirect("/hello")
} ~
path("hello") {
respondWithMediaType(`text/html`) { // XML is marshalled to `text/xml` by default, so we simply override here
complete {
<html>
<h1>Say hello to <em>spray</em> on <em>spray-can</em>!</h1>
<p>(<a href="/stop?method=post">stop server</a>)</p>
</html>
}
}
}
} ~
(post | parameter('method ! "post")) {
path("stop") {
complete {
system.scheduler.scheduleOnce(1 second span) {
system.shutdown()
}
"Shutting down in 1 second…"
}
}
}
}
}
Key Concepts
Big Picture
We have spray-can HttpServer and the spray-servlet connector servlet both an actor-level to response to incoming HTTP requests by simply replying with an HttpResponse.
We can do complete REST API by pattern-matching against the incoming HttpRequest. But it is not wise to do in that way.
As an alternative spray-routing provides a flexible DSL for expressing service behavior.
def receive = runRoute {
path("ping"){
get{
complete("PONG")
}
}
}
Routes
type Route = RequestContext => Unit
Generally when a route receives a request (or rather a RequestContext for it) it can do one of the three things:
1. Complete the request by calling requestContext.complete(…)
2. Reject the request by calling requestContext.reject(…)
3. Ignore the request
Constructing Routes
ctx => ctx.complete("Response") ----> complete("Response")
Composing Routes
Route transformation, Route filtering, Route chaining
The Routing Tree
varl route =
a{
b{
c{…} ~
d{…} ~
...
} ~
...
}
Directives
val route =
path("order" / IntNumber) { id =>
(get | put) { ctx =>
ctx.complete("Received " + ctx.request.method + " request for order " + id)
} }
Or
val getOrPut = get | put
val route =
(path("order" / IntNumber) & getOrPut) { id => ctx =>
ctx.complete("Received " + ctx.request.method + " request for order " + id) }
Rejections
The ~ operator was introduced, which connects two routes in a way that allows a second route to get a go at a request if the first route "rejected" it.
Exception Handling
import spray.routing.SimpleRoutingApp
import spray.http.MediaTypes._
import scala.concurrent.duration._
import spray.util.LoggingContext
import spray.routing.ExceptionHandler
import spray.util.LoggingContext
import spray.http.StatusCodes._
import spray.routing._
object EasyRoutingApp extends App with SimpleRoutingApp {
implicit def myExceptionHandler(implicit log: LoggingContext) =
ExceptionHandler.fromPF {
case e: ArithmeticException => ctx =>
log.warning("Request {} could not be handled normally", ctx.request)
ctx.complete(InternalServerError, "Bad numbers, bad result!!!")
}
…snip…
Timeout Handling
import spray.http._
import spray.routing._
class MyService extends Actor with HttpServiceActor {
def receive = handleTimeouts orElse runRoute(myRoute)
def myRoute: Route = `<my-route-definition>`
def handleTimeouts: Receive = {
case Timeout(x: HttpRequest) =>
sender ! HttpResponse(StatusCodes.InternalServerError, "Too late")
} }
References:
http://spray.io/documentation/spray-routing/