A study project how akka-http works. The code below is a bit compacted, so please use it for reference only how the (new) API must be used. It will not compile/work correctly when you just copy/paste it. Check out the working source code for correct usage.
To use akka-http we need the following dependencies:
reactive-streams
:- Provides the new abstraction for async and non-blocking pipeline processing,
- It has automatic support for back-pressure,
- Standardized API it's called: reactive-streams.org and as of May 2015 is v1.0,
akka-stream-experimental
:- Provided a standard API and DSL for creating composable stream transformations based upon the reactive-streams.org standard.
akka-http-core-experimental
:- Sits on top of
akka-io
, - Performs TCP <-> HTTP translation,
- Cleanly separated layer of stream transformations provided by Akka Extension,
- Implements HTTP 'essentials', no higher-level features (like file serving)
- Sits on top of
akka-http-scala-experimental
:- Provides higher-level server- and client-side APIs
- 'Unmarshalling' custom types from HttpEntities,
- 'Marshalling' custom types to HttpEntities
- (De)compression (GZip / Deflate),
- Routing DSL
akka-http-spray-json-experimental:
:- Provides spray-json support
Akka-Http has a client API and as such RPC's can be created. Take a look at the package com.github.dnvriend.webservices
, I have created
some example RPC style web service clients for eetnu
, iens
, postcode
, openWeatherApi
, based on the generic com.github.dnvriend.webservices.generic.HttpClient
client that supports Http and SSL connections with basic authentication or one legged OAuth1 with consumerKey and consumerSecret configuration
from application.conf
. The RPC clients also support for single RPC without cached connections and the streaming cached connection style where
you can stream data to your clients. For usage please see the tests for the RPC clients. Good stuff :)
A new HTTP server can be launched using the Http()
class. The bindAndHandle()
method is a convenience method which starts
a new HTTP server at the given endpoint and uses the given 'handler' Flow
for processing all incoming connections.
Note that there is no backpressure being applied to the 'connections' Source
, i.e. all connections are being accepted
at maximum rate, which, depending on the applications, might present a DoS risk!
import akka.http.scaladsl._
import akka.http.scaladsl.model._
import akka.stream.scaladsl._
def routes: Flow[HttpRequest, HttpResponse, Unit]
Http().bindAndHandle(routes, "0.0.0.0", 8080)
First some Akka Stream parley:
Stream
: a continually moving flow of elements,Element
: the processing unit of streams,Processing stage
: building blocks that build up aFlow
orFlowGraph
for examplemap()
,filter()
,transform()
,junction()
etc,Source
: a processing stage with exactly one output, emitting data elements when downstream processing stages are ready to receive them,Sink
: a processing stage with exactly one input, requesting and accepting data elementsFlow
: a processing stage with exactly one input and output, which connects its up- and downstream by moving/transforming the data elements flowing through it,Runnable flow
: A flow that has both ends attached to aSource
andSink
respectively,Materialized flow
: An instantiation / incarnation / materialization of the abstract processing-flow template.
The abstractions above (Flow, Source, Sink, Processing stage) are used to create a processing-stream template
or blueprint
. When the template has a Source
connected to a Sink
with optionally some processing stages
between them, such a template
is called a Runnable Flow
.
The materializer for akka-stream
is the ActorMaterializer
which takes the list of transformations comprising a akka.stream.scaladsl.Flow
and materializes them in the form of org.reactivestreams.Processor
instances, in which every stage is converted into one actor.
In akka-http parley, a 'Route' is a Flow[HttpRequest, HttpResponse, Unit]
so it is a processing stage that transforms
HttpRequest
elements to HttpResponse
elements.
The following reactive-streams
are defined in akka-http
:
- Requests on one HTTP connection,
- Responses on one HTTP connection,
- Chunks of a chunked message,
- Bytes of a message entity.
Akka http uses the route directives we know (and love) from Spray:
import akka.http.scaladsl._
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.scaladsl._
def routes: Flow[HttpRequest, HttpResponse, Unit] =
logRequestResult("akka-http-test") {
path("") {
redirect("person", StatusCodes.PermanentRedirect)
} ~
pathPrefix("person") {
complete {
Person("John Doe", 25)
}
} ~
pathPrefix("ping") {
complete {
Ping(TimeUtil.timestamp)
}
}
}
I'm glad to see that akka-http-spray-json-experimental
basically has the same API as spray:
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model.RequestEntity
import akka.http.scaladsl.unmarshalling.Unmarshal
import spray.json.DefaultJsonProtocol._
import spray.json._
case class Person(name: String, age: Int)
val personJson = """{"name":"John Doe","age":25}"""
implicit val personJsonFormat = jsonFormat2(Person)
Person("John Doe", 25).toJson.compactPrint shouldBe personJson
personJson.parseJson.convertTo[Person] shouldBe Person("John Doe", 25)
val person = Person("John Doe", 25)
val entity = Marshal(person).to[RequestEntity].futureValue
Unmarshal(entity).to[Person].futureValue shouldBe person
Akka http has a cleaner API for custom types compared to Spray's. Out of the box it has support to marshal to/from basic types (Byte/String/NodeSeq) and so we can marshal/unmarshal from/to case classes from any line format. The API uses the Marshal object to do the marshalling and the Unmarshal object to to the unmarshal process. Both interfaces return Futures that contain the outcome.
The Unmarshal
class uses an Unmarshaller
that defines how an encoding like eg XML
can be converted from eg. a NodeSeq
to a custom type, like eg. a Person
.
To Marshal
class uses Marshallers
to do the heavy lifting. There are three kinds of marshallers, they all do the same, but one is not interested in the MediaType
, the opaque
marshaller, then there is the withOpenCharset
marshaller, that is only interested in the mediatype, and forwards the received HttpCharset
to the marshal function
so that the responsibility for handling the character encoding is up to the developer,
and the last one, the withFixedCharset
will handle only HttpCharsets that match the marshaller configured one.
An example XML marshaller/unmarshaller:
import akka.http.scaladsl.marshalling.{ Marshal, Marshaller, Marshalling }
import akka.http.scaladsl.model.HttpCharset
import akka.http.scaladsl.model.HttpCharsets._
import akka.http.scaladsl.model.MediaTypes._
import akka.http.scaladsl.unmarshalling.{ Unmarshal, Unmarshaller }
import scala.xml.NodeSeq
case class Person(name: String, age: Int)
val personXml =
<person>
<name>John Doe</name>
<age>25</age>
</person>
implicit val personUnmarshaller = Unmarshaller.strict[NodeSeq, Person] { xml ⇒
Person((xml \\ "name").text, (xml \\ "age").text.toInt)
}
val opaquePersonMarshalling = Marshalling.Opaque(() ⇒ personXml)
val openCharsetPersonMarshalling = Marshalling.WithOpenCharset(`text/xml`, (charset: HttpCharset) ⇒ personXml)
val fixedCharsetPersonMarshalling = Marshalling.WithFixedCharset(`text/xml`, `UTF-8`, () ⇒ personXml)
val opaquePersonMarshaller = Marshaller.opaque[Person, NodeSeq] { person ⇒ personXml }
val withFixedCharsetPersonMarshaller = Marshaller.withFixedCharset[Person, NodeSeq](`text/xml`, `UTF-8`) { person ⇒ personXml }
val withOpenCharsetCharsetPersonMarshaller = Marshaller.withOpenCharset[Person, NodeSeq](`text/xml`) { (person, charset) ⇒ personXml }
implicit val personMarshaller = Marshaller.oneOf[Person, NodeSeq](opaquePersonMarshaller, withFixedCharsetPersonMarshaller, withOpenCharsetCharsetPersonMarshaller)
"personXml" should "be unmarshalled" in {
Unmarshal(personXml).to[Person].futureValue shouldBe Person("John Doe", 25)
}
"Person" should "be marshalled" in {
Marshal(Person("John Doe", 25)).to[NodeSeq].futureValue shouldBe personXml
}
Versioning an API can be tricky. The key is choosing a strategy on how to do versioning. I have found and tried the following stragegies as blogged by Jim Lidell's blog, which is great by the way!
- 'The URL is king' in which the URL is encoded in the URL eg.
http://localhost:8080/api/v1/person
. The downside of this strategy is that the location of a resource may not change, and when we request another representation, the url does change eg. tohttp://localhost:8080/api/v2/person
. - Using a version request parameter like:
http://localhost:8080/api/person?version=1
. The downside of this stragegy lies in the fact that resource urls must be as lean as possible, and the only exception is for filtering, sorting, searching and paging, as stated by Vinay Sahni in his great blog 'Best Practices for Designing a Pragmatic RESTful API'. - Both bloggers and I agree that using request headers for versioning, and therefor relying on vendor specific media types is a great way to keep the resource urls clean, the location does not change and in code the versioning is only a presentation responsibility, easilly resolved by an in scope mashaller.
When you run the example, you can try the following requests:
# The latest version in JSON
curl -H "Accept: application/json" localhost:8080/person
# The latest version in XML
curl -H "Accept: application/xml" localhost:8080/person
# Vendor specific header for JSON v1
curl -H "Accept: application/vnd.acme.v1+json" localhost:8080/person
# Vendor specific header for JSON v2
curl -H "Accept: application/vnd.acme.v2+json" localhost:8080/person
# Vendor specific header for XML v1
curl -H "Accept: application/vnd.acme.v1+xml" localhost:8080/person
# Vendor specific header for XML v2
curl -H "Accept: application/vnd.acme.v2+xml" localhost:8080/person
Please take a look at the Marshallers
trait for an example how you could implement this strategy and the MarshallersTest
how to test the routes using the Accept
header and leveraging the media types.
- Parleys - Dr. Roland Kuhn - Akka HTTP - The Reactive Web Toolkit
- Parleys - Mathias Doenitz - Akka HTTP - Unrest your actors
- Youtube - Mathias Doenitz - Akka HTTP — The What, Why and How
- Youtube - Mathias Doenitz - Spray & Akka HTTP
- Youtube - Mathias Doenitz - Spray on Akka
- Parleys - Dr. Roland Kuhn - Go Reactive: Blueprint for Future Applications
- Parleys - Dr. Roland Kuhn - Distributed in space and time
- Parleys - Mirco Dotta - Akka Streams