=doc first parts of HTTP model documentation

This commit is contained in:
Johannes Rudolph 2014-06-16 23:09:51 +02:00
parent d7b4770657
commit c6993dd279
7 changed files with 491 additions and 0 deletions

View file

@ -0,0 +1,86 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http
import akka.stream.testkit.AkkaSpec
import akka.actor.ActorSystem
import akka.util.Timeout
import scala.concurrent.duration._
import akka.http.model._
class HttpServerExampleSpec
extends AkkaSpec("akka.actor.default-mailbox.mailbox-type = akka.dispatch.UnboundedMailbox") {
def ActorSystem(): ActorSystem = system
"binding example" in {
//#bind-example
import akka.pattern.ask
import akka.io.IO
import akka.http.Http
import akka.stream.scaladsl.Flow
import akka.stream.{ MaterializerSettings, FlowMaterializer }
implicit val system = ActorSystem()
import system.dispatcher
val materializer = FlowMaterializer(MaterializerSettings())
implicit val askTimeout: Timeout = 500.millis
val bindingFuture = IO(Http) ? Http.Bind(interface = "localhost", port = 8080)
bindingFuture foreach {
case Http.ServerBinding(localAddress, connectionStream)
Flow(connectionStream).foreach {
case Http.IncomingConnection(remoteAddress, requestProducer, responseConsumer)
println("Accepted new connection from " + remoteAddress)
// handle connection here
}.consume(materializer)
}
//#bind-example
}
"full-server-example" in {
import akka.pattern.ask
import akka.io.IO
import akka.http.Http
import akka.stream.scaladsl.Flow
import akka.stream.{ MaterializerSettings, FlowMaterializer }
implicit val system = ActorSystem()
import system.dispatcher
val materializer = FlowMaterializer(MaterializerSettings())
implicit val askTimeout: Timeout = 500.millis
val bindingFuture = IO(Http) ? Http.Bind(interface = "localhost", port = 8080)
//#full-server-example
import HttpMethods._
val requestHandler: HttpRequest HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _)
HttpResponse(
entity = HttpEntity(MediaTypes.`text/html`,
<html><body>Hello world!</body></html>.toString))
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!")
}
// ...
bindingFuture foreach {
case Http.ServerBinding(localAddress, connectionStream)
Flow(connectionStream).foreach {
case Http.IncomingConnection(remoteAddress, requestProducer, responseConsumer)
println("Accepted new connection from " + remoteAddress)
Flow(requestProducer).map(requestHandler).produceTo(materializer, responseConsumer)
}.consume(materializer)
}
//#full-server-example
}
}

View file

@ -0,0 +1,85 @@
package docs.http
//#import-model
import akka.http.model._
import org.scalatest.MustMatchers
//#import-model
import akka.stream.testkit.AkkaSpec
import akka.util.ByteString
import akka.http.model.headers.{ GenericHttpCredentials, BasicHttpCredentials }
class ModelSpec extends AkkaSpec {
"construct request" in {
//#construct-request
import HttpMethods._
// construct simple GET request to `homeUri`
val homeUri = Uri("/abc")
HttpRequest(GET, uri = homeUri)
// construct simple GET request to "/index" which is converted to Uri automatically
HttpRequest(GET, uri = "/index")
// construct simple POST request containing entity
val data = ByteString("abc")
HttpRequest(POST, uri = "/receive", entity = data)
// customize every detail of HTTP request
import HttpProtocols._
import MediaTypes._
val userData = ByteString("abc")
val authorization = headers.Authorization(BasicHttpCredentials("user", "pass"))
HttpRequest(
PUT,
uri = "/user",
entity = HttpEntity(`text/plain`, userData),
headers = List(authorization),
protocol = `HTTP/1.0`)
//#construct-request
}
"construct response" in {
//#construct-response
import StatusCodes._
// simple OK response without data created using the integer status code
HttpResponse(200)
// 404 response created using the named StatusCode constant
HttpResponse(NotFound)
// 404 response with a body explaining the error
HttpResponse(404, entity = "Unfortunately, the resource couldn't be found.")
// A redirecting response containing an extra header
val locationHeader = headers.Location("http://example.com/other")
HttpResponse(Found, headers = List(locationHeader))
//#construct-response
}
"deal with headers" in {
//#headers
import akka.http.model.headers._
// create a ``Location`` header
val loc = Location("http://example.com/other")
// create an ``Authorization`` header with HTTP Basic authentication data
val auth = Authorization(BasicHttpCredentials("joe", "josepp"))
// a method that extracts basic HTTP credentials from a request
case class User(name: String, pass: String)
def credentialsOfRequest(req: HttpRequest): Option[User] =
for {
Authorization(BasicHttpCredentials(user, pass)) <- req.header[headers.Authorization]
} yield User(user, pass)
//#headers
credentialsOfRequest(HttpRequest(headers = List(auth))) should be(Some(User("joe", "josepp")))
credentialsOfRequest(HttpRequest()) should be(None)
credentialsOfRequest(HttpRequest(headers = List(Authorization(GenericHttpCredentials("Other", Map.empty[String, String]))))) should be(None)
}
}

