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 defining 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 defining 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 definitions
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
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 simplified
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 simplified
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 simplified
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 {
_.filter(_.name != "Host")
}
}.map {
_.withHeadersTransformed {
_.filter(_.name != "Date")
}
}
}