spray-servlet

spray-servlet is an adapter layer providing (a subset of) the spray-can HTTP Server interface on top of the Servlet API. As one main application it enables the use of spray-routing in a servlet container.

Dependencies

Apart from the Scala library (see Current Versions chapter) spray-servlet depends on

  • spray-http
  • spray-util
  • spray-io (only required until the upgrade to Akka 2.2, will go away afterwards)
  • akka-actor 2.2.x (with ‘provided’ scope, i.e. you need to pull it in yourself)
  • the Servlet-3.0 API (with ‘provided’ scope, usually automatically available from your servlet container)

Installation

The Maven Repository chapter contains all the info about how to pull spray-servlet into your classpath. You might also want to check out:

Configuration

Just like Akka spray-servlet relies on the typesafe config library for configuration. As such its JAR contains a reference.conf file holding the default values of all configuration settings. In your application you typically provide an application.conf, in which you override Akka and/or spray settings according to your needs.

Note

Since spray uses the same configuration technique as Akka you might want to check out the Akka Documentation on Configuration.

This is the reference.conf of the spray-servlet module:

#######################################
# spray-servlet Reference Config File #
#######################################

# This is the reference config file that contains all the default settings.
# Make your edits/overrides in your application.conf.

spray.servlet {

    # The FQN (Fully Qualified Name) of the class to load when the
    # servlet context is initialized (e.g. "com.example.ApiBoot").
    # The class must have a constructor with a single
    # `javax.servlet.ServletContext` parameter and implement
    # the `spray.servlet.WebBoot` trait.
    boot-class = ""

    # If a request hasn't been responded to after the time period set here
    # a `spray.http.Timedout` message will be sent to the timeout handler.
    # Set to `infinite` to completely disable request timeouts.
    request-timeout = 30 s

    # After a `Timedout` message has been sent to the timeout handler and the
    # request still hasn't been completed after the time period set here
    # the server will complete the request itself with an error response.
    # Set to `infinite` to disable timeout timeouts.
    timeout-timeout = 500 ms

    # The path of the actor to send `spray.http.Timedout` messages to.
    # If empty all `Timedout` messages will go to the "regular" request handling actor.
    timeout-handler = ""

    # A path prefix that is automatically "consumed" before the request is
    # being dispatched to the HTTP service route.
    # Can be used to match servlet context paths configured for the application.
    # Make sure to include a leading slash with your prefix, e.g. "/foobar".
    # Set to `AUTO` to make spray-servlet pick up the ServletContext::getContextPath.
    root-path = AUTO

    # Enables/disables the addition of a `Remote-Address` header
    # holding the clients (remote) IP address.
    remote-address-header = off

    # Enables/disables the returning of more detailed error messages to
    # the client in the error response.
    # Should be disabled for browser-facing APIs due to the risk of XSS attacks
    # and (probably) enabled for internal or non-browser APIs.
    # Note that spray will always produce log messages containing the full error details.
    verbose-error-messages = off

    # The maximum size of the request entity that is still accepted by the server.
    # Requests with a greater entity length are rejected with an error response.
    # Must be greater than zero.
    max-content-length = 5 m

    # Enables/disables the inclusion of `spray.servlet.ServletRequestInfoHeader` in the
    # headers of the HTTP request sent to the service actor.
    servlet-request-access = off

    # Enables/disables the logging of warning messages in case an incoming
    # message (request or response) contains an HTTP header which cannot be
    # parsed into its high-level model class due to incompatible syntax.
    # Note that, independently of this settings, spray will accept messages
    # with such headers as long as the message as a whole would still be legal
    # under the HTTP specification even without this header.
    # If a header cannot be parsed into a high-level model instance it will be
    # provided as a `RawHeader`.
    illegal-header-warnings = on

    # Sets the strictness mode for parsing request target URIs.
    # The following values are defined:
    #
    # `strict`: RFC3986-compliant URIs are required,
    #     a 400 response is triggered on violations
    #
    # `relaxed`: all visible 7-Bit ASCII chars are allowed
    #
    # `relaxed-with-raw-query`: like `relaxed` but additionally
    #     the URI query is not parsed, but delivered as one raw string
    #     as the `key` value of a single Query structure element.
    #
    uri-parsing-mode = relaxed
}

