REST on Akka	

!

Antoine Comte	

Twitter : @comte_a	

antoine.comte@gmail.com	

Slidedeck courtesy of the Spray team
What is spray?
Suite of libraries for building and consuming
RESTful web services on top of Akka	


• First released about 2 year ago	

• Principles: lightweight, async, non-blocking,
actor-based, modular, few deps, testable	


• Philosophy: library, not framework
Components
• Rich immutable HTTP model	

• spray-server:


DSL for server-side API construction	


• spray-client: complementary HTTP client 	

• spray-can: low-level HTTP server and client	

• spray-json: straight JSON in scala (no Akka)
spray-server
• Runs on servlet containers or spray-can	

• Tool for building a “self-contained” API layer	

• Central element:

Routing DSL for defining web API behavior	


• Focus: RESTful web API, not web GUI
Basic Architecture
Application

Business	

Logic
Basic Architecture
REST API layer

Application

HTTP Request

Routing	

Logic

Business	

Logic
Basic Architecture
REST API layer

HTTP Request

Action

Routing	

Logic

HTTP Response

Application

domain
object !
Reply

Business	

Logic
API Layer Responsibilities
• Request routing based on method, path,
query parameters, entity	


• (Un)marshalling to / from domain objects	

• Encoding / decoding	

• Authentication / authorization	

• Caching and serving static content	

• RESTful error handling
Route Example
A simple spray route:	

!

val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
} ~
put {
completeWith {
"Received PUT request for order " + id
}
}
}
Routing Basics
Routes in spray:	

type Route = RequestContext => Unit

!

Central object:	

case class RequestContext(
request: HttpRequest,
...) {
  def complete(...) { ... }
  def reject(...) { ... }
  ...
}

Explicit
continuation-

passing style
Routing Basics
The simplest route:	

ctx => ctx.complete("Say hello to spray")

or:	

_.complete("Say hello to spray")

or using a “directive”:	

completeWith("Say hello to spray")
def completeWith[T :Marshaller](value: => T): Route =
_.complete(value)
Directives
Route structure built with directives:	

!

val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
} ~
put {
completeWith {
"Received PUT request for order " + id
}
}
}

extractions

directive
name

args
route concatenation:
recover from rejections
Route structure
forms a tree!

inner route
Directives
Compiles?

Operators are type-safe:	

