HTTP Server Libraries
Web application development typically involves writing your web applications, packaging them into a web application archive, the *.war
file, and then deploy the *.war
file into a standalone Servlet Container that you have previously installed.
The Eclipse Jetty server libraries allow you to write web applications components using either the Jetty APIs (by writing Jetty Handler
s) or using the standard Servlet APIs (by writing Servlet
s and Servlet Filter
s).
These components can then be programmatically assembled together, without the need of creating a *.war
file, added to a Jetty Server
instance that is then started.
This result in your web applications to be available to HTTP clients as if you deployed your *.war
files in a standalone Jetty server.
The Maven artifact coordinates are:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>11.0.24-SNAPSHOT</version>
</dependency>
An org.eclipse.jetty.server.Server
instance is the central component that links together a collection of Connector
s and a collection of Handler
s, with threads from a ThreadPool
doing the work.
The components that accept connections from clients are org.eclipse.jetty.server.Connector
implementations.
When a Jetty server interprets the HTTP protocol (HTTP/1.1, HTTP/2 or HTTP/3), it uses org.eclipse.jetty.server.Handler
instances to process incoming requests and eventually produce responses.
A Server
must be created, configured and started:
// Create and configure a ThreadPool.
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("server");
// Create a Server instance.
Server server = new Server(threadPool);
// Create a ServerConnector to accept connections from clients.
Connector connector = new ServerConnector(server);
// Add the Connector to the Server
server.addConnector(connector);
// Set a simple Handler to handle requests/responses.
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
// Mark the request as handled so that it
// will not be processed by other handlers.
jettyRequest.setHandled(true);
}
});
// Start the Server so it starts accepting connections from clients.
server.start();
The example above shows the simplest HTTP/1.1 server; it has no support for HTTP sessions, for HTTP authentication, or for any of the features required by the Servlet specification.
All these features are provided by the Jetty Server Libraries, and server applications only need to put the required components together to provide all the required features.
The Handler
s provided by the Jetty Server Libraries allow writing server applications that have functionalities similar to Apache HTTPD or Nginx (for example: URL redirection, URL rewriting, serving static content, reverse proxying, etc.), as well as generating content dynamically by processing incoming requests.
Read this section for further details about Handler
s.
If you are interested in writing your server application based on the Servlet APIs, jump to this section.
Request Processing
The Jetty HTTP request processing is outlined below in the diagram below. You may want to refer to the Jetty I/O architecture for additional information about the classes mentioned below.
Request handing is slightly different for each protocol; in HTTP/2 Jetty takes into account multiplexing, something that is not present in HTTP/1.1.
However, the diagram below captures the essence of request handling that is common among all protocols that carry HTTP requests.
First, the Jetty I/O layer emits an event that a socket has data to read.
This event is converted to a call to AbstractConnection.onFillable()
, where the Connection
first reads from the EndPoint
into a ByteBuffer
, and then calls a protocol specific parser to parse the bytes in the ByteBuffer
.
The parser emit events that are protocol specific; the HTTP/2 parser, for example, emits events for each HTTP/2 frame that has been parsed, and similarly does the HTTP/3 parser.
The parser events are then converted to protocol independent events such as "request start", "request headers", "request content chunk", etc.
that in turn are converted into method calls to HttpChannel
.
When enough of the HTTP request is arrived, the Connection
calls HttpChannel.handle()
that calls the Handler
chain, that eventually calls the server application code.
HttpChannel Events
The central component processing HTTP requests is HttpChannel
.
There is a 1-to-1 relationship between an HTTP request/response and an HttpChannel
, no matter what is the specific protocol that carries the HTTP request over the network (HTTP/1.1, HTTP/2, HTTP/3 or FastCGI).
Advanced server applications may be interested in the progress of the processing of an HTTP request/response by HttpChannel
.
A typical case is to know exactly when the HTTP request/response processing is complete, for example to monitor processing times.
A Handler or a Servlet Filter may not report precisely when an HTTP request/response processing is finished.
A server application may write a small enough content that is aggregated by Jetty for efficiency reasons; the write returns immediately, but nothing has been written to the network yet.
|
HttpChannel
notifies HttpChannel.Listener
s of the progress of the HTTP request/response handling.
Currently, the following events are available:
-
requestBegin
-
beforeDispatch
-
dispatchFailure
-
afterDispatch
-
requestContent
-
requestContentEnd
-
requestTrailers
-
requestEnd
-
responseBegin
-
responseCommit
-
responseContent
-
responseFailure
-
responseEnd
-
complete
Please refer to the HttpChannel.Listener
javadocs for the complete list of events.
Server applications can register HttpChannel.Listener
by adding them as beans to the Connector
:
class TimingHttpChannelListener implements HttpChannel.Listener
{
private final ConcurrentMap<Request, Long> times = new ConcurrentHashMap<>();
@Override
public void onRequestBegin(Request request)
{
times.put(request, NanoTime.now());
}
@Override
public void onComplete(Request request)
{
long begin = times.remove(request);
long elapsed = NanoTime.since(begin);
System.getLogger("timing").log(INFO, "Request {0} took {1} ns", request, elapsed);
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Add the HttpChannel.Listener as bean to the connector.
connector.addBean(new TimingHttpChannelListener());
// Set a simple Handler to handle requests/responses.
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
jettyRequest.setHandled(true);
}
});
server.start();
Request Logging
HTTP requests and responses can be logged to provide data that can be later analyzed with other tools. These tools can provide information such as the most frequently accessed request URIs, the response status codes, the request/response content lengths, geographical information about the clients, etc.
The default request/response log line format is the NCSA Format extended with referrer data and user-agent data.
Typically, the extended NCSA format is the is enough and it’s the standard used and understood by most log parsing tools and monitoring tools. To customize the request/response log line format see the |
Request logging can be enabled at the server level, or at the web application context level.
The request logging output can be directed to an SLF4J logger named "org.eclipse.jetty.server.RequestLog"
at INFO
level, and therefore to any logging library implementation of your choice (see also this section about logging).
Server server = new Server();
// Sets the RequestLog to log to an SLF4J logger named "org.eclipse.jetty.server.RequestLog" at INFO level.
server.setRequestLog(new CustomRequestLog(new Slf4jRequestLogWriter(), CustomRequestLog.EXTENDED_NCSA_FORMAT));
Alternatively, the request logging output can be directed to a daily rolling file of your choice, and the file name must contain yyyy_MM_dd
so that rolled over files retain their date:
Server server = new Server();
// Use a file name with the pattern 'yyyy_MM_dd' so rolled over files retain their date.
RequestLogWriter logWriter = new RequestLogWriter("/var/log/yyyy_MM_dd.jetty.request.log");
// Retain rolled over files for 2 weeks.
logWriter.setRetainDays(14);
// Log times are in the current time zone.
logWriter.setTimeZone(TimeZone.getDefault().getID());
// Set the RequestLog to log to the given file, rolling over at midnight.
server.setRequestLog(new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT));
For maximum flexibility, you can log to multiple RequestLog
s using class RequestLog.Collection
, for example by logging with different formats or to different outputs.
You can use CustomRequestLog
with a custom RequestLog.Writer
to direct the request logging output to your custom targets (for example, an RDBMS).
You can implement your own RequestLog
if you want to have functionalities that are not implemented by CustomRequestLog
.
Request logging can also be enabled at the web application context level, using RequestLogHandler
(see this section about how to organize Jetty Handler
s) to wrap a web application Handler
:
Server server = new Server();
// Create a first ServletContextHandler for your main application.
ServletContextHandler mainContext = new ServletContextHandler();
mainContext.setContextPath("/main");
// Create a RequestLogHandler to log requests for your main application.
RequestLogHandler requestLogHandler = new RequestLogHandler();
requestLogHandler.setRequestLog(new CustomRequestLog());
// Wrap the main application with the request log handler.
requestLogHandler.setHandler(mainContext);
// Create a second ServletContextHandler for your other application.
// No request logging for this application.
ServletContextHandler otherContext = new ServletContextHandler();
mainContext.setContextPath("/other");
server.setHandler(new HandlerList(requestLogHandler, otherContext));
Server Connectors
A Connector
is the component that handles incoming requests from clients, and works in conjunction with ConnectionFactory
instances.
The available implementations are:
-
org.eclipse.jetty.server.ServerConnector
, for TCP/IP sockets. -
org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector
for Unix-Domain sockets (requires Java 16 or later).
Both use a java.nio.channels.ServerSocketChannel
to listen to a socket address and to accept socket connections.
Since ServerConnector
wraps a ServerSocketChannel
, it can be configured in a similar way, for example the IP port to listen to, the IP address to bind to, etc.:
Server server = new Server();
// The number of acceptor threads.
int acceptors = 1;
// The number of selectors.
int selectors = 1;
// Create a ServerConnector instance.
ServerConnector connector = new ServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
// Configure TCP/IP parameters.
// The port to listen to.
connector.setPort(8080);
// The address to bind to.
connector.setHost("127.0.0.1");
// The TCP accept queue size.
connector.setAcceptQueueSize(128);
server.addConnector(connector);
server.start();
Likewise, UnixDomainServerConnector
also wraps a ServerSocketChannel
and can be configured with the Unix-Domain path to listen to:
Server server = new Server();
// The number of acceptor threads.
int acceptors = 1;
// The number of selectors.
int selectors = 1;
// Create a ServerConnector instance.
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, acceptors, selectors, new HttpConnectionFactory());
// Configure Unix-Domain parameters.
// The Unix-Domain path to listen to.
connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
// The TCP accept queue size.
connector.setAcceptQueueSize(128);
server.addConnector(connector);
server.start();
You can use Unix-Domain sockets support only when you run your server with Java 16 or later. |
The acceptors are threads (typically only one) that compete to accept socket connections.
When a connection is accepted, ServerConnector
wraps the accepted SocketChannel
and passes it to the SelectorManager
.
Therefore, there is a little moment where the acceptor thread is not accepting new connections because it is busy wrapping the just accepted connection to pass it to the SelectorManager
.
Connections that are ready to be accepted but are not accepted yet are queued in a bounded queue (at the OS level) whose capacity can be configured with the acceptQueueSize
parameter.
If your application must withstand a very high rate of connections opened, configuring more than one acceptor thread may be beneficial: when one acceptor thread accepts one connection, another acceptor thread can take over accepting connections.
The selectors are components that manage a set of connected sockets, implemented by ManagedSelector
.
Each selector requires one thread and uses the Java NIO mechanism to efficiently handle a set of connected sockets.
As a rule of thumb, a single selector can easily manage up to 1000-5000 sockets, although the number may vary greatly depending on the application.
For example, web site applications tend to use sockets for one or more HTTP requests to retrieve resources and then the socket is idle for most of the time. In this case a single selector may be able to manage many sockets because chances are that they will be idle most of the time. On the contrary, web messaging applications tend to send many small messages at a very high frequency so that sockets are rarely idle. In this case a single selector may be able to manage less sockets because chances are that many of them will be active at the same time.
It is possible to configure more than one ServerConnector
(each listening on a different port), or more than one UnixDomainServerConnector
(each listening on a different path), or ServerConnector
s and UnixDomainServerConnector
s, for example:
Server server = new Server();
// Create a ServerConnector instance on port 8080.
ServerConnector connector1 = new ServerConnector(server, 1, 1, new HttpConnectionFactory());
connector1.setPort(8080);
server.addConnector(connector1);
// Create another ServerConnector instance on port 9090,
// for example with a different HTTP configuration.
HttpConfiguration httpConfig2 = new HttpConfiguration();
httpConfig2.setHttpCompliance(HttpCompliance.LEGACY);
ServerConnector connector2 = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig2));
connector2.setPort(9090);
server.addConnector(connector2);
server.start();
Configuring Protocols
For each accepted socket connection, the server Connector
asks a ConnectionFactory
to create a Connection
object that handles the traffic on that socket connection, parsing and generating bytes for a specific protocol (see this section for more details about Connection
objects).
A server Connector
can be configured with one or more ConnectionFactory
s.
If no ConnectionFactory
is specified then HttpConnectionFactory
is implicitly configured.
Clear-Text HTTP/1.1
HttpConnectionFactory
creates HttpConnection
objects that parse bytes and generate bytes for the HTTP/1.1 protocol.
This is how you configure Jetty to support clear-text HTTP/1.1:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Configure the HTTP support, for example:
httpConfig.setSendServerVersion(false);
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// Create the ServerConnector.
ServerConnector connector = new ServerConnector(server, http11);
connector.setPort(8080);
server.addConnector(connector);
server.start();
Encrypted HTTP/1.1 (https)
Supporting encrypted HTTP/1.1 (that is, requests with the https
scheme) is supported by configuring an SslContextFactory
that has access to the KeyStore containing the private server key and public server certificate, in this way:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Add the SecureRequestCustomizer because we are using TLS.
httpConfig.addCustomizer(new SecureRequestCustomizer());
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// Configure the SslContextFactory with the keyStore information.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
// The ConnectionFactory for TLS.
SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol());
// The ServerConnector instance.
ServerConnector connector = new ServerConnector(server, tls, http11);
connector.setPort(8443);
server.addConnector(connector);
server.start();
You can customize the SSL/TLS provider as explained in this section.
Clear-Text HTTP/2
It is well known that the HTTP ports are 80
(for clear-text HTTP) and 443
for encrypted HTTP.
By using those ports, a client had prior knowledge that the server would speak, respectively, the HTTP/1.x protocol and the TLS protocol (and, after decryption, the HTTP/1.x protocol).
HTTP/2 was designed to be a smooth transition from HTTP/1.1 for users and as such the HTTP ports were not changed.
However the HTTP/2 protocol is, on the wire, a binary protocol, completely different from HTTP/1.1.
Therefore, with HTTP/2, clients that connect to port 80
(or to a specific Unix-Domain path) may speak either HTTP/1.1 or HTTP/2, and the server must figure out which version of the HTTP protocol the client is speaking.
Jetty can support both HTTP/1.1 and HTTP/2 on the same clear-text port by configuring both the HTTP/1.1 and the HTTP/2 ConnectionFactory
s:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// The ConnectionFactory for clear-text HTTP/2.
HTTP2CServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
// The ServerConnector instance.
ServerConnector connector = new ServerConnector(server, http11, h2c);
connector.setPort(8080);
server.addConnector(connector);
server.start();
Note how the ConnectionFactory
s passed to ServerConnector
are in order: first HTTP/1.1, then HTTP/2.
This is necessary to support both protocols on the same port: Jetty will start parsing the incoming bytes as HTTP/1.1, but then realize that they are HTTP/2 bytes and will therefore upgrade from HTTP/1.1 to HTTP/2.
This configuration is also typical when Jetty is installed in backend servers behind a load balancer that also takes care of offloading TLS. When Jetty is behind a load balancer, you can always prepend the PROXY protocol as described in this section.
Encrypted HTTP/2
When using encrypted HTTP/2, the unencrypted protocol is negotiated by client and server using an extension to the TLS protocol called ALPN.
Jetty supports ALPN and encrypted HTTP/2 with this configuration:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Add the SecureRequestCustomizer because we are using TLS.
httpConfig.addCustomizer(new SecureRequestCustomizer());
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// The ConnectionFactory for HTTP/2.
HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfig);
// The ALPN ConnectionFactory.
ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory();
// The default protocol to use in case there is no negotiation.
alpn.setDefaultProtocol(http11.getProtocol());
// Configure the SslContextFactory with the keyStore information.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
// The ConnectionFactory for TLS.
SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());
// The ServerConnector instance.
ServerConnector connector = new ServerConnector(server, tls, alpn, h2, http11);
connector.setPort(8443);
server.addConnector(connector);
server.start();
Note how the ConnectionFactory
s passed to ServerConnector
are in order: TLS, ALPN, HTTP/2, HTTP/1.1.
Jetty starts parsing TLS bytes so that it can obtain the ALPN extension.
With the ALPN extension information, Jetty can negotiate a protocol and pick, among the ConnectionFactory
s supported by the ServerConnector
, the ConnectionFactory
correspondent to the negotiated protocol.
The fact that the HTTP/2 protocol comes before the HTTP/1.1 protocol indicates that HTTP/2 is the preferred protocol for the server.
Note also that the default protocol set in the ALPN ConnectionFactory
, which is used in case ALPN is not supported by the client, is HTTP/1.1 — if the client does not support ALPN is probably an old client so HTTP/1.1 is the safest choice.
You can customize the SSL/TLS provider as explained in this section.
HTTP/3
HTTP/3 is based on UDP, differently from HTTP/1 and HTTP/2 that are based on TCP.
An HTTP/3 client may initiate a connection (using the QUIC protocol via UDP) on the canonical HTTP secure port 443
, but chances are that the connection may not succeed (for example, the server does not listen for UDP on port 443
, only listens for TCP).
For this reason, HTTP servers typically listen on the canonical HTTP secure port 443
for HTTP/1 and HTTP/2, and advertise the availability HTTP/3 as an HTTP alternate service on a different port (and possibly a different host).
For example, an HTTP/2 response may include the following header:
Alt-Svc: h3=":843"
The presence of this header indicates that protocol h3
is available on the same host (since no host is defined before the port), but on port 843
.
The HTTP/3 client may now initiate a QUIC connection on port 843
and make HTTP/3 requests.
The code necessary to configure HTTP/2 is described in this section.
To setup HTTP/3, for example on port 843
, you need the following code (some of which could be shared with other connectors such as HTTP/2’s):
Server server = new Server();
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new SecureRequestCustomizer());
// Create and configure the HTTP/3 connector.
HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactory, new HTTP3ServerConnectionFactory(httpConfig));
connector.setPort(843);
server.addConnector(connector);
server.start();
Using Conscrypt as SSL/TLS Provider
If not explicitly configured, the TLS implementation is provided by the JDK you are using at runtime.
OpenJDK’s vendors may replace the default TLS provider with their own, but you can also explicitly configure an alternative TLS provider.
The standard TLS provider from OpenJDK is implemented in Java (no native code), and its performance is not optimal, both in CPU usage and memory usage.
A faster alternative, implemented natively, is Google’s Conscrypt, which is built on BoringSSL, which is Google’s fork of OpenSSL.
As Conscrypt eventually binds to a native library, there is a higher risk that a bug in Conscrypt or in the native library causes a JVM crash, while the Java implementation will not cause a JVM crash. |
To use Conscrypt as TLS provider, you must have the Conscrypt jar and the Jetty dependency jetty-alpn-conscrypt-server-11.0.24-SNAPSHOT.jar
in the class-path or module-path.
Then, you must configure the JDK with the Conscrypt provider, and configure Jetty to use the Conscrypt provider, in this way:
// Configure the JDK with the Conscrypt provider.
Security.addProvider(new OpenSSLProvider());
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
// Configure Jetty's SslContextFactory to use Conscrypt.
sslContextFactory.setProvider("Conscrypt");
Jetty Behind a Load Balancer
It is often the case that Jetty receives connections from a load balancer configured to distribute the load among many Jetty backend servers.
From the Jetty point of view, all the connections arrive from the load balancer, rather than the real clients, but is possible to configure the load balancer to forward the real client IP address and IP port to the backend Jetty server using the PROXY protocol.
The PROXY protocol is widely supported by load balancers such as HAProxy (via its send-proxy directive), Nginx(via its proxy_protocol on directive) and others.
|
To support this case, Jetty can be configured in this way:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Configure the HTTP support, for example:
httpConfig.setSendServerVersion(false);
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// The ConnectionFactory for the PROXY protocol.
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
// Create the ServerConnector.
ServerConnector connector = new ServerConnector(server, proxy, http11);
connector.setPort(8080);
server.addConnector(connector);
server.start();
Note how the ConnectionFactory
s passed to ServerConnector
are in order: first PROXY, then HTTP/1.1.
Note also how the PROXY ConnectionFactory
needs to know its next protocol (in this example, HTTP/1.1).
Each ConnectionFactory
is asked to create a Connection
object for each accepted TCP connection; the Connection
objects will be chained together to handle the bytes, each for its own protocol.
Therefore the ProxyConnection
will handle the PROXY protocol bytes and HttpConnection
will handle the HTTP/1.1 bytes producing a request object and response object that will be processed by Handler
s.
The load balancer may be configured to communicate with Jetty backend servers via Unix-Domain sockets (requires Java 16 or later). For example:
Server server = new Server();
// The HTTP configuration object.
HttpConfiguration httpConfig = new HttpConfiguration();
// Configure the HTTP support, for example:
httpConfig.setSendServerVersion(false);
// The ConnectionFactory for HTTP/1.1.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfig);
// The ConnectionFactory for the PROXY protocol.
ProxyConnectionFactory proxy = new ProxyConnectionFactory(http11.getProtocol());
// Create the ServerConnector.
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, proxy, http11);
connector.setUnixDomainPath(Path.of("/tmp/jetty.sock"));
server.addConnector(connector);
server.start();
Note that the only difference when using Unix-Domain sockets is instantiating UnixDomainServerConnector
instead of ServerConnector
and configuring the Unix-Domain path instead of the IP port.
Server Handlers
An org.eclipse.jetty.server.Handler
is the component that processes incoming HTTP requests and eventually produces HTTP responses.
Handler
s can be organized in different ways:
-
in a sequence, where
Handler
s are invoked one after the other-
HandlerCollection
invokes allHandler
s one after the other -
HandlerList
invokesHandlers
s until one callsRequest.setHandled(true)
to indicate that the request has been handled and no furtherHandler
should be invoked
-
-
nested, where one
Handler
invokes the next, nested,Handler
-
HandlerWrapper
implements this behavior
-
The HandlerCollection
behavior (invoking all handlers) is useful when for example the last Handler
is a logging Handler
that logs the request (that may have been modified by previous handlers).
The HandlerList
behavior (invoking handlers up to the first that calls Request.setHandled(true)
) is useful when each handler processes a different URIs or a different virtual hosts: Handler
s are invoked one after the other until one matches the URI or virtual host.
The nested behavior is useful to enrich the request with additional services such as HTTP session support (SessionHandler
), or with specific behaviors dictated by the Servlet specification (ServletHandler
).
Handler
s can be organized in a tree by composing them together:
// Create a Server instance.
Server server = new Server();
HandlerCollection collection = new HandlerCollection();
// Link the root Handler with the Server.
server.setHandler(collection);
HandlerList list = new HandlerList();
collection.addHandler(list);
collection.addHandler(new LoggingHandler());
list.addHandler(new App1Handler());
HandlerWrapper wrapper = new HandlerWrapper();
list.addHandler(wrapper);
wrapper.setHandler(new App2Handler());
The corresponding Handler
tree structure looks like the following:
HandlerCollection
├── HandlerList
│ ├── App1Handler
│ └── HandlerWrapper
│ └── App2Handler
└── LoggingHandler
Server applications should rarely write custom Handler
s, preferring instead to use existing Handler
s provided by the Jetty Server Libraries for managing web application contexts, security, HTTP sessions and Servlet support.
Refer to this section for more information about how to use the Handler
s provided by the Jetty Server Libraries.
However, in some cases the additional features are not required, or additional constraints on memory footprint, or performance, or just simplicity must be met.
In these cases, implementing your own Handler
may be a better solution.
Refer to this section for more information about how to write your own Handler
s.
Jetty Handlers
Web applications are the unit of deployment in an HTTP server or Servlet container such as Jetty.
Two different web applications are typically deployed on different context paths, where a context path is the initial segment of the URI path.
For example, web application webappA
that implements a web user interface for an e-commerce site may be deployed to context path /shop
, while web application webappB
that implements a REST API for the e-commerce business may be deployed to /api
.
A client making a request to URI /shop/cart
is directed by Jetty to webappA
, while a request to URI /api/products
is directed to webappB
.
An alternative way to deploy the two web applications of the example above is to use virtual hosts.
A virtual host is a subdomain of the primary domain that shares the same IP address with the primary domain.
If the e-commerce business primary domain is domain.com
, then a virtual host for webappA
could be shop.domain.com
, while a virtual host for webappB
could be api.domain.com
.
Web application webappA
can now be deployed to virtual host shop.domain.com
and context path /
, while web application webappB
can be deployed to virtual host api.domain.com
and context path /
.
Both applications have the same context path /
, but they can be distinguished by the subdomain.
A client making a request to https://shop.domain.com/cart
is directed by Jetty to webappA
, while a request to https://api.domain.com/products
is directed to webappB
.
Therefore, in general, a web application is deployed to a context which can be seen as the pair (virtual_host, context_path)
.
In the first case the contexts were (domain.com, /shop)
and (domain.com, /api)
, while in the second case the contexts were (shop.domain.com, /)
and (api.domain.com, /)
.
Server applications using the Jetty Server Libraries create and configure a context for each web application.
Many contexts can be deployed together to enrich the web application offering — for example a catalog context, a shop context, an API context, an administration context, etc.
Web applications can be written using exclusively the Servlet APIs, since developers know well the Servlet API and because they guarantee better portability across Servlet container implementations.
Embedded web applications based on the Servlet APIs are described in this section.
Embedded web applications may also require additional features such as access to Jetty specific APIs, or utility features such as redirection from HTTP to HTTPS, support for gzip
content compression, etc.
The Jetty Server Libraries provides a number of out-of-the-box Handlers that implement the most common functionalities and are described in this section.
ContextHandler
ContextHandler
is a Handler
that represents a context for a web application.
It is a HandlerWrapper
that performs some action before and after delegating to the nested Handler
.
The simplest use of ContextHandler
is the following:
class ShopHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
// Implement the shop.
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a ContextHandler with contextPath.
ContextHandler context = new ContextHandler();
context.setContextPath("/shop");
context.setHandler(new ShopHandler());
// Link the context to the server.
server.setHandler(context);
server.start();
The Handler
tree structure looks like the following:
Server
└── ContextHandler /shop
└── ShopHandler
ContextHandlerCollection
Server applications may need to deploy to Jetty more than one web application.
Recall from the introduction that Jetty offers HandlerCollection
and HandlerList
that may contain a sequence of children Handler
s.
However, both of these have no knowledge of the concept of context and just iterate through the sequence of Handler
s.
A better choice for multiple web application is ContextHandlerCollection
, that matches a context from either its context path or virtual host, without iterating through the Handler
s.
If ContextHandlerCollection
does not find a match, it just returns.
What happens next depends on the Handler
tree structure: other Handler
s may be invoked after ContextHandlerCollection
, for example DefaultHandler
(see this section).
Eventually, if Request.setHandled(true)
is not called, Jetty returns an HTTP 404
response to the client.
class ShopHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
// Implement the shop.
}
}
class RESTHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
baseRequest.setHandled(true);
// Implement the REST APIs.
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a ContextHandlerCollection to hold contexts.
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
// Link the ContextHandlerCollection to the Server.
server.setHandler(contextCollection);
// Create the context for the shop web application.
ContextHandler shopContext = new ContextHandler("/shop");
shopContext.setHandler(new ShopHandler());
// Add it to ContextHandlerCollection.
contextCollection.addHandler(shopContext);
server.start();
// Create the context for the API web application.
ContextHandler apiContext = new ContextHandler("/api");
apiContext.setHandler(new RESTHandler());
// Web applications can be deployed after the Server is started.
contextCollection.deployHandler(apiContext, Callback.NOOP);
The Handler
tree structure looks like the following:
Server
└── ContextHandlerCollection
├── ContextHandler /shop
│ └── ShopHandler
└── ContextHandler /api
└── RESTHandler
ResourceHandler — Static Content
Static content such as images or files (HTML, JavaScript, CSS) can be sent by Jetty very efficiently because Jetty can write the content asynchronously, using direct ByteBuffer
s to minimize data copy, and using a memory cache for faster access to the data to send.
Being able to write content asynchronously means that if the network gets congested (for example, the client reads the content very slowly) and the server stalls the send of the requested data, then Jetty will wait to resume the send without blocking a thread to finish the send.
ResourceHandler
supports the following features:
-
Welcome files, for example serving
/index.html
for request URI/
-
Precompressed resources, serving a precompressed
/document.txt.gz
for request URI/document.txt
-
Range requests, for requests containing the
Range
header, which allows clients to pause and resume downloads of large files -
Directory listing, serving a HTML page with the file list of the requested directory
-
Conditional headers, for requests containing the
If-Match
,If-None-Match
,If-Modified-Since
,If-Unmodified-Since
headers.
The number of features supported and the efficiency in sending static content are on the same level as those of common front-end servers used to serve static content such as Nginx or Apache. Therefore, the traditional architecture where Nginx/Apache was the front-end server used only to send static content and Jetty was the back-end server used only to send dynamic content is somehow obsolete as Jetty can perform efficiently both tasks. This leads to simpler systems (less components to configure and manage) and more performance (no need to proxy dynamic requests from front-end servers to back-end servers).
It is common to use Nginx/Apache as load balancers, or as rewrite/redirect servers. We typically recommend HAProxy as load balancer, and Jetty has rewrite/redirect features as well. |
This is how you configure a ResourceHandler
to create a simple file server:
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create and configure a ResourceHandler.
ResourceHandler handler = new ResourceHandler();
// Configure the directory where static resources are located.
handler.setBaseResource(Resource.newResource("/path/to/static/resources/"));
// Configure directory listing.
handler.setDirectoriesListed(false);
// Configure welcome files.
handler.setWelcomeFiles(new String[]{"index.html"});
// Configure whether to accept range requests.
handler.setAcceptRanges(true);
// Link the context to the server.
server.setHandler(handler);
server.start();
If you need to serve static resources from multiple directories:
ResourceHandler handler = new ResourceHandler();
// For multiple directories, use ResourceCollection.
ResourceCollection directories = new ResourceCollection();
directories.addPath("/path/to/static/resources/");
directories.addPath("/another/path/to/static/resources/");
handler.setBaseResource(directories);
If the resource is not found, ResourceHandler
will not call Request.setHandled(true)
so what happens next depends on the Handler
tree structure.
See also how to use DefaultHandler
.
GzipHandler
GzipHandler
provides supports for automatic decompression of compressed request content and automatic compression of response content.
GzipHandler
is a HandlerWrapper
that inspects the request and, if the request matches the GzipHandler
configuration, just installs the required components to eventually perform decompression of the request content or compression of the response content.
The decompression/compression is not performed until the web application reads request content or writes response content.
GzipHandler
can be configured at the server level in this way:
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create and configure GzipHandler.
GzipHandler gzipHandler = new GzipHandler();
// Only compress response content larger than this.
gzipHandler.setMinGzipSize(1024);
// Do not compress these URI paths.
gzipHandler.setExcludedPaths("/uncompressed");
// Also compress POST responses.
gzipHandler.addIncludedMethods("POST");
// Do not compress these mime types.
gzipHandler.addExcludedMimeTypes("font/ttf");
// Link a ContextHandlerCollection to manage contexts.
ContextHandlerCollection contexts = new ContextHandlerCollection();
gzipHandler.setHandler(contexts);
// Link the GzipHandler to the Server.
server.setHandler(gzipHandler);
server.start();
The Handler
tree structure looks like the following:
Server
└── GzipHandler
└── ContextHandlerCollection
├── ContextHandler 1
:── ...
└── ContextHandler N
However, in less common cases, you can configure GzipHandler
on a per-context basis, for example because you want to configure GzipHandler
with different parameters for each context, or because you want only some contexts to have compression support:
// Create a ContextHandlerCollection to hold contexts.
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
// Link the ContextHandlerCollection to the Server.
server.setHandler(contextCollection);
// Create the context for the shop web application.
ContextHandler shopContext = new ContextHandler("/shop");
shopContext.setHandler(new ShopHandler());
// You want to gzip the shop web application only.
GzipHandler shopGzipHandler = new GzipHandler();
shopGzipHandler.setHandler(shopContext);
// Add it to ContextHandlerCollection.
contextCollection.addHandler(shopGzipHandler);
// Create the context for the API web application.
ContextHandler apiContext = new ContextHandler("/api");
apiContext.setHandler(new RESTHandler());
// Add it to ContextHandlerCollection.
contextCollection.addHandler(apiContext);
The Handler
tree structure looks like the following:
Server
└── ContextHandlerCollection
└── ContextHandlerCollection
├── GzipHandler
│ └── ContextHandler /shop
│ └── ShopHandler
└── ContextHandler /api
└── RESTHandler
RewriteHandler
RewriteHandler
provides support for URL rewriting, very similarly to Apache’s mod_rewrite or Nginx rewrite module.
The Maven artifact coordinates are:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-rewrite</artifactId>
<version>11.0.24-SNAPSHOT</version>
</dependency>
RewriteHandler
can be configured with a set of rules; a rule inspects the request and when it matches it performs some change to the request (for example, changes the URI path, adds/removes headers, etc.).
The Jetty Server Libraries provide rules for the most common usages, but you can write your own rules by extending the org.eclipse.jetty.rewrite.handler.Rule
class.
Please refer to the jetty-rewrite
module javadocs for the complete list of available rules.
You typically want to configure RewriteHandler
at the server level, although it is possible to configure it on a per-context basis.
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
RewriteHandler rewriteHandler = new RewriteHandler();
// Compacts URI paths with double slashes, e.g. /ctx//path/to//resource.
rewriteHandler.addRule(new CompactPathRule());
// Rewrites */products/* to */p/*.
rewriteHandler.addRule(new RewriteRegexRule("/(.*)/product/(.*)", "/$1/p/$2"));
// Redirects permanently to a different URI.
RedirectRegexRule redirectRule = new RedirectRegexRule("/documentation/(.*)", "https://docs.domain.com/$1");
redirectRule.setStatusCode(HttpStatus.MOVED_PERMANENTLY_301);
rewriteHandler.addRule(redirectRule);
// Link the RewriteHandler to the Server.
server.setHandler(rewriteHandler);
// Create a ContextHandlerCollection to hold contexts.
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
// Link the ContextHandlerCollection to the RewriteHandler.
rewriteHandler.setHandler(contextCollection);
server.start();
The Handler
tree structure looks like the following:
Server
└── RewriteHandler
└── ContextHandlerCollection
├── ContextHandler 1
:── ...
└── ContextHandler N
StatisticsHandler
StatisticsHandler
gathers and exposes a number of statistic values related to request processing such as:
-
Total number of requests
-
Current number of concurrent requests
-
Minimum, maximum, average and standard deviation of request processing times
-
Number of responses grouped by HTTP code (i.e. how many
2xx
responses, how many3xx
responses, etc.) -
Total response content bytes
Server applications can read these values and use them internally, or expose them via some service, or export them to JMX.
StatisticsHandler
can be configured at the server level or at the context level.
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
server.addConnector(connector);
StatisticsHandler statsHandler = new StatisticsHandler();
// Link the StatisticsHandler to the Server.
server.setHandler(statsHandler);
// Create a ContextHandlerCollection to hold contexts.
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
// Link the ContextHandlerCollection to the StatisticsHandler.
statsHandler.setHandler(contextCollection);
server.start();
The Handler
tree structure looks like the following:
Server
└── StatisticsHandler
└── ContextHandlerCollection
├── ContextHandler 1
:── ...
└── ContextHandler N
SecuredRedirectHandler — Redirect from HTTP to HTTPS
SecuredRedirectHandler
allows to redirect requests made with the http
scheme (and therefore to the clear-text port) to the https
scheme (and therefore to the encrypted port).
For example a request to http://domain.com:8080/path?param=value
is redirected to https://domain.com:8443/path?param=value
.
Server applications must configure a HttpConfiguration
object with the secure scheme and secure port so that SecuredRedirectHandler
can build the redirect URI.
SecuredRedirectHandler
is typically configured at the server level, although it can be configured on a per-context basis.
Server server = new Server();
// Configure the HttpConfiguration for the clear-text connector.
int securePort = 8443;
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.setSecurePort(securePort);
// The clear-text connector.
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
connector.setPort(8080);
server.addConnector(connector);
// Configure the HttpConfiguration for the encrypted connector.
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
// Add the SecureRequestCustomizer because we are using TLS.
httpConfig.addCustomizer(new SecureRequestCustomizer());
// The HttpConnectionFactory for the encrypted connector.
HttpConnectionFactory http11 = new HttpConnectionFactory(httpsConfig);
// Configure the SslContextFactory with the keyStore information.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");
// The ConnectionFactory for TLS.
SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol());
// The encrypted connector.
ServerConnector secureConnector = new ServerConnector(server, tls, http11);
secureConnector.setPort(8443);
server.addConnector(secureConnector);
SecuredRedirectHandler securedHandler = new SecuredRedirectHandler();
// Link the SecuredRedirectHandler to the Server.
server.setHandler(securedHandler);
// Create a ContextHandlerCollection to hold contexts.
ContextHandlerCollection contextCollection = new ContextHandlerCollection();
// Link the ContextHandlerCollection to the StatisticsHandler.
securedHandler.setHandler(contextCollection);
server.start();
DefaultHandler
DefaultHandler
is a terminal Handler
that always calls Request.setHandled(true)
and performs the following:
-
Serves the
favicon.ico
Jetty icon when it is requested -
Sends a HTTP
404
response for any other request -
The HTTP
404
response content nicely shows a HTML table with all the contexts deployed on theServer
instance
DefaultHandler
is best used as the last Handler
of a HandlerList
, for example:
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a HandlerList.
HandlerList handlerList = new HandlerList();
// Add as first a ContextHandlerCollection to manage contexts.
ContextHandlerCollection contexts = new ContextHandlerCollection();
handlerList.addHandler(contexts);
// Add as last a DefaultHandler.
DefaultHandler defaultHandler = new DefaultHandler();
handlerList.addHandler(defaultHandler);
// Link the HandlerList to the Server.
server.setHandler(handlerList);
server.start();
The Handler
tree structure looks like the following:
Server
└── HandlerList
├── ContextHandlerCollection
│ ├── ContextHandler 1
│ :── ...
│ └── ContextHandler N
└── DefaultHandler
In the example above, ContextHandlerCollection
will try to match a request to one of the contexts; if the match fails, HandlerList
will call the next Handler
which is DefaultHandler
that will return a HTTP 404
with an HTML page showing the existing contexts deployed on the Server
.
DefaultHandler just sends a nicer HTTP 404 response in case of wrong requests from clients.
Jetty will send an HTTP 404 response anyway if DefaultHandler is not used.
|
Servlet API Handlers
ServletContextHandler
Handler
s are easy to write, but often web applications have already been written using the Servlet APIs, using Servlet
s and Filter
s.
ServletContextHandler
is a ContextHandler
that provides support for the Servlet APIs and implements the behaviors required by the Servlet specification.
The Maven artifact coordinates are:
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>11.0.24-SNAPSHOT</version>
</dependency>
class ShopCartServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
{
// Implement the shop cart functionality.
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a ServletContextHandler with contextPath.
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/shop");
// Add the Servlet implementing the cart functionality to the context.
ServletHolder servletHolder = context.addServlet(ShopCartServlet.class, "/cart/*");
// Configure the Servlet with init-parameters.
servletHolder.setInitParameter("maxItems", "128");
// Add the CrossOriginFilter to protect from CSRF attacks.
FilterHolder filterHolder = context.addFilter(CrossOriginFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
// Configure the filter.
filterHolder.setAsyncSupported(true);
// Link the context to the server.
server.setHandler(context);
server.start();
The Handler
and Servlet components tree structure looks like the following:
Server
└── ServletContextHandler /shop
├── ShopCartServlet /cart/*
└── CrossOriginFilter /*
Note how the Servlet components (they are not Handler
s) are represented in italic.
Note also how adding a Servlet
or a Filter
returns a holder object that can be used to specify additional configuration for that particular Servlet
or Filter
.
When a request arrives to ServletContextHandler
the request URI will be matched against the Filter
s and Servlet
mappings and only those that match will process the request, as dictated by the Servlet specification.
ServletContextHandler is a terminal Handler , that is it always calls Request.setHandled(true) when invoked.
Server applications must be careful when creating the Handler tree to put ServletContextHandler s as last Handler s in a HandlerList or as children of ContextHandlerCollection .
|
WebAppContext
WebAppContext
is a ServletContextHandler
that auto configures itself by reading a web.xml
Servlet configuration file.
Server applications can specify a *.war
file or a directory with the structure of a *.war
file to WebAppContext
to deploy a standard Servlet web application packaged as a war
(as defined by the Servlet specification).
Where server applications using ServletContextHandler
must manually invoke methods to add Servlet
s and Filter
s, WebAppContext
reads WEB-INF/web.xml
to add Servlet
s and Filter
s, and also enforces a number of restrictions defined by the Servlet specification, in particular related to class loading.
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Create a WebAppContext.
WebAppContext context = new WebAppContext();
// Configure the path of the packaged web application (file or directory).
context.setWar("/path/to/webapp.war");
// Configure the contextPath.
context.setContextPath("/app");
// Link the context to the server.
server.setHandler(context);
server.start();
The Servlet specification requires that a web application class loader must load the web application classes from WEB-INF/classes
and WEB_INF/lib
.
The web application class loader is special because it behaves differently from typical class loaders: where typical class loaders first delegate to their parent class loader and then try to find the class locally, the web application class loader first tries to find the class locally and then delegates to the parent class loader.
The typical class loading model, parent-first, is inverted for web application class loaders, as they use a child-first model.
Furthermore, the Servlet specification requires that web applications cannot load or otherwise access the Servlet container implementation classes, also called server classes.
In the Jetty case, the Servlet specification class javax.servlet.http.HttpServletRequest
is implemented by org.eclipse.jetty.server.Request
.
Web applications cannot downcast Servlet’s HttpServletRequest
to Jetty’s Request
to access Jetty specific features — this ensures maximum web application portability across Servlet container implementations.
Lastly, the Servlet specification requires that other classes, also called system classes, such as javax.servlet.http.HttpServletRequest
or JDK classes such as java.lang.String
or java.sql.Connection
cannot be modified by web applications by putting, for example, modified versions of those classes in WEB-INF/classes
so that they are loaded first by the web application class loader (instead of the class-path class loader where they are normally loaded from).
WebAppContext
implements this class loader logic using a single class loader, org.eclipse.jetty.webapp.WebAppClassLoader
, with filtering capabilities: when it loads a class, it checks whether the class is a system class or a server class and acts according to the Servlet specification.
When WebAppClassLoader
is asked to load a class, it first tries to find the class locally (since it must use the inverted child-first model); if the class is found, and it is not a system class, the class is loaded; otherwise the class is not found locally.
If the class is not found locally, the parent class loader is asked to load the class; the parent class loader uses the standard parent-first model, so it delegates the class loading to its parent, and so on.
If the class is found, and it is not a server class, the class is loaded; otherwise the class is not found and a ClassNotFoundException
is thrown.
Unfortunately, the Servlet specification does not define exactly which classes are system classes and which classes are server classes.
However, Jetty picks good defaults and allows server applications to customize system classes and server classes in WebAppContext
.
DefaultServlet — Static Content for Servlets
If you have a Servlet web application, you may want to use a DefaultServlet
instead of ResourceHandler
.
The features are similar, but DefaultServlet
is more commonly used to serve static files for Servlet web applications.
// Create a ServletContextHandler with contextPath.
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/app");
// Add the DefaultServlet to serve static content.
ServletHolder servletHolder = context.addServlet(DefaultServlet.class, "/");
// Configure the DefaultServlet with init-parameters.
servletHolder.setInitParameter("resourceBase", "/path/to/static/resources/");
servletHolder.setAsyncSupported(true);
Implementing Handler
The Handler
API consist fundamentally of just one method:
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
}
The target
parameter is an identifier for the resource.
This is normally the URI that is parsed from an HTTP request.
However, a request could be forwarded to either a named resource, in which case target
will be the name of the resource, or to a different URI, in which case target
will be the new URI.
Applications may wrap the request or response (or both) and forward the wrapped request or response to a different URI (which may be possibly handled by a different Handler
).
This is the reason why there are two request parameters in the Handler
APIs: the first is the unwrapped, original, request that also gives access to Jetty-specific APIs, while the second is the application-wrapped Servlet request.
Hello World Handler
A simple "Hello World" Handler
is the following:
class HelloWorldHandler extends AbstractHandler
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
// Mark the request as handled by this Handler.
jettyRequest.setHandled(true);
response.setStatus(200);
response.setContentType("text/html; charset=UTF-8");
// Write a Hello World response.
response.getWriter().print("" +
"<!DOCTYPE html>" +
"<html>" +
"<head>" +
" <title>Jetty Hello World Handler</title>" +
"</head>" +
"<body>" +
" <p>Hello World</p>" +
"</body>" +
"</html>" +
"");
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Set the Hello World Handler.
server.setHandler(new HelloWorldHandler());
server.start();
Such a simple Handler
extends from AbstractHandler
and can access the request and response main features, such as reading request headers and content, or writing response headers and content.
Filtering Handler
A filtering Handler
is a handler that perform some modification to the request or response, and then either forwards the request to another Handler
or produces an error response:
class FilterHandler extends HandlerWrapper
{
@Override
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
String path = request.getRequestURI();
if (path.startsWith("/old_path/"))
{
// Rewrite old paths to new paths.
HttpURI uri = jettyRequest.getHttpURI();
String newPath = "/new_path/" + path.substring("/old_path/".length());
HttpURI newURI = HttpURI.build(uri).path(newPath);
// Modify the request object.
jettyRequest.setHttpURI(newURI);
}
// This Handler is not handling the request, so
// it does not call jettyRequest.setHandled(true).
// Forward to the next Handler.
super.handle(target, jettyRequest, request, response);
}
}
Server server = new Server();
Connector connector = new ServerConnector(server);
server.addConnector(connector);
// Link the Handlers.
FilterHandler filter = new FilterHandler();
filter.setHandler(new HelloWorldHandler());
server.setHandler(filter);
server.start();
Note how a filtering Handler
extends from HandlerWrapper
and as such needs another handler to forward the request processing to, and how the two Handler
s needs to be linked together to work properly.
Writing HTTP Server Applications
Writing HTTP applications is typically simple, especially when using blocking APIs. However, there are subtle cases where it is worth clarifying what a server application should do to obtain the desired results when run by Jetty.
Sending 1xx Responses
The HTTP/1.1 RFC allows for 1xx
informational responses to be sent before a real content response.
Unfortunately the servlet specification does not provide a way for these to be sent, so Jetty has had to provide non-standard handling of these headers.
100 Continue
The 100 Continue
response should be sent by the server when a client sends a request with an Expect: 100-continue
header, as the client will not send the body of the request until the 100 Continue
response has been sent.
The intent of this feature is to allow a server to inspect the headers and to tell the client to not send a request body that might be too large or insufficiently private or otherwise unable to be handled.
Jetty achieves this by waiting until the input stream or reader is obtained by the filter/servlet, before sending the 100 Continue
response.
Thus a filter/servlet may inspect the headers of a request before getting the input stream and send an error response (or redirect etc.) rather than the 100 continues.
class Continue100HttpServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
{
// Inspect the method and headers.
boolean isPost = HttpMethod.POST.is(request.getMethod());
boolean expects100 = HttpHeaderValue.CONTINUE.is(request.getHeader("Expect"));
long contentLength = request.getContentLengthLong();
if (isPost && expects100)
{
if (contentLength > 1024 * 1024)
{
// Rejects uploads that are too large.
response.sendError(HttpStatus.PAYLOAD_TOO_LARGE_413);
}
else
{
// Getting the request InputStream indicates that
// the application wants to read the request content.
// Jetty will send the 100 Continue response at this
// point, and the client will send the request content.
ServletInputStream input = request.getInputStream();
// Read and process the request input.
}
}
else
{
// Process normal requests.
}
}
}
102 Processing
RFC 2518 defined the 102 Processing
status code that can be sent:
when the server has a reasonable expectation that the request will take significant time to complete.
As guidance, if a method is taking longer than 20 seconds (a reasonable, but arbitrary value) to process the server SHOULD return a 102 Processing
response.
However, a later update of RFC 2518, RFC 4918, removed the 102 Processing
status code for "lack of implementation".
Jetty supports the 102 Processing
status code.
If a request is received with the Expect: 102-processing
header, then a filter/servlet may send a 102 Processing
response (without terminating further processing) by calling response.sendError(102)
.