View file

@ -0,0 +1,153 @@
.. _http-core-server-scala:
HTTP Server
===========
The Akka HTTP server is an embedded, stream-based, fully asynchronous, low-overhead
HTTP/1.1 server implemented on top of `Akka Streams`_. (todo: fix link)
It sports the following features:
- Low per-connection overhead for supporting many thousand concurrent connections
- Efficient message parsing and processing logic for high throughput applications
- Full support for `HTTP persistent connections`_
- Full support for `HTTP pipelining`_
- Full support for asynchronous HTTP streaming (including "chunked" transfer encoding) accessible through an idiomatic
reactive streams API
- Optional SSL/TLS encryption
.. _HTTP persistent connections: http://en.wikipedia.org/wiki/HTTP_persistent_connection
.. _HTTP pipelining: http://en.wikipedia.org/wiki/HTTP_pipelining
.. _Akka streams: http://akka.io/docs/
Design Philosophy
-----------------
Akka HTTP server is scoped with a clear focus on the essential functionality of an HTTP/1.1 server:
- Connection management
- Message parsing and header separation
- Timeout management (for requests and connections)
- Response ordering (for transparent pipelining support)
All non-core features of typical HTTP servers (like request routing, file serving, compression, etc.) are left to
the next-higher layer in the application stack, they are not implemented by the HTTP server itself.
Apart from general focus this design keeps the server small and light-weight as well as easy to understand and
maintain.
Basic Architecture
------------------
Akka HTTP server is implemented on top of Akka streams and makes heavy use of them - in its
implementation and also on all levels of its API.
On the connection level Akka HTTP supports basically the same interface as Akka streams IO: A socket binding is
represented as a stream of incoming connections. Each connection itself is composed of an input stream of requests and
an output consumer of responses. The application has to provide the handler to "translate" requests into responses.
Streaming is also supported for single message entities itself. Particular kinds of ``HttpEntity``
subclasses provide support for fixed or streamed message entities.
Starting and Stopping
---------------------
An Akka HTTP server is started by sending an ``Http.Bind`` command to the `akka.http.Http`_ extension:
.. includecode:: code/docs/http/HttpServerExampleSpec.scala
:include: bind-example
With the ``Http.Bind`` command you specify the interface and port to bind to and register interest in handling incoming
HTTP connections. Additionally the ``Http.Bind`` command also allows you to define socket options as well as a larger number
of settings for configuring the server according to your needs.
The sender of the ``Http.Bind`` command (e.g. an actor you have written) will receive an ``Http.ServerBinding`` reply
after the HTTP layer has successfully started the server at the respective endpoint. In case the bind fails (e.g.
because the port is already busy) a ``Failure`` message is dispatched instead. As shown in the above example this works
well with the ask pattern and Future operations.
The ``Http.ServerBinding`` informs the binder of the actual local address of the bound socket and it contains a
stream of incoming connections of type ``Producer[Http.IncomingConnection]``. Connections are handled by subscribing
to the connection stream and handling the incoming connections.
The binding is released and the underlying listening socket is closed when all subscribers of the
``Http.ServerBinding.connectionStream`` have cancelled their subscriptions.
.. _akka.http.Http: @github@/akka-http-core/src/main/scala/akka/http/Http.scala
Request-Response Cycle
----------------------
When a new connection has been accepted it will be published by the ``Http.ServerBinding.connectionStream`` as an
``Http.IncomingConnection`` which consists of the remote address, a ``requestProducer``, and a ``responseConsumer``.
Handling requests in this model means connecting the ``requestProducer`` stream with an application-defined component that
maps requests to responses which then feeds into the ``responseConsumer``:
.. includecode:: code/docs/http/HttpServerExampleSpec.scala
:include: full-server-example
In this case, a request is handled by transforming the request stream with a function ``HttpRequest => HttpResponse``
using Akka stream's ``map`` operator. Depending on the use case, arbitrary other ways of connecting are conceivable using
Akka stream's operators (e.g using ``mapFuture`` to allow parallel processing of several requests when HTTP pipelining is
enabled).
It's the application's responsibility to feed responses into the ``responseConsumer`` in the same order as the respective
requests have come in. Also, each request must result in exactly one response. Using stream operators like ``map`` or
``mapFuture`` will automatically fulfill this requirement.
Streaming request/response entities
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Streaming of HTTP message entities is supported through subclasses of ``HttpEntity``. You need to be able to deal
with streamed entities when receiving a request as well as when constructing responses. See :ref:`HttpEntity` for
a description of the alternatives.
(todo): Link to :ref:`http-routing-scala` for (un-)marshalling facilities.
Closing a connection
~~~~~~~~~~~~~~~~~~~~
The HTTP connection will be closed when the ``responseConsumer`` gets completed or when the ``requestProducer``'s
subscription was cancelled and no more responses are pending.
You can also use the value of the ``Connection`` header of a response as described below to give a hint to the
implementation to close the connection after the completion of the response.
HTTP Headers
------------
When the Akka HTTP server receives an HTTP request it tries to parse all its headers into their respective
model classes. No matter whether this succeeds or not, the connection actor will always pass on all
received headers to the application. Unknown headers as well as ones with invalid syntax (according to the header
parser) will be made available as ``RawHeader`` instances. For the ones exhibiting parsing errors a warning message is
logged depending on the value of the ``illegal-header-warnings`` config setting.
Some common headers are treated specially in the model and in the implementation and should not occur in the ``headers``
field of an HTTP message:
- ``Content-Type``: Use the ``contentType`` field of the ``HttpEntity`` subclasses to set or determine the content-type
on an entity.
- ``Transfer-Encoding``: The ``Transfer-Encoding`` is represented by subclasses of ``HttpEntity``.
- ``Content-Length``: The ``Content-Length`` header is represented implicitly by the choice of an ``HttpEntity`` subclass:
A Strict entity determines the Content-Length by the length of the data provided. A Default entity has an explicit
``contentLength`` field which specifies the amount of data the streaming producer will produce. Chunked and CloseDelimited
entities don't need to define a length.
- ``Server``: The ``Server`` header is usually added automatically and it's value can be configured. An application can
decide to provide a custom ``Server`` header by including an explicit instance in the response.
- ``Date``: The ``Date`` header is added automatically and will be ignored if supplied manually.
- ``Connection``: When sending out responses the connection actor watches for a ``Connection`` header set by the
application and acts accordingly, i.e. you can force the connection actor to close the connection after having sent
the response by including a ``Connection("close")`` header. To unconditionally force a connection keep-alive you can
explicitly set a ``Connection("Keep-Alive")`` header. If you don't set an explicit ``Connection`` header the
connection actor will keep the connection alive if the client supports this (i.e. it either sent a
``Connection: Keep-Alive`` header or advertised HTTP/1.1 capabilities without sending a ``Connection: close`` header).
SSL Support
-----------
(todo)

