=doc #19840 Introduction docs for HTTP

This commit is contained in:
Johan Andrén 2016-02-22 11:45:48 +01:00
parent 06b4d54960
commit 61ff9ba9b9
5 changed files with 382 additions and 67 deletions

View file

@ -4,7 +4,7 @@
package docs.http.scaladsl package docs.http.scaladsl
import akka.actor.{ ActorRef, ActorSystem } import akka.actor.{ Props, ActorRef, ActorSystem }
import akka.event.LoggingAdapter import akka.event.LoggingAdapter
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding import akka.http.scaladsl.Http.ServerBinding
@ -13,6 +13,7 @@ import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Flow, Sink } import akka.stream.scaladsl.{ Flow, Sink }
import akka.testkit.TestActors import akka.testkit.TestActors
import org.scalatest.{ Matchers, WordSpec } import org.scalatest.{ Matchers, WordSpec }
import scala.io.StdIn
import scala.language.postfixOps import scala.language.postfixOps
import scala.concurrent.{ ExecutionContext, Future } import scala.concurrent.{ ExecutionContext, Future }
@ -31,7 +32,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher implicit val executionContext = system.dispatcher
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] =
Http().bind(interface = "localhost", port = 8080) Http().bind(interface = "localhost", port = 8080)
@ -49,7 +50,8 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher // needed for the future onFailure in the end
implicit val executionContext = system.dispatcher
val handler = get { val handler = get {
complete("Hello world!") complete("Hello world!")
@ -60,7 +62,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
val bindingFuture: Future[ServerBinding] = val bindingFuture: Future[ServerBinding] =
Http().bindAndHandle(handler, host, port) Http().bindAndHandle(handler, host, port)
bindingFuture onFailure { bindingFuture.onFailure {
case ex: Exception => case ex: Exception =>
log.error(ex, "Failed to bind to {}:{}!", host, port) log.error(ex, "Failed to bind to {}:{}!", host, port)
} }
@ -74,7 +76,8 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
"binding-failure-handling" in compileOnlySpec { "binding-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher // needed for the future onFailure in the end
implicit val executionContext = system.dispatcher
// let's say the OS won't allow us to bind to 80. // let's say the OS won't allow us to bind to 80.
val (host, port) = ("localhost", 80) val (host, port) = ("localhost", 80)
@ -84,7 +87,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
.to(handleConnections) // Sink[Http.IncomingConnection, _] .to(handleConnections) // Sink[Http.IncomingConnection, _]
.run() .run()
bindingFuture onFailure { bindingFuture.onFailure {
case ex: Exception => case ex: Exception =>
log.error(ex, "Failed to bind to {}:{}!", host, port) log.error(ex, "Failed to bind to {}:{}!", host, port)
} }
@ -97,7 +100,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
"incoming-connections-source-failure-handling" in compileOnlySpec { "incoming-connections-source-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher implicit val executionContext = system.dispatcher
import Http._ import Http._
val (host, port) = ("localhost", 8080) val (host, port) = ("localhost", 8080)
@ -119,7 +122,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
"connection-stream-failure-handling" in compileOnlySpec { "connection-stream-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher implicit val executionContext = system.dispatcher
val (host, port) = ("localhost", 8080) val (host, port) = ("localhost", 8080)
val serverSource = Http().bind(host, port) val serverSource = Http().bind(host, port)
@ -153,6 +156,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
implicit val system = ActorSystem() implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer() implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val serverSource = Http().bind(interface = "localhost", port = 8080) val serverSource = Http().bind(interface = "localhost", port = 8080)
@ -186,87 +190,110 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._ import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer import akka.stream.ActorMaterializer
import scala.io.StdIn
implicit val system = ActorSystem() object WebServer {
implicit val materializer = ActorMaterializer()
val requestHandler: HttpRequest => HttpResponse = { def main(args: Array[String]) {
case HttpRequest(GET, Uri.Path("/"), _, _, _) => implicit val system = ActorSystem()
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, implicit val materializer = ActorMaterializer()
"<html><body>Hello world!</body></html>")) // needed for the future map/flatmap in the end
implicit val executionContext = system.dispatcher
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => val requestHandler: HttpRequest => HttpResponse = {
HttpResponse(entity = "PONG!") case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) => case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
sys.error("BOOM!") HttpResponse(entity = "PONG!")
case _: HttpRequest => case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
HttpResponse(404, entity = "Unknown resource!") sys.error("BOOM!")
case _: HttpRequest =>
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done
}
} }
Http().bindAndHandleSync(requestHandler, "localhost", 8080)
} }
// format: OFF // format: OFF
"high-level-server-example" in compileOnlySpec { "high-level-server-example" in compileOnlySpec {
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer import akka.stream.ActorMaterializer
import scala.io.StdIn
implicit val system = ActorSystem() object WebServer {
implicit val materializer = ActorMaterializer() def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route = val route =
get { get {
pathSingleSlash { pathSingleSlash {
complete { complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
<html> } ~
<body>Hello world!</body> path("ping") {
</html> complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
} }
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
// `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow` // `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow`
Http().bindAndHandle(route, "localhost", 8080) val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done
}
}
} }
"minimal-routing-example" in compileOnlySpec { "minimal-routing-example" in compileOnlySpec {
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer import akka.stream.ActorMaterializer
import scala.io.StdIn
object Main extends App { object WebServer {
implicit val system = ActorSystem("my-system") def main(args: Array[String]) {
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val route = implicit val system = ActorSystem("my-system")
path("hello") { implicit val materializer = ActorMaterializer()
get { // needed for the future flatMap/onComplete in the end
complete { implicit val executionContext = system.dispatcher
<h1>Say hello to akka-http</h1>
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
} }
} }
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine() // for the future transformations StdIn.readLine() // let it run until user presses return
bindingFuture bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port .flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done .onComplete(_ system.terminate()) // and shutdown when done
}
} }
} }
@ -380,4 +407,121 @@ class HttpServerExampleSpec extends WordSpec with Matchers {
} }
} }
} }
"stream random numbers" in compileOnlySpec {
//#stream-random-numbers
import akka.stream.scaladsl._
import akka.util.ByteString
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpEntity, ContentTypes}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.util.Random
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
// streams are re-usable so we can define it here
// and use it for every request
val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
val route =
path("random") {
get {
complete(
HttpEntity(
ContentTypes.`text/plain(UTF-8)`,
// transform each number to a chunk of bytes
numbers.map(n => ByteString(s"$n\n"))
)
)
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done
}
}
//#stream-random-numbers
}
object Auction {
def props: Props = ???
}
"interact with an actor" in compileOnlySpec {
//#actor-interaction
import scala.concurrent.duration._
import akka.util.Timeout
import akka.pattern.ask
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import scala.io.StdIn
object WebServer {
case class Bid(userId: String, bid: Int)
case object GetBids
case class Bids(bids: List[Bid])
// these are from spray-json
implicit val bidFormat = jsonFormat2(Bid)
implicit val bidsFormat = jsonFormat1(Bids)
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val auction = system.actorOf(Auction.props, "auction")
val route =
path("auction") {
put {
parameter("bid".as[Int], "user") { (bid, user) =>
// place a bid, fire-and-forget
auction ! Bid(user, bid)
complete(StatusCodes.Accepted, "bid placed")
}
}
get {
implicit val timeout: Timeout = 5.seconds
// query the actor for the current auction state
val bids: Future[Bids] = (auction ? GetBids).mapTo[Bids]
complete(bids)
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done
}
}
//#actor-interaction
}
} }

