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:
- The xsbt-web-plugin for simplifying the development process
- The Getting Started chapter for info on the spray project template for spray-servlet
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
andChunkedMessageEnd
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, withflush
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:
Clone the spray repository:
git clone git://github.com/spray/spray.git
Change into the base directory:
cd spray
Run SBT:
sbt "project simple-spray-servlet-server" container:start shell
Browse to http://127.0.0.1:8080/
Alternatively you can access the service with
curl
:curl -v 127.0.0.1:8080/ping
Stop the service with:
container:stop