View file

@ -0,0 +1,151 @@
.. _http-model-scala:
HTTP Model
==========
The akka HTTP model contains a mostly immutable, case-class based model of the major HTTP data structures,
like HTTP requests, responses and common headers. It also includes a parser for the latter, which is able to construct
the more structured header models from raw unstructured header name/value pairs.
Overview
--------
Since akka-http-core provides the central HTTP data structures you will find the following import in quite a
few places around the code base (and probably your own code as well):
.. includecode:: code/docs/http/ModelSpec.scala
:include: import-model
This brings in scope all of the relevant things that are defined here and that youll want to work with, mainly:
- ``HttpRequest`` and ``HttpResponse``, the central message model
- ``headers``, the package containing all the predefined HTTP header models and supporting types
- Supporting types like ``Uri``, ``HttpMethods``, ``MediaTypes``, ``StatusCodes``, etc.
A common pattern is that the model of a certain entity is represented by an immutable type (class or trait),
while the actual instances of the entity defined by the HTTP spec live in an accompanying object carrying the name of
the type plus a trailing plural 's'.
For example:
- Defined HttpMethod instances live in the HttpMethods object.
- Defined HttpCharset instances live in the HttpCharsets object.
- Defined HttpEncoding instances live in the HttpEncodings object.
- Defined HttpProtocol instances live in the HttpProtocols object.
- Defined MediaType instances live in the MediaTypes object.
- Defined StatusCode instances live in the StatusCodes object.
HttpRequest / HttpResponse
--------------------------
``HttpRequest`` and ``HttpResponse`` are the basic case classes representing HTTP messages.
An ``HttpRequest`` consists of
- method (GET, POST, etc.)
- URI
- protocol
- headers
- entity (body data)
Here are some examples how to construct an ``HttpRequest``:
.. includecode:: code/docs/http/ModelSpec.scala
:include: construct-request
All parameters of ``HttpRequest`` have default values set, so e.g. ``headers`` don't need to be specified
if there are none. Many of the parameters types (like ``HttpEntity`` and ``Uri``) define implicit conversions
for common use cases to simplify the creation of request and response instances.
An ``HttpResponse`` consists of
- status code
- protocol
- headers
- entity
Here are some examples how to construct an ``HttpResponse``:
.. includecode:: code/docs/http/ModelSpec.scala
:include: construct-response
Aside from the simple ``HttpEntity`` constructors to create an entity from a fixed ``ByteString`` shown here,
subclasses of ``HttpEntity`` allow data to be specified as a stream of data which is explained in the next section.
.. _HttpEntity:
HttpEntity
----------
An ``HttpEntity`` carries the content of a request together with its content-type which is needed to interpret the raw
byte data.
Akka HTTP provides several kinds of entities to support static and streaming data for the different kinds of ways
to transport streaming data with HTTP. There are four subtypes of HttpEntity:
HttpEntity.Strict
An entity which wraps a static ``ByteString``. It represents a standard, non-chunked HTTP message with ``Content-Length``
set.
HttpEntity.Default
A streaming entity which needs a predefined length and a ``Producer[ByteString]`` to produce the body data of
the message. It represents a standard, non-chunked HTTP message with ``Content-Length`` set. It is an error if the
provided ``Producer[ByteString]`` doesn't produce exactly as many bytes as specified. On the wire, a Strict entity
and a Default entity cannot be distinguished. However, they offer a valuable alternative in the API to distinguish
between strict and streamed data.
HttpEntity.Chunked
A streaming entity of unspecified length that uses `Chunked Transfer Coding`_ for transmitting data. Data is
represented by a ``Producer[ChunkStreamPart]``. A ``ChunkStreamPart`` is either a non-empty ``Chunk`` or a ``LastChunk``
containing optional trailer headers. The stream must consist of 0..n ``Chunked`` parts and can be terminated by an
optional ``LastChunk`` part (which carries optional trailer headers).
HttpEntity.CloseDelimited
A streaming entity of unspecified length that is delimited by closing the connection ("Connection: close"). Note,
that this entity type can only be used in an ``HttpResponse``.
Entity types ``Strict``, ``Default``, and ``Chunked`` are a subtype of ``HttpEntity.Regular`` which allows to use them for
requests and responses. In contrast, ``HttpEntity.CloseDelimited`` can only be used for responses.
Streaming entity types (i.e. all but ``Strict``) cannot be shared or serialized. To create a strict, sharable copy of an
entity or message use ``HttpEntity.toStrict`` or ``HttpMessage.toStrict`` which returns a Future of the object with the
body data collected into a ``ByteString``.
.. _Chunked Transfer Coding: http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-26#section-4.1
The ``HttpEntity`` companion object contains several helper constructors to create entities from common types easily.
You can pattern match over the subtypes of ``HttpEntity`` if you want to provide special handling for each of the
subtypes. However, in many cases a recipient of an `HttpEntity` doesn't care about of which subtype an entity is
(and how data is transported exactly on the HTTP layer). Therefore, a general ``HttpEntity.dataBytes`` is provided
which allows access to the data of an entity regardless of its concrete subtype.
.. note::
When to use which subtype?
- Use Strict if the amount of data is small and it is already in the heap (or even available as a ``ByteString``)
- Use Default if the data is generated by a streaming data source and the size of the data is fixed
- Use Chunked to support a data stream of unknown length
- Use CloseDelimited as an alternative to Chunked e.g. if chunked transfer encoding isn't supported by a peer.
Header model
------------
Akka HTTP contains a rich model of the common HTTP headers. Parsing and rendering is done automatically so that
applications don't need to care for the actual syntax of headers. Headers not modelled explicitly are represented
as a ``RawHeader``.
See these examples of how to deal with headers:
.. includecode:: code/docs/http/ModelSpec.scala
:include: headers
Parsing / Rendering
-------------------
Parsing and rendering of HTTP data structures is heavily optimized and for most types there's currently no public API
provided to parse (or render to) Strings or byte arrays.

View file

@ -0,0 +1,6 @@
.. _http-routing-scala:
HTTP Routing
============
(todo)

View file

@ -0,0 +1,9 @@
HTTP
====
.. toctree::
:maxdepth: 2
http-model
http-core-server
http-routing