val orderPath = path("order" / IntNumber)
val dir = orderPath | get
val dir = orderPath | path("[^/]+".r / DoubleNumber)
val dir = orderPath | parameter('order.as[Int])
val order = orderPath & parameters('oem, 'expired ?)
val route = order { (orderId, oem, expired) =>
... // inner route
}
Directives



spray 1.2 comes with 110 predefined directives:

alwaysCache, anyParam, anyParams, authenticate, authorize, autoChunk, cache, cachingProhibited,
cancelAllRejections, cancelRejection, clientIP, complete, compressResponse,
compressResponseIfRequested, cookie, decodeRequest, decompressRequest, delete, deleteCookie,
detach, dynamic, dynamicIf, encodeResponse, entity, extract, failWith, formField, formFields, get,
getFromBrowseableDirectories, getFromBrowseableDirectory, getFromDirectory, getFromFile,
getFromResource, getFromResourceDirectory, handleExceptions, handleRejections, handleWith, head,
headerValue, headerValueByName, headerValuePF, hextract, host, hostName, hprovide,
jsonpWithParameter, listDirectoryContents, logRequest, logRequestResponse, logResponse,
mapHttpResponse, mapHttpResponsePart, mapHttpResponseEntity, mapHttpResponseHeaders,
mapInnerRoute, mapRejections, mapRequest, mapRequestContext, mapRouteResponse,
mapRouteResponsePF, method, overrideMethodWithParameter, noop, onComplete, onFailure,
onSuccess, optionalCookie, optionalHeaderValue, optionalHeaderValueByName,
optionalHeaderValuePF, options, parameter, parameterMap, parameterMultiMap, parameters,
parameterSeq, pass, patch, path, pathPrefix, pathPrefixTest, pathSuffix, pathSuffixTest, post, produce,
provide, put, redirect, reject, rejectEmptyResponse, requestEncodedWith, requestEntityEmpty,
requestEntityPresent, respondWithHeader, respondWithHeaders, respondWithLastModifiedHeader,
respondWithMediaType, respondWithSingletonHeader, respondWithSingletonHeaders,
respondWithStatus, responseEncodingAccepted, rewriteUnmatchedPath, routeRouteResponse,
scheme, schemeName, setCookie, unmatchedPath, validate
Real World Example
lazy val route = {
encodeResponse(Gzip) {
path("") {
get {
redirect("/doc")
}
} ~
pathPrefix("api") {
jsonpWithParameter("callback") {
path("top-articles") {
get {
parameter('max.as[Int]) { max =>
validate(max >= 0, "query parameter 'max' must be >= 0") {
completeWith {
(topArticlesService ? max).mapTo[Seq[Article]]
}
}
}
}
} ~
tokenAuthenticate { user =>
path("ranking") {
get {
countAndTime(user, "ranking") {
parameters('fixed ? 0, 'mobile ? 0, 'sms ? 0, 'mms ? 0,
Best Practices
• Keep route structure clean and readable,

pull out all logic into custom directives	


• Don’t let API layer leak into application	

• Use (Un)marshalling infrastructure	

• Wrap blocking code with `detach`	

• Use sbt-revolver + JRebel for fast dev turnaround
There is more ...
• SprayJsonSupport, LiftJsonSupport,
TwirlSupport, ScalateSupport	


• Asynchronous response push streaming	

• Testing spray routes	

• RESTful errors	

• spray-client
Current State
• spray 1.2-RC2 just released

• Coming features: new documentation site,
deeper REST support, monitoring, request
throttling, and more ...
A few current sprayers ...
Getting started
• Main site & documentation:

http://spray.io/	


• Mailing list:


http://groups.google.com/group/spray-user	


• Twitter:


@sprayio
Thank you!

Spray human talks

  • 1.
    REST on Akka ! AntoineComte Twitter : @comte_a antoine.comte@gmail.com Slidedeck courtesy of the Spray team
  • 2.
    What is spray? Suiteof libraries for building and consuming RESTful web services on top of Akka • First released about 2 year ago • Principles: lightweight, async, non-blocking, actor-based, modular, few deps, testable • Philosophy: library, not framework
  • 3.
    Components • Rich immutableHTTP model • spray-server:
 DSL for server-side API construction • spray-client: complementary HTTP client • spray-can: low-level HTTP server and client • spray-json: straight JSON in scala (no Akka)
  • 4.
    spray-server • Runs onservlet containers or spray-can • Tool for building a “self-contained” API layer • Central element:
 Routing DSL for defining web API behavior • Focus: RESTful web API, not web GUI
  • 5.
  • 6.
    Basic Architecture REST APIlayer Application HTTP Request Routing Logic Business Logic
  • 7.
    Basic Architecture REST APIlayer HTTP Request Action Routing Logic HTTP Response Application domain object ! Reply Business Logic
  • 8.
    API Layer Responsibilities •Request routing based on method, path, query parameters, entity • (Un)marshalling to / from domain objects • Encoding / decoding • Authentication / authorization • Caching and serving static content • RESTful error handling
  • 9.
    Route Example A simplespray route: ! val route: Route = path("order" / HexIntNumber) { id => get { completeWith { "Received GET request for order " + id } } ~ put { completeWith { "Received PUT request for order " + id } } }
  • 10.
    Routing Basics Routes inspray: type Route = RequestContext => Unit ! Central object: case class RequestContext( request: HttpRequest, ...) {   def complete(...) { ... }   def reject(...) { ... }   ... } Explicit continuation-
 passing style
  • 11.
    Routing Basics The simplestroute: ctx => ctx.complete("Say hello to spray") or: _.complete("Say hello to spray") or using a “directive”: completeWith("Say hello to spray") def completeWith[T :Marshaller](value: => T): Route = _.complete(value)
  • 12.
    Directives Route structure builtwith directives: ! val route: Route = path("order" / HexIntNumber) { id => get { completeWith { "Received GET request for order " + id } } ~ put { completeWith { "Received PUT request for order " + id } } } extractions directive name args route concatenation: recover from rejections Route structure forms a tree! inner route
  • 13.
    Directives Compiles? Operators are type-safe: valorderPath = path("order" / IntNumber) val dir = orderPath | get val dir = orderPath | path("[^/]+".r / DoubleNumber) val dir = orderPath | parameter('order.as[Int]) val order = orderPath & parameters('oem, 'expired ?) val route = order { (orderId, oem, expired) => ... // inner route }
  • 14.
    Directives 
 spray 1.2 comeswith 110 predefined directives:
 alwaysCache, anyParam, anyParams, authenticate, authorize, autoChunk, cache, cachingProhibited, cancelAllRejections, cancelRejection, clientIP, complete, compressResponse, compressResponseIfRequested, cookie, decodeRequest, decompressRequest, delete, deleteCookie, detach, dynamic, dynamicIf, encodeResponse, entity, extract, failWith, formField, formFields, get, getFromBrowseableDirectories, getFromBrowseableDirectory, getFromDirectory, getFromFile, getFromResource, getFromResourceDirectory, handleExceptions, handleRejections, handleWith, head, headerValue, headerValueByName, headerValuePF, hextract, host, hostName, hprovide, jsonpWithParameter, listDirectoryContents, logRequest, logRequestResponse, logResponse, mapHttpResponse, mapHttpResponsePart, mapHttpResponseEntity, mapHttpResponseHeaders, mapInnerRoute, mapRejections, mapRequest, mapRequestContext, mapRouteResponse, mapRouteResponsePF, method, overrideMethodWithParameter, noop, onComplete, onFailure, onSuccess, optionalCookie, optionalHeaderValue, optionalHeaderValueByName, optionalHeaderValuePF, options, parameter, parameterMap, parameterMultiMap, parameters, parameterSeq, pass, patch, path, pathPrefix, pathPrefixTest, pathSuffix, pathSuffixTest, post, produce, provide, put, redirect, reject, rejectEmptyResponse, requestEncodedWith, requestEntityEmpty, requestEntityPresent, respondWithHeader, respondWithHeaders, respondWithLastModifiedHeader, respondWithMediaType, respondWithSingletonHeader, respondWithSingletonHeaders, respondWithStatus, responseEncodingAccepted, rewriteUnmatchedPath, routeRouteResponse, scheme, schemeName, setCookie, unmatchedPath, validate
  • 15.
    Real World Example lazyval route = { encodeResponse(Gzip) { path("") { get { redirect("/doc") } } ~ pathPrefix("api") { jsonpWithParameter("callback") { path("top-articles") { get { parameter('max.as[Int]) { max => validate(max >= 0, "query parameter 'max' must be >= 0") { completeWith { (topArticlesService ? max).mapTo[Seq[Article]] } } } } } ~ tokenAuthenticate { user => path("ranking") { get { countAndTime(user, "ranking") { parameters('fixed ? 0, 'mobile ? 0, 'sms ? 0, 'mms ? 0,
  • 16.
    Best Practices • Keeproute structure clean and readable,
 pull out all logic into custom directives • Don’t let API layer leak into application • Use (Un)marshalling infrastructure • Wrap blocking code with `detach` • Use sbt-revolver + JRebel for fast dev turnaround
  • 17.
    There is more... • SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport • Asynchronous response push streaming • Testing spray routes • RESTful errors • spray-client
  • 18.
    Current State • spray1.2-RC2 just released
 • Coming features: new documentation site, deeper REST support, monitoring, request throttling, and more ...
  • 19.
    A few currentsprayers ...
  • 20.
    Getting started • Mainsite & documentation:
 http://spray.io/ • Mailing list:
 http://groups.google.com/group/spray-user • Twitter:
 @sprayio
  • 21.