From c6993dd279b50d36d25381e956efe756e3c084b8 Mon Sep 17 00:00:00 2001 From: Johannes Rudolph Date: Mon, 16 Jun 2014 23:09:51 +0200 Subject: [PATCH] =doc first parts of HTTP model documentation --- akka-docs-dev/rst/scala.rst | 1 + .../docs/http/HttpServerExampleSpec.scala | 86 ++++++++++ .../rst/scala/code/docs/http/ModelSpec.scala | 85 ++++++++++ akka-docs-dev/rst/scala/http-core-server.rst | 153 ++++++++++++++++++ akka-docs-dev/rst/scala/http-model.rst | 151 +++++++++++++++++ akka-docs-dev/rst/scala/http-routing.rst | 6 + akka-docs-dev/rst/scala/index-http.rst | 9 ++ 7 files changed, 491 insertions(+) create mode 100644 akka-docs-dev/rst/scala/code/docs/http/HttpServerExampleSpec.scala create mode 100644 akka-docs-dev/rst/scala/code/docs/http/ModelSpec.scala create mode 100644 akka-docs-dev/rst/scala/http-core-server.rst create mode 100644 akka-docs-dev/rst/scala/http-model.rst create mode 100644 akka-docs-dev/rst/scala/http-routing.rst create mode 100644 akka-docs-dev/rst/scala/index-http.rst diff --git a/akka-docs-dev/rst/scala.rst b/akka-docs-dev/rst/scala.rst index 9453e15dc1..1371dec927 100644 --- a/akka-docs-dev/rst/scala.rst +++ b/akka-docs-dev/rst/scala.rst @@ -7,3 +7,4 @@ Scala Documentation :maxdepth: 2 experimental/index + scala/index-http diff --git a/akka-docs-dev/rst/scala/code/docs/http/HttpServerExampleSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/HttpServerExampleSpec.scala new file mode 100644 index 0000000000..815ae8c8cb --- /dev/null +++ b/akka-docs-dev/rst/scala/code/docs/http/HttpServerExampleSpec.scala @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +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`, + Hello world!.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 + } +} diff --git a/akka-docs-dev/rst/scala/code/docs/http/ModelSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/ModelSpec.scala new file mode 100644 index 0000000000..67a0867e61 --- /dev/null +++ b/akka-docs-dev/rst/scala/code/docs/http/ModelSpec.scala @@ -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) + } +} diff --git a/akka-docs-dev/rst/scala/http-core-server.rst b/akka-docs-dev/rst/scala/http-core-server.rst new file mode 100644 index 0000000000..37a7e1f2c1 --- /dev/null +++ b/akka-docs-dev/rst/scala/http-core-server.rst @@ -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) \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http-model.rst b/akka-docs-dev/rst/scala/http-model.rst new file mode 100644 index 0000000000..8ce420b6ce --- /dev/null +++ b/akka-docs-dev/rst/scala/http-model.rst @@ -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 you’ll 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. diff --git a/akka-docs-dev/rst/scala/http-routing.rst b/akka-docs-dev/rst/scala/http-routing.rst new file mode 100644 index 0000000000..10b104caad --- /dev/null +++ b/akka-docs-dev/rst/scala/http-routing.rst @@ -0,0 +1,6 @@ +.. _http-routing-scala: + +HTTP Routing +============ + +(todo) \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/index-http.rst b/akka-docs-dev/rst/scala/index-http.rst new file mode 100644 index 0000000000..006333a89d --- /dev/null +++ b/akka-docs-dev/rst/scala/index-http.rst @@ -0,0 +1,9 @@ +HTTP +==== + +.. toctree:: + :maxdepth: 2 + + http-model + http-core-server + http-routing