View file

@ -8,9 +8,13 @@ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives import akka.http.scaladsl.server.Directives
import org.scalatest.{ Matchers, WordSpec } import org.scalatest.{ Matchers, WordSpec }
import scala.concurrent.Future
class SprayJsonExampleSpec extends WordSpec with Matchers { class SprayJsonExampleSpec extends WordSpec with Matchers {
"spray-json example" in { def compileOnlySpec(body: => Unit) = ()
"spray-json example" in compileOnlySpec {
//#example //#example
import spray.json._ import spray.json._
@ -31,9 +35,7 @@ class SprayJsonExampleSpec extends WordSpec with Matchers {
val route = val route =
get { get {
pathSingleSlash { pathSingleSlash {
complete { complete(Item("thing", 42)) // will render as JSON
Item("thing", 42) // will render as JSON
}
} }
} ~ } ~
post { post {
@ -47,4 +49,63 @@ class SprayJsonExampleSpec extends WordSpec with Matchers {
//# //#
} }
} }
"second-spray-json-example" in compileOnlySpec {
//#second-spray-json-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.Done
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
object WebServer {
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// formats for unmarshalling and marshalling
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order)
// (fake) async database query api
def fetchItem(itemId: Long): Future[Option[Item]] = ???
def saveOrder(order: Order): Future[Done] = ???
def main(args: Array[String]) {
// needed to run the route
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
get {
pathPrefix("item" / LongNumber) { id =>
// there might be no item for a given id
val maybeItem: Future[Option[Item]] = fetchItem(id)
onSuccess(maybeItem) {
case Some(item) => complete(item)
case None => complete(StatusCodes.NotFound)
}
}
} ~
post {
path("create-order") {
entity(as[Order]) { order =>
val saved: Future[Done] = saveOrder(order)
onComplete(saved) { done =>
complete("order created")
}
}
}
}
}
}
//#second-spray-json-example
}
} }

View file

@ -1,3 +1,5 @@
.. _akka-http-spray-json:
JSON Support JSON Support
============ ============

View file

@ -1,3 +1,5 @@
.. _akka-http-xml-marshalling:
XML Support XML Support
=========== ===========

View file

@ -10,23 +10,129 @@ You get to pick the API level of abstraction that is most suitable for your appl
This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get
it done with a low-level API, which offers more flexibility but might require you to write more application code. it done with a low-level API, which offers more flexibility but might require you to write more application code.
Akka HTTP is structured into several modules: Using Akka HTTP
---------------
Akka HTTP is provided in separate jar files, to use it make sure to include the following dependencies::
akka-http-core "com.typesafe.akka" %% "akka-http-core" % "@version@" @crossString@
A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets) "com.typesafe.akka" %% "akka-http-experimental" % "@version@" @crossString@
Routing DSL for HTTP servers
----------------------------
The high-level, routing API of Akka HTTP provides a DSL to describe HTTP "routes" and how they should be handled.
Each route is composed of one or more level of ``Directive`` s that narrows down to handling one specific type of
request.
For example one route might start with matching the ``path`` of the request, only matching if it is "/hello", then
narrowing it down to only handle HTTP ``get`` requests and then ``complete`` those with a string literal, which
will be sent back as a HTTP OK with the string as response body.
Transforming request and response bodies between over-the-wire formats and objects to be used in your application is
done separately from the route declarations, in marshallers, which are pulled in implicitly using the "magnet" pattern.
This means that you can ``complete`` a request with any kind of object a as long as there is an implicit marshaller
available in scope.
Default marshallers are provided for simple objects like String or ByteString, and you can define your own for example
for JSON. An additional module provides JSON serialization using the spray-json library (see :ref:`akka-http-spray-json`
for details).
The ``Route`` created using the Route DSL is then "bound" to a port to start serving HTTP requests:
.. includecode2:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala
:snippet: minimal-routing-example
A common use case is to reply to a request using a model object having the marshaller transform it into JSON. In
this case shown by two separate routes. The first route queries an asynchronous database and marshalls the
``Future[Option[Item]]`` result into a JSON response. The second unmarshalls an ``Order`` from the incoming request
saves it to the database and replies with an OK when done.
.. includecode2:: ../code/docs/http/scaladsl/SprayJsonExampleSpec.scala
:snippet: second-spray-json-example
The logic for the marshalling and unmarshalling JSON in this example is provided by the "spray-json" library
(details on how to use that here: :ref:`akka-http-spray-json`).
One of the strengths of Akka HTTP is that streaming data is at its heart meaning that both request and response bodies
can be streamed through the server achieving constant memory usage even for very large requests or responses. Streaming
responses will be backpressured by the remote client so that the server will not push data faster than the client can
handle, streaming requests means that the server decides how fast the remote client can push the data of the request
body.
Example that streams random numbers as long as the client accepts them:
.. includecode:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala
:include: stream-random-numbers
Connecting to this service with a slow HTTP client would backpressure so that the next random number is produced on
demand with constant memory usage on the server. This can be seen using curl and limiting the rate
``curl --limit-rate 50b 127.0.0.1:8080/random``
Akka HTTP routes easily interacts with actors. In this example one route allows for placing bids in a fire-and-forget
style while the second route contains a request-response interaction with an actor. The resulting response is rendered
as json and returned when the response arrives from the actor.
.. includecode:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala
:include: actor-interaction
Again the logic for the marshalling and unmarshalling JSON in this example is provided by the "spray-json" library
(details on how to use that here: :ref:`akka-http-spray-json`)
Read more about the details of the high level APIs in the section :ref:`http-high-level-server-side-api`.
Low-level HTTP server APIs
--------------------------
The low-level Akka HTTP server APIs allows for handling connections or individual requests by accepting
``HttpRequest`` s and answering them by producing ``HttpResponse`` s. This is provided by the ``akka-http-core`` module.
APIs for handling such request-responses as function calls and as a ``Flow[HttpRequest, HttpResponse, _]`` are available.
.. includecode2:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala
:snippet: low-level-server-example
Read more details about the low level APIs in the section :ref:`http-low-level-server-side-api`.
HTTP client API
---------------
The client APIs provide methods for calling a HTTP server using the same ``HttpRequest`` and ``HttpResponse`` abstractions
that Akka HTTP server uses but adds the concept of connection pools to allow multiple requests to the same server to be
handled more performantly by re-using TCP connections to the server.
Example simple request:
.. includecode:: ../code/docs/http/scaladsl/HttpClientExampleSpec.scala
:include: single-request-example
Read more about the details of the client APIs in the section :ref:`http-client-side`.
The modules that make up Akka HTTP
----------------------------------
Akka HTTP is structured into several modules:
akka-http akka-http
Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL
for defining HTTP-based APIs on the server-side for defining HTTP-based APIs on the server-side, this is the recommended way to write HTTP servers
with Akka HTTP. Details can be found in the section :ref:`http-high-level-server-side-api`
akka-http-core
A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets)
Details can be found in sections :ref:`http-low-level-server-side-api` and :ref:`http-client-side`
akka-http-testkit akka-http-testkit
A test harness and set of utilities for verifying server-side service implementations A test harness and set of utilities for verifying server-side service implementations
akka-http-spray-json akka-http-spray-json
Predefined glue-code for (de)serializing custom types from/to JSON with spray-json_ Predefined glue-code for (de)serializing custom types from/to JSON with spray-json_
Details can be found here: :ref:`akka-http-spray-json`
akka-http-xml akka-http-xml
Predefined glue-code for (de)serializing custom types from/to XML with scala-xml_ Predefined glue-code for (de)serializing custom types from/to XML with scala-xml_
Details can be found here: :ref:`akka-http-xml-marshalling`
.. _spray-json: https://github.com/spray/spray-json .. _spray-json: https://github.com/spray/spray-json
.. _scala-xml: https://github.com/scala/scala-xml .. _scala-xml: https://github.com/scala/scala-xml