Basic Architecture

The central element of spray-servlet is the Servlet30ConnectorServlet. Its job is to accept incoming HTTP requests, suspend them (using Servlet 3.0 startAsync), create immutable spray-http HttpRequest instances for them and dispatch these to a service actor provided by the application.

The messaging API as seen from the application is modeled as closely as possible like its counterpart, the spray-can HTTP Server.

In the most basic case, the service actor completes a request by simply replying with an HttpResponse instance to the request sender:

def receive = {
  case HttpRequest(...) => sender ! HttpResponse(...)
}

Starting and Stopping

A spray-servlet application is started by the servlet container. The application JAR should contain a web.xml similar to this one from the simple-spray-servlet-server example.

The web.xml registers a ServletContextListener (spray.servlet.Initializer), which initializes the application when the servlet is started. The Initializer loads the configured boot-class and instantiates it using the default constructor, which must be available. The boot class must implement the WebBoot trait, which is defined like this:

/**
 * Trait that must be implemented by the Boot class.
 */
trait WebBoot {

  /**
   * The ActorSystem the application would like to use.
   */
  def system: ActorSystem

  /**
   * The service actor to dispatch incoming HttpRequests to.
   */
  def serviceActor: ActorRef
}

A very basic boot class implementation is this one from the simple-spray-servlet-server example.

The boot class is responsible for creating the Akka ActorSystem for the application as well as the service actor. When the application is shut down by the servlet container the Initializer shuts down the ActorSystem, which cleanly terminates all application actors including the service actor.

Message Protocol

Just like in its counterpart, the spray-can HTTP Server, all communication between the connector servlet and the application happens through actor messages.

Request-Response Cycle

As soon as a new request has been successfully read from the servlet API it is dispatched to the service actor created by the boot class. The service actor processes the request according to the application logic and responds by sending an HttpResponse instance to the sender of the request.

The ActorRef used as the sender of an HttpRequest received by the service actor is unique to the request, i.e. each request will appear to be sent from different senders. spray-servlet uses these sender ActorRefs to coalesce the response with the request, so you cannot sent several responses to the same sender. However, the different response parts of a chunked response need to be sent to the same sender.

Caution

Since the ActorRef used as the sender of a request is an UnregisteredActorRef it is not reachable remotely. This means that the service actor needs to live in the same JVM as the connector servlet.

Chunked Responses

Alternatively to a single HttpResponse instance the handler can choose to respond to the request sender with the following sequence of individual messages:

  • One ChunkedResponseStart
  • Zero or more MessageChunks
  • One ChunkedMessageEnd

The connector servlet writes the individual response parts into the servlet response OutputStream and flushes it. Whether these parts are really rendered “to the wire” as chunked message parts depends on the servlet container implementation. The Servlet API has not dedicated support for chunked responses.

Request Timeouts

If the service actor does not complete a request within the configured request-timeout period a spray.http.Timedout message is sent to the timeout handler, which can be the service actor itself or another actor (depending on the timeout-handler config setting). The timeout handler then has the chance to complete the request within the time period configured as timeout-timeout. Only if the timeout handler also misses its deadline for completing the request will the connector servlet complete the request itself with a “hard-coded” error response (which you can change by overriding the timeoutResponse method of the Servlet30ConnectorServlet).

Send Confirmations

If required the connector servlet can reply with a “send confirmation” message to every response (part) coming in from the application. You request a send confirmation by modifying a response part with the withAck method (see the ACKed Sends section of the spray-can documentation for example code). Confirmation messages are especially helpful for triggering the sending of the next response part in a response streaming scenario, since with such a design the application will never produce more data than the servlet container can handle.

