Akka Http

a.k.a. Spray 2.0

Présenté par Jean Detoeuf / @thebignet

Jean Detoeuf

Développeur

Passionné de nouvelles technologies

#jvm #docker #craftmanship #rpi #diy

Avertissement

Je ne suis pas un expert en Scala

Soyez sympas ^^

Projet en cours de maturation

version 2.0.1 lors de cette présentation

mélange entre Spray et Akka

J'ai eu l'idée de cette présentation à la version 1.0, il était temps !

Akka Streams

Présenté au SLUG par Frédéric Masion en février 2015

Source ~> Flow ~> Sink

Source décrit une source de données

Flow représente une transformation de ces données

Sink une opération terminale

Akka Streams

Source ~> Flow1 ~> Flow2a ~> Sink ~> Flow2b

Fan-out / Fan-in

Akka Http

Constuit sur Akka Streams

Internet est un tuyau

rempli de chatons

Akka Http

Flow[HttpRequest, HttpResponse]

Akka Http

Client et serveur

Mettre des acteurs sur HTTP

Modules

C'est bon, montre-moi le code

Les exemples sont pour la plupart tirés de la documentation d'Akka

Exemple simple

import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
object Main extends App {
  implicit val system = ActorSystem("my-system")
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher
 
  val route =
    path("hello") {
      get {
        complete {
          <h1>Say hello to akka-http</h1>
        }
      }
    }
 
  val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
 
  println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
  Console.readLine() // for the future transformations
  bindingFuture
    .flatMap(_.unbind()) // trigger unbinding from the port
    .onComplete(_ ⇒ system.shutdown()) // and shutdown when done
}

Exemple simple

import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
object Main extends App {
  implicit val system = ActorSystem("my-system")
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher
 
  val route =
    path("hello") {
      get {
        complete {
          <h1>Say hello to akka-http</h1>
        }
      }
    }
 
  val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
 
  println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
  Console.readLine() // for the future transformations
  bindingFuture
    .flatMap(_.unbind()) // trigger unbinding from the port
    .onComplete(_ ⇒ system.shutdown()) // and shutdown when done
}

Exemple simple

import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
object Main extends App {
  implicit val system = ActorSystem("my-system")
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher
 
  val route =
    path("hello") {
      get {
        complete {
          <h1>Say hello to akka-http</h1>
        }
      }
    }
 
  val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
 
  println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
  Console.readLine() // for the future transformations
  bindingFuture
    .flatMap(_.unbind()) // trigger unbinding from the port
    .onComplete(_ ⇒ system.shutdown()) // and shutdown when done
}

Exemple simple

import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
object Main extends App {
  implicit val system = ActorSystem("my-system")
  implicit val materializer = ActorMaterializer()
  implicit val ec = system.dispatcher
 
  val route =
    path("hello") {
      get {
        complete {
          <h1>Say hello to akka-http</h1>
        }
      }
    }
 
  val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
 
  println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
  Console.readLine() // for the future transformations
  bindingFuture
    .flatMap(_.unbind()) // trigger unbinding from the port
    .onComplete(_ ⇒ system.shutdown()) // and shutdown when done
}

Marshalling/Unmarshalling

Marshalling/Unmarshalling

Comment passer d'un flux HTTP à Scala et inversement ?

Marshalling

Marshallers prédéfinis

Array[Byte]
ByteString
Array[Char]
String
akka.http.scaladsl.model.FormData
akka.http.scaladsl.model.MessageEntity
T <: akka.http.scaladsl.model.Multipart
T si ToEntityMarshaller[T] est présent

Marshalling

Résolution implicite

Si le type ToEntityMarshaller[T] est défini, il est utilisé

Unmarshalling

Unmarshallers prédéfinis

Byte
Short
Int
Long
Float
Double
Boolean
Array[Byte]
ByteString
Array[Char]
String
akka.http.scaladsl.model.FormData

Routage

Low-level

import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
 
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
 
val requestHandler: HttpRequest => HttpResponse = {
  case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
    HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
      "Hello world!"))
 
  case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
    HttpResponse(entity = "PONG!")
 
  case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
    sys.error("BOOM!")
 
  case _: HttpRequest =>
    HttpResponse(404, entity = "Unknown resource!")
}
 
Http().bindAndHandleSync(requestHandler, "localhost", 8080)

High-level

import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
 
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
 
val route =
  get {
    pathSingleSlash {
      complete {
        <html>
          <body>Hello world!</body>
        </html>
      }
    } ~
    path("ping") {
      complete("PONG!")
    } ~
    path("crash") {
      sys.error("BOOM!")
    }
  }
 
// `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow`
Http().bindAndHandle(route, "localhost", 8080)

Directives

Directives

Intercepte la requête

Filtrage de la requête

Transformation de la requête ou réponse

Extraire des informations

Exemple

val route =
path("order" / IntNumber) { id =>
  (get | put) {
    extractMethod { m =>
      complete(s"Received ${m.name} request for order $id")
    }
  }
}

Exemple 2

val orderGetOrPutWithMethod =
path("order" / IntNumber) & (get | put) & extractMethod
val route = orderGetOrPutWithMethod { (id, m) =>
  complete(s"Received ${m.name} request for order $id")
}

PathMatcher

val matcher: PathMatcher1[Option[Int]] =
 "foo" / "bar" / "X" ~ IntNumber.? / ("edit" | "create")

Intercepte les chemins suivants :

foo/bar/X42/edit
et
foo/bar/X/create

Extraction case class

case class Color(red: Int, green: Int, blue: Int)
val route = path("color") {
  parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]).as(Color) {
    color =>
    // utiliser color
  }
}

Validation case class

case class Color(name: String, red: Int, green: Int, blue: Int) {
  require(!name.isEmpty, "color name must not be empty")
  require(0 <= red && red <= 255, "red color component must be between 0 and 255")
  require(0 <= green && green <= 255, "green color component must be between 0 and 255")
  require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}

ValidationRejection si require ne passe pas

Par défaut : 400 Bad Request

Tests

Pour la route suivante

val smallRoute =
get {
  pathSingleSlash {
    complete {
      "Hello World !"
    }
  } ~
  path("ping") {
    complete("PONG !")
  }
}

Test

"Hello World ! non renvoyé sur /" in {
  Get() ~> smallRoute ~> check {
    status === StatusCodes.OK
    responseAs[String] shouldEqual "Hello World !"
  }
}

Pour la même route ...

val smallRoute =
get {
  pathSingleSlash {
    complete {
      "Hello World !"
    }
  } ~
  path("ping") {
    complete("PONG !")
  }
}

Test

"GET sur chemin inconnu non pris en compte" in {
  Get("/pouet") ~> smallRoute ~> check {
    handled shouldBe false
  }
}

Variables utilisables dans les tests

entityAs

handled

header

response

status

entre autres ...

Client

val responseFuture: Future[HttpResponse] =
  Http().singleRequest(HttpRequest(uri = "http://akka.io"))

Questions ?

Merci pour votre écoute