spray: REST on Akka

19,605 views

Published on

The slides of my talk at @nescalas (the northeast scala symposium, nescala.org) on March 9th, 2012

Published in: Technology
0 Comments
27 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
19,605
On SlideShare
0
From Embeds
0
Number of Embeds
2,945
Actions
Shares
0
Downloads
37
Comments
0
Likes
27
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • spray: REST on Akka

    1. REST on Akka⇗ northeast scala symposium March 9th, 2012
    2. What is spray?
    3. What is spray?Suite of libraries for building and consumingRESTful web services on top of Akka
    4. What is spray?Suite of libraries for building and consumingRESTful web services on top of Akka• First released about 1 year ago
    5. What is spray?Suite of libraries for building and consumingRESTful web services 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?Suite of libraries for building and consumingRESTful web services on top of Akka• First released about 1 year ago• Principles: lightweight, async, non-blocking, actor-based, modular, few deps, testable• Philosophy: library, not framework
    7. Components
    8. Components• Rich immutable HTTP model
    9. Components• Rich immutable HTTP model• spray-server: DSL for server-side API construction
    10. Components• Rich immutable HTTP model• spray-server: DSL for server-side API construction• spray-client: complementary HTTP client
    11. 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
    12. 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)
    13. spray-server
    14. spray-server• Runs on servlet containers or spray-can
    15. spray-server• Runs on servlet containers or spray-can• Tool for building a “self-contained” API layer
    16. 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
    17. 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
    18. Basic Architecture Application Business Logic
    19. Basic ArchitectureREST API layer Application Routing Logic Business Logic
    20. Basic Architecture REST API layer ApplicationHTTP Request Routing Logic Business Logic
    21. Basic Architecture REST API layer ApplicationHTTP Request Action Routing Logic Business Logic
    22. Basic Architecture REST API layer ApplicationHTTP Request Action domain Routing object ! Business Logic Logic
    23. Basic Architecture REST API layer ApplicationHTTP Request Action domain Routing object ! Business Logic Logic Reply
    24. Basic Architecture REST API layer ApplicationHTTP Request Action domain Routing object ! Business Logic Logic Reply
    25. Basic Architecture REST API layer ApplicationHTTP Request Action domain Routing object ! Business Logic LogicHTTP Response Reply
    26. API Layer Responsibilities
    27. API Layer Responsibilities• Request routing based on method, path, query parameters, entity
    28. API Layer Responsibilities• Request routing based on method, path, query parameters, entity• (Un)marshalling to / from domain objects
    29. API Layer Responsibilities• Request routing based on method, path, query parameters, entity• (Un)marshalling to / from domain objects• Encoding / decoding
    30. API Layer Responsibilities• Request routing based on method, path, query parameters, entity• (Un)marshalling to / from domain objects• Encoding / decoding• Authentication / authorization
    31. 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
    32. 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
    33. Route ExampleA 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 } } }
    34. Routing BasicsRoutes in spray: type Route = RequestContext => Unit
    35. Routing BasicsRoutes in spray: type Route = RequestContext => Unit Explicit continuation- passing style
    36. Routing BasicsRoutes in spray: type Route = RequestContext => Unit Explicit continuation- passing styleCentral object: case class RequestContext( request: HttpRequest, ...) {   def complete(...) { ... }   def reject(...) { ... }   ... }
    37. Routing BasicsThe simplest route: ctx => ctx.complete("Say hello to spray")
    38. Routing BasicsThe simplest route: ctx => ctx.complete("Say hello to spray")or: _.complete("Say hello to spray")
    39. Routing BasicsThe simplest route: ctx => ctx.complete("Say hello to spray")or: _.complete("Say hello to spray")or using a “directive”: completeWith("Say hello to spray")
    40. Routing BasicsThe 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)
    41. DirectivesRoute 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 } } }
    42. 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 } } }
    43. DirectivesRoute 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 } } }
    44. DirectivesRoute 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 } } }
    45. DirectivesRoute 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 }
    46. DirectivesRoute 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 } } }
    47. DirectivesRoute 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 } } }
    48. DirectivesRoute 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!
    49. DirectivesDRYing up with the `|` operator:val route = path("order" / HexIntNumber) { id => (get | put) { ctx => ctx.complete("Received " + ctx.request.method + " request for order " + id) } }
    50. DirectivesPulling out a custom directive:val getOrPut = get | putval route = path("order" / HexIntNumber) { id => getOrPut { ctx => ctx.complete("Received " + ctx.request.method + " request for order " + id) } }
    51. DirectivesThe `&` operator as alternative to nesting:val getOrPut = get | putval route = (path("order" / HexIntNumber) & getOrPut) { id => ctx => ctx.complete("Received " + ctx.request.method + " request for order " + id) }
    52. DirectivesPulling 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) }
    53. DirectivesOperators are type-safe:val orderPath = path("order" / IntNumber)
    54. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)
    55. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | get
    56. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | get
    57. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)
    58. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)
    59. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)val dir = orderPath | parameter(order.as[Int])
    60. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)val dir = orderPath | parameter(order.as[Int])
    61. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)val dir = orderPath | parameter(order.as[Int])val order = orderPath & parameters(oem, expired ?)
    62. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval dir = orderPath | path("[^/]+".r / DoubleNumber)val dir = orderPath | parameter(order.as[Int])val order = orderPath & parameters(oem, expired ?)
    63. Directives Compiles?Operators are type-safe:val orderPath = path("order" / IntNumber)val dir = orderPath | getval 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}
    64. Directivesspray 0.9.0 comes with 69 predefined directives:alwaysPass, authenticate, authorize, autoChunk, cache, cacheResults, clientIP,completeWith, content, cookie, decodeRequest, delete, deleteCookie, detach,dynamic, encodeResponse, filter, filter1, filter2, filter3, filter4, filter5, filter6,filter7, filter8, filter9, formField, formFields, get, getFromDirectory, getFromFile,getFromFileName, getFromResource, getFromResourceDirectory, handleWith,hardFail, head, headerValue, headerValuePF, host, jsonpWithParameter, method,optionalCookie, options, parameter, parameters, path, pathPrefix, post,produce, provide, put, redirect, reject, respondWithContentType,respondWithHeader, respondWithHeaders, respondWithMediaType,respondWithStatus, setCookie, trace, transformChunkedResponse,transformRejections, transformRequest, transformRequestContext,transformResponse, transformRoute, transformUnchunkedResponse, validate
    65. Real World Examplelazy 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,
    66. } } }~ } } Real World Example tokenAuthenticate { user => path("ranking") { get { countAndTime(user, "ranking") { parameters(fixed ? 0, mobile ? 0, sms ? 0, mms ? 0, data ? 0).as(RankingDescriptor) { descr => (rankingService ? Ranking(descr)).mapTo[RankingResult] } } } }~ path("accounts") { post { authorize(user.isAdmin) { content(as[AccountDetails]) { details => (accountService ? NewAccount(details)).mapTo[OpResult] } } } }~ path("account" / IntNumber) { accountId => get { ... } ~ put { ... } ~ delete { ... } } }}~pathPrefix("v1") {
    67. } }~ Real World Example path("account" / IntNumber) { accountId => get { ... } ~ put { ... } ~ delete { ... } } } }~ pathPrefix("v1") { proxyToDjango } }~ pathPrefix("doc") { respondWithHeader(`Cache-Control`(`max-age`(3600))) { transformResponse(_.withContentTransformed(markdown2Html)) { getFromResourceDirectory("doc/root", pathRewriter = appendFileExt) } } }~ }~ cacheIfEnabled { encodeResponse(Gzip) { getFromResourceDirectory("public") } }}
    68. Best Practices
    69. Best Practices• Keep route structure clean and readable, pull out all logic into custom directives
    70. Best Practices• Keep route structure clean and readable, pull out all logic into custom directives• Don’t let API layer leak into application
    71. 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
    72. 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`
    73. 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 turn- around
    74. There is more ...
    75. There is more ...• SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport
    76. There is more ...• SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport• Asynchronous response push streaming
    77. There is more ...• SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport• Asynchronous response push streaming• Testing spray routes
    78. There is more ...• SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport• Asynchronous response push streaming• Testing spray routes• RESTful errors
    79. There is more ...• SprayJsonSupport, LiftJsonSupport, TwirlSupport, ScalateSupport• Asynchronous response push streaming• Testing spray routes• RESTful errors• spray-client
    80. Current State
    81. Current State• spray 0.9.0 just released (last release for Akka 1.x)
    82. Current State• spray 0.9.0 just released (last release for Akka 1.x)• Current focus: Release first Akka 2.0 compatible milestone of spray 1.0
    83. Current State• spray 0.9.0 just released (last release for Akka 1.x)• Current focus: Release first Akka 2.0 compatible milestone of spray 1.0• Coming features: new documentation site, deeper REST support, monitoring, request throttling, and more ...
    84. A few current sprayers ...
    85. Getting started• Main site & documentation: https://github.com/spray/spray/wiki• Mailing list: http://groups.google.com/group/spray-user• Twitter: @spraycc
    86. Thank you!
    87. More
    88. Proxying with sprayCombining 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") } } }

    ×