Send confirmations are always dispatched to the actor, which sent the respective response (part).

Closed Notifications

The Servlet API completely hides the actual management of the HTTP connections from the application. Therefore the connector servlet has no real way of finding out whether a connection was closed or not. However, if the connection was closed unexpectedly for whatever reason a subsequent attempt to write to it usually fails with an IOException. In order to adhere to same message protocol as the spray-can HTTP Server the connector servlet therefore dispatches any exception, which the servlet container throws when a response (part) is written, back to the application wrapped in an Tcp.ErrorClosed message.

In addition the connector servlet also dispatches Tcp.Closed notification messages after the final part of a response has been successfully written to the servlet container. This allows the application to use the same execution model for spray-servlet as it would for the spray-can HTTP Server.

HTTP Headers

The connector servlet always passes all received headers on to the application. Additionally the values of the Content-Length and Content-Type headers are interpreted by the servlet itself. All other headers are of no interest to it.

Also, if your HttpResponse instances include a Content-Length or Content-Type header they will be ignored and not written through to the servlet container (as the connector servlet sets these response headers itself).

Note

The Content-Type header has special status in spray since its value is part of the HttpEntity model class. Even though the header also remains in the headers list of the HttpRequest spray’s higher layers (like spray-routing) only work with the Content-Type value contained in the HttpEntity.

Accessing HttpServletRequest

If your application needs access to the javax.servlet.http.HttpServletRequest, the spray.servlet.servlet-request-access setting can be set to on. This results in the connector servlet adding an additional request header of type spray.servlet.ServletRequestInfoHeader. This allows the service actor (or directives) to access members of HttpServletRequest that are not in HttpRequest. This is necessary when working with container managed security and access to the authenticated principal is required (via getUserPrincipal) or when accessing an authenticated client SSL certificate (via getAttribute("javax.servlet.request.X509Certificate")).

Differences to spray-can

Chunked Requests
Since the Servlet API does not expose the individual request parts of chunked requests to a servlet there is no way spray-servlet can pass them through to the application. The way chunked requests are handled is completely up to the servlet container.
Chunked Responses
spray-can renders ChunkedResponseStart, MessageChunks and ChunkedMessageEnd messages directly to “the wire”. Since the Servlet API operates on a somewhat higher level of abstraction spray-servlet can only write these messages to the servlet container one by one, with flush calls in between. The way the servlet container interprets these calls is up to its implementation.
Closed Messages
The Servlet API completely hides the actual management of the HTTP connections from the application. Therefore the connector servlet has no way of finding out whether a connection was closed or not. In order to provide a similar message protocol as spray-can the connector servlet therefore simply assumes that all connections are closed after the final part of a response has been written, no matter whether the servlet container actually uses persistent connections or not.
Timeout Semantics
When working with chunked responses the semantics of the request-timeout config setting are different. In spray-can it designates the maximum time, in which a response must have been started (i.e. the first chunk received), while in spray-servlet it defines the time, in which the response must have been completed (i.e. the last chunk received).
HTTP Pipelining & SSL Support
Whether and how HTTP pipelining and SSL/TLS encryption are supported depends on the servlet container implementation.

Packaging a WAR file

If you use the xsbt-web-plugin you can very easily package your project into a WAR file with the package command provided by the plugin.

Example

The /examples/spray-servlet/ directory of the spray repository contains a number of example projects for spray-servlet.

simple-spray-servlet-server

This example implements a very simple web-site built on top of spray-servlet. It shows off various features like streaming and timeout handling.

Follow these steps to run it on your machine:

  1. Clone the spray repository:

    git clone git://github.com/spray/spray.git
    
  2. Change into the base directory:

    cd spray
    
  3. Run SBT:

    sbt "project simple-spray-servlet-server" container:start shell
    
  4. Browse to http://127.0.0.1:8080/

  5. Alternatively you can access the service with curl:

    curl -v 127.0.0.1:8080/ping
    
  6. Stop the service with:

    container:stop