Building scalable rest service using Akka HTTP

46
Introduction to Akka HTTP Building scalable rest service in Scala https://github.com/shashankgowdal/introduction-to-akkahttp

Transcript of Building scalable rest service using Akka HTTP

Introduction to Akka HTTPBuilding scalable rest service in Scala

https://github.com/shashankgowdal/introduction-to-akkahttp

● Shashank L

● Senior Software engineer at Tellius

● Part time big data consultant and trainer at datamantra.io

● www.shashankgowda.com

Agenda● Motivation● Akka, Reactive streams● Akka Http● Akka Http High level API● Testing● Additional functionality● Working with files● Websockets● Akka Http with Spark

Scala frameworks to build REST API● Play/Lift

○ Full stack web framework● Scalatra

○ Lightweight but requires a application container for deployment

● Spray○ Lightweight and robust as it's asynchronous and built on top

of Actors● Akka HTTP

○ Next version of Spray framework

Why Akka HTTP● Its 2.0 version of Spray.io

● Internals of Spray have been rewritten to use

Reactive streams

● Reactive streams idea gel well with earlier ideas of

Spray

● Multiple level API

Akka● Toolkit and runtime for building highly concurrent,

distributed, and fault tolerant applications on the JVM

● Uses message passing based concurrency model

● Written in Scala

● Scale up or out

● Program at higher level

● Distributable by design

● Message passing is built on top of AkkaActor model

Akka actor● Receives message and takes action to handle them● Consists of

○ Behaviour○ State○ Mailbox

Reactive streams● Asynchronous● Stream processing● Back-pressured● Interoperability● Few Implementations

○ Akka Streams○ RxJava○ Play○ Kafka

● Spark streaming with Kafka receiver

Akka streams

● Akka’s implementation of Reactive streams

● DSL for building a complete stream

● Higher level abstraction over the actor model

● Stream inputs and outputs are typesafe

● Internally uses Actor to implement Reactive stream

properties

Source, Flow and Sink

Source, Flow and Sink

com.shashank.akkahttp.IntroductionToStream

implicit val sys = ActorSystem("IntroductionToStream")implicit val mat:Materializer = ActorMaterializer()

val source = Source(List(1, 2, 3))

val flow = Flow[Int].map(_.toString)

val sink = Sink.foreach(println)

val runnableGraph = source via flow to sinkrunnableGraph.run()

HTTP server with Akka streams

HTTPServer as a:Flow[HttpRequest, HttpResponse]

Socket input

Bytes ⇒ HttpRequest

HttpRequest ⇒ HttpResponse

HttpResponse ⇒ Bytes

Socket Sink

Akka Http server using Flow

implicit val sys = ActorSystem("IntroductionToAkkaHttp")implicit val mat:Materializer = ActorMaterializer()

val requestResponseFlow = Flow.fromFunction[HttpRequest, HttpResponse]( request => {println(request.toString)HttpResponse(StatusCodes.OK, entity = "Hello!")

})

Http().bindAndHandle(requestResponseFlow, "localhost", 8080)

com.shashank.akkahttp.basic.serving.StreamsServing

Akka HTTP

● Part of Akka project

● HTTP toolkit (Client and Server)

● On top of akka-actor and akka-streams

● Multiple levels of abstraction - Open API

● Scalable, Max throughput, acceptable latency

● APIs in both Scala and Java

Akka HTTP - Implementation

● Fully asynchronous and nonblocking

● Focused on higher level API

● Lightweight

● Modular

● Testable

● Based on spray.io

● Better streaming REST functionality support

Akka HTTP stack

Akka HTTP structure

Akka HTTP module

akka-http

akka-http-core

akka-http-testkit

akka-http-spray-json

akka-http-xml

Akka HTTP - Parts● akka-http-core

○ low-level server & client side HTTP implementation

● akka-http○ high-level API : DSL, (un)marshalling, (de)compression

