Git Product home page Git Product logo

akka-http-test's Introduction

akka-http-test

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.

License

Dependencies

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)
  • 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

Web Service Clients (RPC)

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 :)

Web Server

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)

Routes

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 a Flow or FlowGraph for example map(), 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 elements
  • Flow: 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 a Source and Sink 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.

Streams everywhere

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.

Route Directives

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)
      }
    }
  }

Spray-Json

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

Custom Marshalling/Unmarshalling

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
}

Vendor specific media types

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!

  1. '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. to http://localhost:8080/api/v2/person.
  2. 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'.
  3. 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.

Video

Slides

Northeast Scala Symposium 2015

Projects that use akka-http

akka-http-test's People

Contributors

dnvriend avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.