Rest with-spray

40
Getting Some REST With Spray Nimrod Argov

Transcript of Rest with-spray

Getting Some REST With Spray

Nimrod Argov

–spray.io

“Open source toolkit for building REST/HTTP-based integration layers on top of Scala and Akka”

Another HTTP Framework?

Netty

Servlets

Undertow

Play!

Sure we can.

Is it good enough?

Things We Don’t WantContainers XML Mutability Java under the surface

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<servlet> <servlet-name>controlServlet</servlet-name> <servlet-class> com.jenkov.butterfly.ControlServlet </servlet-class> </servlet>

<servlet-mapping> <servlet-name>controlServlet</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>

Not a FrameworkA set of libraries for http integration

Testableand not just e2e

ScalaCase Classes Functions as Values Collections Type Safety

AkkaConcurrency Model Actors Unified Management

Spray Under the Hood

Scala

Akka Actors & Futures

Akka I/O (used to be spray-io)

Thread Pool / Config / Logging by Akka

Main Modules

Server DSL (spray-routing)

Client DSL (spray-client)

Low-level HTTP Server/Client (spray-can)

HTTP Data Model (spray-http)

Test Kit (spray-testkit)

Spray Routing

Hello, World!val myRoute = path("hello"){ complete { "world" } }

object MyServiceActor extends App with SimpleRoutingApp with MyService { implicit val system = ActorSystem("MyService-System") startServer(interface = "localhost", port = 9090)(myRoute) }

Routestype Route = RequestContext => Unit

ctx => ctx.complete(“response”)

complete("response")

Route CompositionTransform request / response for inner nested routes

Filter requests for certain routes by

Method

Path

Params

Route Chaining - Try a different route

Directives

Context Transformations

Filtering

Value Extraction

Request Completion

Directives - Chainingval route = a { b { c { ... // route 1 } ~ d { ... // route 2 } ~ ... // route 3 } ~ e { ... // route 4 } }

Directives - Filteringval myRoute = path("hello") { get { complete { "world" } } } ~ path("hi") { post { complete("Hello World!") } }

Directives - Extraction

val myRoute = path("student" / IntNumber){ id => complete(s"student id received: $id") }

Directives - Composition

val getOrPost = get | post

val myRoute = (path("test") & getOrPost) { complete("This could have been a get or a post only") }

Directives - Composition

val teacher = "teacher" / IntNumberval course = "course" / IntNumberval teacherCourse = path(teacher / course)

val myRoute = teacherCourse { (teacherId, courseId) => complete(s"Got teacher $teacherId and course $courseId") }

Directives - Composition

val teacher = "teacher" / IntNumberval course = "course" / IntNumberval teacherCourse = path(teacher / course)

val myRoute = (teacherCourse & getOrPost){ (teacherId, courseId) => complete(s"Got teacher $teacherId and course $courseId") }

Directives - Compositionval teacher = "teacher" / IntNumberval course = "course" / IntNumberval teacherCourse = path(teacher / course)

val getOrPostTeacherCourse = teacherCourse & getOrPost

val myRoute = getOrPostTeacherCourse { (teacherId, courseId) => complete(s"Got teacher $teacherId and course $courseId") }

Directives - Type Safety

val teacher = "teacher" / IntNumber

(teacher | get){ teacherId => complete(s"Got teacher $teacherId") }

DOES NOT COMPILE!

Directives - Many Variations

path("hi") { post { parameter("param".as[Int]) { param => validate(param > 0, "Sorry, param has to be greater than zero"){ complete("Hello World!" * param) } } }}

Rejectionsval noSuchPageHandler = RejectionHandler { case Nil => complete(NotFound, "Sorry, it's not here!") }

val myRoute = getOrPostTeacherCourse { (teacherId, courseId) => handleRejections(noSuchPageHandler) { complete(s"Got teacher $teacherId and course $courseId") }}

Rejections - Selective

implicit val generalRejectionHandler = RejectionHandler { case MissingQueryParamRejection("id") :: _ => complete(BadRequest, "Looks like a bug to me!") }

Asynchronous Processingclass CalculatingActor extends Actor { override def receive = { case _ => sender ! 42 } } val calculator: ActorRef = actorRefFactory.actorOf(Props[CalculatingActor])

path("calc") { complete{ (calculator ? "calc"). mapTo[Int]. map(result => s"calculation result is $result") }}

Testing

TestKit

Support for specs2 and Scalatest

No need for the actual server

Does use an actor system

TestKit

REQUEST ~> ROUTE ~> check { ASSERTIONS}

TestKitclass RouteTest extends Specification with Specs2RouteTest

with MyService{ def actorRefFactory = system "My REST service" should { "Answer a hello message" in { Get("/hello") ~> myRoute ~> check { responseAs[String] === "world" } } } }

TestKitclass RouteTest extends Specification with Specs2RouteTest

with MyService{ def actorRefFactory = system "My REST service" should { "Reject a POST request" in { Post("/hello") ~> myRoute ~> check { rejection === MethodRejection(HttpMethods.GET) } } }}

TestKitclass RouteTest extends Specification with Specs2RouteTest

with MyService{ def actorRefFactory = system "My REST service" should { "Reject a POST request" in { Post("/hello") ~> sealRoute(myRoute) ~> check { status === MethodNotAllowed } } } }

Future[Spray]?

Akka-HTTP

Spray 2.0

Polishing of APIs

Java APIs

Based on Reactive Streams

Akka-HTTP

Better API for handling chunked messages

Client Overhaul

Websockets support (spray doesn’t support out of the box)

References

http://spray.io/documentation/

https://github.com/spray/spray/tree/master/examples

nimroda at wix dot com

@nimrodargov