● akka-http-testkit○ Utilities for testing server-side implementation

● akka-http-spray-json○ (de)serialization from/to JSON with spray-json

● akka-http-xml○ (de)serialization from/to XML with scala-xml

HTTP model● Fully immutable, case-class based

● Abstraction for most HTTP things (Types!)

● Little logic inside

● Lots of predefined types - media type, status code, encodings etc

● Efficient parsing and rendering

● Clients/Connected services should obey the standard

HTTP model - Examplescase class HttpRequest(method: HttpMethod = HttpMethods.GET, uri: Uri = Uri./, headers: immutable.Seq[HttpHeader] = Nil, entity: RequestEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`)

case class HttpResponse(status: StatusCode = StatusCodes.OK, headers: immutable.Seq[HttpHeader] = Nil, entity: ResponseEntity = HttpEntity.Empty, protocol: HttpProtocol = HttpProtocols.`HTTP/1.1`)

case class Uri(scheme: String, authority: Authority, path: Path, rawQueryString: Option[String], fragment: Option[String])

HTTP model - Examplesobject ContentTypes {

val `application/json` = ContentType(MediaTypes.`application/json`)val `application/octet-stream` = ContentType(MediaTypes.`application/octet-stream`)

val `text/plain(UTF-8)` = MediaTypes.`text/plain` withCharset HttpCharsets.`UTF-8` val `text/html(UTF-8)` = MediaTypes.`text/html` withCharset HttpCharsets.`UTF-8` val `text/xml(UTF-8)` = MediaTypes.`text/xml` withCharset HttpCharsets.`UTF-8`

val `text/csv(UTF-8)` = MediaTypes.`text/csv` withCharset HttpCharsets.`UTF-8` val NoContentType = ContentType(MediaTypes.NoMediaType)}

val `US-ASCII` = register("US-ASCII")("iso-ir-6", "ANSI_X3.4-1968", "ANSI_X3.4-1986", "ISO_646.irv:1991", "ASCII", "ISO646-US", "us", "IBM367", "cp367", "csASCII")val `ISO-8859-1` = register("ISO-8859-1")("iso-ir-100", "ISO_8859-1", "latin1", "l1", "IBM819", "CP819", "csISOLatin1")val `UTF-8` = register("UTF-8")("UTF8")val `UTF-16` = register("UTF-16")("UTF16")val `UTF-16BE` = register("UTF-16BE")()val `UTF-16LE` = register("UTF-16LE")()

HTTP server functional way● Low level HTTP API● Provided by akka-http-core-moduleimplicit val sys = ActorSystem("IntroductionToAkkaHttp")implicit val mat:Materializer = ActorMaterializer()

val handler :(HttpRequest => HttpResponse) = {case HttpRequest(HttpMethods.GET, Uri.Path("/ping"), _, _, _) =>

HttpResponse(StatusCodes.OK, entity = "pong!") case r => HttpResponse(status = StatusCodes.BadRequest)}Http().bindAndHandleSync(handler, "localhost", 8080)

com.shashank.akkahttp.basic.serving.FunctionalServing

Akka Http high level API

HTTP Server Nice way!

● Low level API becomes uneasy to handle when we need large number of routes

● For this we should use higher level API by akka-http● Route using Routing DSLimplicit val sys = ActorSystem("IntroductionToAkkaHttp")implicit val mat:Materializer = ActorMaterializer()

val routes: Route = ???Http(sys).bindAndHandle(route, "localhost", 8090)

Routing DSL

● Internal domain specific language for routing● How most services are actually written● Layer to the application● Type safe but flexible● Not just routing - behaviour definition● Very composable● Fun, powerful and looks clean

Routing DSL - Exampleval route =path("welcome"){ get{ complete { "welcome to rest service" } }} ~path("demo"){ get{ complete { "welcome to demonstration" } }}Http().bindAndHandle(route, "localhost", 8090)

com.shashank.akkahttp.basic.routing.RoutingDSL

Directives● A Directive is a small building block used for creating

routes.● There are some predefined directives( get, post,

complete etc.)● We can also define our custom directives.● Roles: extracting, transforming request or response,

filtering, side-effecting● ~136 predefined directives

Rejections

● Produced by directives● Travel down the routing structure● EmptyRejection is thrown when no route completes● Can be extended● Can be cancelled● Handling rejection = transforming it to response● Ex. MethodRejection, AuthorizationFailedRejection,MissingCookieRejection, MissingQueryParamRejection

Rejections● ~ operator connects two routes in a way that allows a

second route to get a go at a request if the first route "rejected" it.

path("order") { get { complete("Received GET") } ~ post { complete("Received POST") }}

com.shashank.akkahttp.basic.routing.Rejection

Failures

● Are triggered by exceptions● Travel up the routing structure● Can be handled by handleExceptions directive or

top-level ExceptionHandler● Can be used to simplify the flow for validation

purpose

com.shashank.akkahttp.basic.routing.Failure

Testing Akka Http

● Simple and straightforward● Allows to assert responses returned for given

requests● Integrates well with ScalatestGet("/ping") ~> route ~> check{ status === OK entity.as[String] === "It Works!"}

com.shashank.akkahttp.basic.routing.TestKit

Testkit

Additional Rest functionalities inAkka Http

Query parameters

def parameters(param: <ParamDef[T]>): Directive1[T]

● Mandatory parameters● Optional parameters● Parameters with required value● Deserialized parameter● Repeated parameter● Deserialized parameter into Case class

(Un)Marshalling

● Marshalling - high level → low (wire) level

● Unmarshalling - low (wire) level → high level

● Many predefined (un) marshallers are available

● Extensible

● JSON and XML supported

● Type-class approach - Implicit resolution

Custom Http entity data

● Akka Http JSON support● Support conversion to and from JVM objects to wire

representation like JSON, XML etc● SprayJsonSupport provides a

FromEntityUnmarshaller[T] and ToEntityMarshaller[T] for every type T with Spray Json Reader/Writer

com.shashank.akkahttp.basic.routing.CustomEntityWithJson

Custom directive

● Configuration labelling● Transforming existing directives● Directive0, Directive1● Authorization● Authentication

com.shashank.akkahttp.basic.routing.CustomDirective

Working with Files

File uploaddef uploadedFile(fieldName: String): Directive1[(FileInfo, File)]

● Streams the contents of a file uploaded as a multipart form into a temporary file on disk

● Cannot start processing the file unless it written completely to temporary file

com.shashank.akkahttp.basic.routing.FileUpload

File upload streamdef fileUpload(fieldName: String): Directive1[(FileInfo, Source[ByteString, Any])]

● Simple access to the stream of bytes for a file uploaded as a multipart form together with metadata about the upload as extracted value.

com.shashank.akkahttp.basic.routing.FileUploadStream

Websockets

Websocket● WebSocket is a protocol that provides a bi-directional channel

between browser and webserver● Data is exchanged in messages whereby a message can either

be binary data or unicode text

Server side websocket in AkkaHttp● Akka HTTP provides a stream-based implementation of the

WebSocket protocol● basic unit of data exchange in the WebSocket is a message i.e

TextMessage or BinaryMessage● Websocket handshake is managed and hidden from application

layer● A message handler is expected to be implemented as a

Flow[Message, Message, Any]● Testing Websocket using WSProbe

com.shashank.akkahttp.basic.routing.Websocket

Akka HTTP with Spark

Spark Cluster

Akka Http Rest

serverClient

HDFS

Pros and Cons

● Pros○ Backed by Lightbend(Typesafe)○ Integration layers○ Microservices○ Pure REST APIs

● Cons○ Full fledged web applications (server side template generation)

○ Not mature enough○ Easy to start, hard to master