3. What is spray?
Vision: Provide the best toolkit for REST/HTTP
and low-level network-IO on top of Akka
4. What is spray?
Vision: Provide the best toolkit for REST/HTTP
and low-level network-IO on top of Akka
⢠First released about 1 year ago
5. What is spray?
Vision: Provide the best toolkit for REST/HTTP
and low-level network-IO on top of Akka
⢠First released about 1 year ago
⢠Principles: lightweight, async, non-blocking,
actor-based, modular, few deps, testable
6. What is spray?
Vision: Provide the best toolkit for REST/HTTP
and low-level network-IO on top of Akka
⢠First released about 1 year ago
⢠Principles: lightweight, async, non-blocking,
actor-based, modular, few deps, testable
⢠Philosophy: set of libraries, not framework
10. Current State
Transitioning from Akka 1.3 to Akka 2.0
⢠spray 0.9.0 for Akka 1.3 released in March
⢠spray 1.0-M1 for Akka 2.0 two weeks ago
11. Current State
Transitioning from Akka 1.3 to Akka 2.0
⢠spray 0.9.0 for Akka 1.3 released in March
⢠spray 1.0-M1 for Akka 2.0 two weeks ago
⢠Next: second milestone of spray 1.0
12. Current State
Transitioning from Akka 1.3 to Akka 2.0
⢠spray 0.9.0 for Akka 1.3 released in March
⢠spray 1.0-M1 for Akka 2.0 two weeks ago
⢠Next: second milestone of spray 1.0
Focus of this talk
24. spray-routing
⢠Runs on spray-servlet or spray-can
⢠Tool for building a âself-containedâ API layer
25. spray-routing
⢠Runs on spray-servlet or spray-can
⢠Tool for building a âself-containedâ API layer
⢠Central element:
Routing DSL for deďŹning web API behavior
26. spray-routing
⢠Runs on spray-servlet or spray-can
⢠Tool for building a âself-containedâ API layer
⢠Central element:
Routing DSL for deďŹning web API behavior
⢠Focus: RESTful web API, not web GUI
44. API Building in spray
⢠HTTP messages are actor messages
class PingServiceActor extends Actor {
def receive = {
case HttpRequest(GET, "/ping", _, _, _) =>
sender ! HttpResponse(200, "PONG")
}
}
45. API Building in spray
⢠HTTP messages are actor messages
class PingServiceActor extends Actor {
def receive = {
case HttpRequest(GET, "/ping", _, _, _) =>
sender ! HttpResponse(200, "PONG")
}
}
⢠Could build services only via pattern-matching
46. API Building in spray
⢠HTTP messages are actor messages
class PingServiceActor extends Actor {
def receive = {
case HttpRequest(GET, "/ping", _, _, _) =>
sender ! HttpResponse(200, "PONG")
}
}
⢠Could build services only via pattern-matching
⢠But: pattern-matching becomes awkward for
more complex service deďŹnitions
47. API Building in spray: DSL
Simple example:
class MyServiceActor extends Actor with Routing {
def receive = receiveFromRoute {
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
} ~
put {
completeWith {
"Received PUT request for order " + id
}
}
}
}
}
50. sprays Routing DSL
⢠Built from recombinant elements called
âdirectivesâ
⢠Concise, readable, maintainable
51. sprays Routing DSL
⢠Built from recombinant elements called
âdirectivesâ
⢠Concise, readable, maintainable
⢠Highly composable
52. sprays Routing DSL
⢠Built from recombinant elements called
âdirectivesâ
⢠Concise, readable, maintainable
⢠Highly composable
⢠Type-safe
53. sprays Routing DSL
⢠Built from recombinant elements called
âdirectivesâ
⢠Concise, readable, maintainable
⢠Highly composable
⢠Type-safe
⢠Easily extensible with custom constructs
54. sprays Routing DSL
⢠Built from recombinant elements called
âdirectivesâ
⢠Concise, readable, maintainable
⢠Highly composable
⢠Type-safe
⢠Easily extensible with custom constructs
⢠Directly interfaces with Akka API
56. Routing Basics
Routes in spray:
type Route = RequestContext => Unit Explicit
continuation-
passing style
57. Routing Basics
Routes in spray:
type Route = RequestContext => Unit Explicit
continuation-
passing style
Central object:
case class RequestContext(
request: HttpRequest,
...) {
  def complete(...) { ... }
  def reject(...) { ... }
  ...
}
59. Routing Basics
The simplest route:
ctx => ctx.complete("Say hello to spray")
or:
_.complete("Say hello to spray")
60. 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")
61. 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)
62. 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
}
}
}
63. Directives
Route structure built with directives:
val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
directive
}~
name put {
completeWith {
"Received PUT request for order " + id
}
}
}
64. Directives
Route structure built with directives:
val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}
}~
args
put {
completeWith {
"Received PUT request for order " + id
}
}
}
65. Directives
Route structure built with directives:
val route: Route =
path("order" / HexIntNumber) { id =>
get { extractions
completeWith {
"Received GET request for order " + id
}
}~
put {
completeWith {
"Received PUT request for order " + id
}
}
}
66. 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
}
} inner route
}
67. Directives
Route structure built with directives:
val route: Route =
path("order" / HexIntNumber) { id =>
get {
completeWith {
"Received GET request for order " + id
}~
} route concatenation:
put { recover from rejections
completeWith {
"Received PUT request for order " + id
}
}
}
68. 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
}
}
}
69. 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
}
} Route structure
}
forms a tree!
75. Best Practices
⢠Keep route structure clean and readable,
pull out all logic into custom directives
76. Best Practices
⢠Keep route structure clean and readable,
pull out all logic into custom directives
⢠Donât let API layer leak into application
77. 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
78. 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
⢠Use sbt-revolver + JRebel for fast dev turn-
around
88. Whatâs next?
⢠release 1.0 featuring improved and simpliďŹed
API for Akka 2.0
⢠Better and deeper documentation
(follow Akkas model of treating docs as code)
89. Whatâs next?
⢠release 1.0 featuring improved and simpliďŹed
API for Akka 2.0
⢠Better and deeper documentation
(follow Akkas model of treating docs as code)
⢠Coming features: deeper REST support,
monitoring, request throttling, ...
90. Whatâs next?
⢠release 1.0 featuring improved and simpliďŹed
API for Akka 2.0
⢠Better and deeper documentation
(follow Akkas model of treating docs as code)
⢠Coming features: deeper REST support,
monitoring, request throttling, ...
⢠Possibly: websockets, SPDY
95. 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
}
}
}
96. Directives
DRYing up with the `|` operator:
val route =
path("order" / HexIntNumber) { id =>
(get | put) { ctx =>
ctx.complete("Received " + ctx.request.method +
" request for order " + id)
}
}
97. Directives
Pulling out a custom directive:
val getOrPut = get | put
val route =
path("order" / HexIntNumber) { id =>
getOrPut { ctx =>
ctx.complete("Received " + ctx.request.method +
" request for order " + id)
}
}
98. Directives
The `&` operator as alternative to nesting:
val getOrPut = get | put
val route =
(path("order" / HexIntNumber) & getOrPut) { id =>
ctx =>
ctx.complete("Received " + ctx.request.method +
" request for order " + id)
}
99. Directives
Pulling out once more:
val orderGetOrPut =
path("order" / HexIntNumber) & (get | put)
val route =
orderGetOrPut { id => ctx =>
ctx.complete("Received " + ctx.request.method +
" request for order " + id)
}
104. Directives
Compiles?
Operators are type-safe:
val orderPath = path("order" / IntNumber)
val dir = orderPath | get
val dir = orderPath | path("[^/]+".r / DoubleNumber)
105. Directives
Compiles?
Operators are type-safe:
val orderPath = path("order" / IntNumber)
val dir = orderPath | get
val dir = orderPath | path("[^/]+".r / DoubleNumber)
106. 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])
107. 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])
108. 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 ?)
109. 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 ?)
110. 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
}
111. Proxying with spray
Combining with spray-client to build a proxy:
val conduit = new HttpConduit("target.example.com", 8080)
lazy val proxyToTarget: Route = { ctx =>
ctx.complete {
conduit.sendReceive {
ctx.request.withHeadersTransformed {
_.ďŹlter(_.name != "Host")
}
}.map {
_.withHeadersTransformed {
_.ďŹlter(_.name != "Date")
}
}
}