spray-can is a low-overhead, high-performance, fully asynchronous HTTP 1.1 server and client library implemented entirely in Scala on top of Akka.
Both, the spray-can server and the spray-can client, sport the following features:
- Low per-connection overhead for supporting thousands of concurrent connections
- Efficient message parsing and processing logic for high throughput applications (> 50K requests/sec on ordinary consumer hardware)
- Full support for HTTP/1.1 persistant connections
- Full support for message pipelining
- Full support for asynchronous HTTP streaming (i.e. "chunked" transfer encoding)
- Akka-Actor and -Future based architecture for easy integration into your Akka applications
- No dependencies except for JavaSE 6, Scala 2.9 and Akka 1.2 (actors module).
The spray-can HttpServer is implemented as an Akka actor running on a single, private thread managing a Java NIO selector. Incoming HTTP requests are dispatched as immutable messages to a service actor provided by the application. Requests are completed by calling a responder function passed along (in continuation style) with the request message.
spray-can is scoped with a clear focus on the essential functionality of any 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 layer in the application stack, they are not implemented by spray-can itself. Apart from general focus this design keeps spray-can small and light-weight as well as easy to understand and to maintain. It also makes a spray-can HttpServer a perfect "container" for a spray-server application, since spray-can and spray-server nicely complement and interface into each other. The coming 0.8.0 release of spray-server will support spray-can HttpServer out of the box.
(Everything in this section is also valid in analogy for the spray-can HttpClient implementation.)
The easiest way to get started with spray-can is to try out the server-example
and/or the client-example
that's
part of the spray-can codebase:
-
Git-clone this repository:
$ git clone git://github.com/spray/spray-can.git my-project
-
Change directory into your clone:
$ cd my-project
-
Launch SBT (SBT 0.11.0) and run the server example:
$ sbt "project server-example" run
-
Browse to http://127.0.0.1:8080 and play around with the sample "app".
-
Run the client example:
$ sbt "project client-example" run
-
Start hacking on the sources in
server-example/src/main/scala/cc/spray/can/example/
and/orclient-example/src/main/scala/cc/spray/can/example/
For setting up your own project you need to pull in the spray-can artifacts from the http://scala-tools.org repositories.
The latest release is 0.9.1
. It's built against Scala 2.9.1 and Akka 1.2.
The spray-can HTTP server is really easy to use. All you need to do is start a new HttpServer actor as well as an actor holding your custom request handling logic. Ideally these actors should be supervised:
Supervisor(
SupervisorConfig(
OneForOneStrategy(List(classOf[Exception]), 3, 100),
List(
Supervise(Actor.actorOf(new MyService()), Permanent),
Supervise(Actor.actorOf(new HttpServer()), Permanent)
)
)
)
You can pass a ServerConfig instance to the HttpServer constructor. If you don't spray-can looks for a server
configuration in your applications akka.conf
file and uses default settings for anything not specified there.
By default your service actor needs to have the id spray-root-service
in order to be found by the HttpServer
.
After being started the server actor will accept new HTTP connections on the configured host interface (and port) and
dispatch all incoming HTTP requests as RequestContext messages to your service actor. You can take a look at the
server-examples TestService implementation for some example of what basic request handling with spray-can might
look like.
The spray-can server always passes all received headers on to your application. Additionally the values of the following request headers are interpreted by the server itself:
Connection
Content-Length
Transfer-Encoding
All other headers are of no interest to the server layer.
When sending out responses the server watches for a Connection
header that your application might set and acts
accordingly. I.e. you can force spray-can to close the connection after having sent the response by including an
HttpHeader("Connection", "close")
. To unconditionally force a connection keep-alive you can explicitly set a
HttpHeader("Connection", "Keep-Alive")
header. If you don't set an explicit Connection
header the server will keep
the connection alive if the client supports this (i.e. it either sent a "Connection: Keep-Alive" header or specified
HTTP/1.1 capabilities without sending a "Connection: close" header).
Your HttpResponse instances must not include explicit Content-Length
, Transfer-Encoding
or Date
headers, since
spray-can sets these automatically.
If configured with a non-zero requestTimeout
setting the spray-can HttpServer will watch for request timeouts.
If your application logic does not complete a request by either calling responder.complete
or
responder.startChunkedResponse
on the incoming RequestContext within the configured timeout period the HttpServer
dispatches a Timeout message to the configured timeout actor (which may well be identical to your service actor).
The application then has another chance to complete the request, this time within the configured timeoutTimeout
period. Only if the request is still uncompleted after this time period the HttpServer
will complete the request
itself with the result from its timeoutTimeoutResponse
method (which you may override should the need arise).
If the ServerConfig has a non-zero idleTimeout
the HttpServer
will close idle connections after the respective
time period.
HTTP pipelining is fully supported and completely transparent to your application. I.e. the client is allowed to send
a whole sequence of requests in a row without first waiting for responses. The spray-can HttpServer
dispatches
such pipelined requests to your service actor just as any other. However, since in many asynchronous applications
response times can be somewhat undeterministic spray-can will take care of properly ordering all responses coming in
from your application before sending them out to "the wire". I.e. your application will "see" requests in the order
they are coming in but is not required to uphold this order when generating responses.
HTTP/1.1 defines the "chunked" transfer encoding for HTTP messages (requests and responses), which allows for the sending of very large (even "infinite") HTTP requests or responses. Normally the client or server sending an HTTP message needs to know the size of the message before sending it. Chunked transfer encoding removes this requirement, i.e. the client or server can start sending the message before the complete length is known (which might be useful when transferring things like a live video or audio stream).
spray-can fully supports chunked HTTP requests and responses in an asynchronous fashion.
When the spray-can HttpServer
receives the first bits of a chunked request from a client it starts a new Akka actor
for handling the different parts of the request. The ServerConfig contains a streamActorCreator
member which
can hold a custom function performing the actual actor creation. spray-can takes care of properly starting and
stopping the actor your custom function created as well as dispatching MessageChunk and ChunkedRequestEnd messages
to it. If you do not supply a custom streamActorCreator
spray-can uses a BufferingRequestStreamActor for incoming
chunked requests to transparently buffer and assemble regular HttpResponse instances before dispatching them to the
regular service actor.
Your application can decide to respond to a request with a chunked response rather than a "traditional" one. This is
done via the startChunkedResponse
method of the responder
member of the incoming RequestContext. This method
returns a ChunkedResponder that allows for the sending of the individual message chunks as well as finalization of the
response.
The best way to shut down a spray-can HTTP server instance is to send it an Akka PoisonPill
message.
This will ensure the proper closing of all open connections as well as the freeing all other occupied resources.
Simply stopping the HttpServer
actor by calling stop()
(or Actor.registry.shutdownAll()
) can sometimes lead to the
server thread not properly terminating.
The spray-can HttpClient
is the natural counterpart of the HttpServer
. It shares all core features as well as the
basic "low-level" philosophy with the server.
Just like the HttpServer
the HttpClient
is implemented as an Akka actor running on a single, private thread.
So, in order to use it you first need to create and start it:
Supervisor(
SupervisorConfig(
OneForOneStrategy(List(classOf[Exception]), 3, 100),
List(Supervise(Actor.actorOf(new HttpClient()), Permanent))
)
)
You can pass a ClientConfig instance to the HttpClient constructor. If you don't spray-can looks for a client
configuration in your applications akka.conf
file and uses default settings for anything not specified there.
After being started the client actor will wait for Connect messages from your application, which it responds with
an object implementing the HttpConnection trait. Its scaladoc API documentation should give you a pretty good idea
of how to use an HttpConnection instance for sending requests and receive responses.
As you can see from this API the spray-can HttpClient
works on the basis of individual connections. There is no
higher-level support for automatic connection pooling and such, since this is considered the responsibility of the
next-higher application layer.
If configured with a non-zero requestTimeout
setting the spray-can HttpClient will watch for request timeouts.
If the server does not respond within the configured timeout period a respective HttpClientException instance will
be created and delivered to either the receiver actor or the response future.
Additionally the HttpClient
will automatically close idle connections if the configured idleTimeout
is non-zero.
If you know that the HTTP server your application connects to supports request pipelining you can send several requests in a row without first waiting for responses to come in. The HttpDialog DSL (see below) might make working with persistant connections and request pipelinging a bit easier.
Just like the HttpServer
the HttpClient
supports sending chunked requests as well as receiving chunked responses
in an asynchronous fashion. The scaladoc API documentation of the HttpConnection trait should be rather
self-explanatory with regard to its usage.
As a thin layer on top of the HttpClient
spray-can provides a convenience mini-DSL that makes working with
HTTP connections a bit easier. It is probably best explained by example.
The following snippet shows a minimal, single-request HttpDialog:
import HttpClient._
val response: Future[HttpResponse] =
HttpDialog("github.com")
.send(HttpRequest(method = GET, uri = "/"))
.end
A non-pipelined two-request dialog:
val responses: Future[Seq[HttpResponse]] =
HttpDialog("example.com")
.send(HttpRequest(POST, "/shout").withBody("yeah!"))
.awaitResponse
.send(HttpRequest(PUT, "/count").withBody("42"))
.end
A pipelined three-request dialog:
val responses: Future[Seq[HttpResponse]] =
HttpDialog(host = "img.example.com", port = 8888)
.send(HttpRequest(GET, "a.gif"))
.send(HttpRequest(GET, "b.gif"))
.send(HttpRequest(GET, "c.gif"))
.end
A request -> response -> request dialog:
val response: Future[HttpResponse] =
HttpDialog("example.com")
.send(HttpRequest(GET, "/ping"))
.reply(response => HttpRequest(GET, "/ping2", body = response.body))
.end
Note that the explicit result type annotations are only shown here for documentation. They can be inferred and are therefore not required.
The best way to shut down a spray-can HTTP client instance is to send it an Akka PoisonPill
message.
This will ensure the proper closing of all open connections as well as the freeing all other occupied resources.
Simply stopping the HttpClient
actor by calling stop()
(or Actor.registry.shutdownAll()
) can sometimes lead to
the client thread not properly terminating.
Many questions might already be answered by the spray-can API documentation.
You can also turn to the active http://groups.google.com/group/spray-user mailing list for support.
spray-can is licensed under APL 2.0.
Feedback and contributions to the project, no matter what kind, are always very welcome. However, patches can only be accepted from their original author. Along with any patches, please state that the patch is your original work and that you license the work to the spray-can project under the project’s open source license.