Skip to main content
Embedded Guide

Client Libraries

The Eclipse Jetty Project provides also provides client-side libraries that allow you to embed a client in your applications. A typical example is a client application that needs to contact a third party service via HTTP (for example a REST service). Another example is a proxy application that receives HTTP requests and forwards them as FCGI requests to a PHP application such as WordPress, or receives HTTP/1.1 requests and converts them to HTTP/2. Yet another example is a client application that needs to received events from a WebSocket server.

The client libraries are designed to be non-blocking and offer both synchronous and asynchronous APIs and come with a large number of configuration options.

These are the available client libraries:

If you are interested in the low-level details of how the Eclipse Jetty client libraries work, or are interested in writing a custom protocol, look at the Client I/O Architecture.

HTTP Client

HttpClient Introduction

The Jetty HTTP client module provides easy-to-use APIs and utility classes to perform HTTP (or HTTPS) requests.

Jetty’s HTTP client is non-blocking and asynchronous. It offers an asynchronous API that never blocks for I/O, making it very efficient in thread utilization and well suited for high performance scenarios such as load testing or parallel computation.

However, when all you need to do is to perform a GET request to a resource, Jetty’s HTTP client offers also a synchronous API; a programming interface where the thread that issued the request blocks until the request/response conversation is complete.

Jetty’s HTTP client supports different transports: HTTP/1.1, FastCGI and HTTP/2. This means that the semantic of a HTTP request (that is, " GET me the resource /index.html ") can be carried over the network in different formats. The most common and default format is HTTP/1.1. That said, Jetty’s HTTP client can carry the same request using the FastCGI format or the HTTP/2 format.

The FastCGI transport is heavily used in Jetty’s FastCGI support that allows Jetty to work as a reverse proxy to PHP (exactly like Apache or Nginx do) and therefore be able to serve - for example - WordPress websites.

The HTTP/2 transport allows Jetty’s HTTP client to perform requests using HTTP/2 to HTTP/2 enabled web sites, see also Jetty’s HTTP/2 support.

Out of the box features that you get with the Jetty HTTP client include:

  • Redirect support - redirect codes such as 302 or 303 are automatically followed.

  • Cookies support - cookies sent by servers are stored and sent back to servers in matching requests.

  • Authentication support - HTTP "Basic" and "Digest" authentications are supported, others are pluggable.

  • Forward proxy support - HTTP proxying and SOCKS4 proxying.

Starting HttpClient

The Jetty artifact that provides the main HTTP client implementation is jetty-client. The Maven artifact coordinates are the following:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-client</artifactId>
  <version>10.0.0.beta1</version>
</dependency>

The main class is named org.eclipse.jetty.client.HttpClient.

You can think of a HttpClient instance as a browser instance. Like a browser it can make requests to different domains, it manages redirects, cookies and authentication, you can configure it with a proxy, and it provides you with the responses to the requests you make.

In order to use HttpClient, you must instantiate it, configure it, and then start it:

// Instantiate HttpClient.
HttpClient httpClient = new HttpClient();

// Configure HttpClient, for example:
httpClient.setFollowRedirects(false);

// Start HttpClient.
httpClient.start();

You may create multiple instances of HttpClient, but typically one instance is enough for an application. There are several reasons for having multiple HttpClient instances including, but not limited to:

  • You want to specify different configuration parameters (for example, one instance is configured with a forward proxy while another is not).

  • You want the two instances to behave like two different browsers and hence have different cookies, different authentication credentials, etc.

  • You want to use different transports.

Like browsers, HTTPS requests are supported out-of-the-box, as long as the server provides a valid certificate. In case the server does not provide a valid certificate (or in case it is self-signed) you want to customize HttpClient's TLS configuration as described in this section.

Stopping HttpClient

It is recommended that when your application stops, you also stop the HttpClient instance (or instances) that you are using.

// Stop HttpClient.
httpClient.stop();

Stopping HttpClient makes sure that the memory it holds (for example, authentication credentials, cookies, etc.) is released, and that the thread pool and scheduler are properly stopped allowing all threads used by HttpClient to exit.

HttpClient Architecture

A HttpClient instance can be thought as a browser instance, and it manages the following components:

A destination is the client-side component that represent an origin on a server, and manages a queue of requests for that origin, and a pool of connections to that origin.

An origin may be simply thought as the tuple (scheme, host, port) and it is where the client connects to in order to communicate with the server. However, this is not enough.

If you use HttpClient to write a proxy you may have different clients that want to contact the same server. In this case, you may not want to use the same proxy-to-server connection to proxy requests for both clients, for example for authentication reasons: the server may associate the connection with authentication credentials and you do not want to use the same connection for two different users that have different credentials. Instead, you want to use different connections for different clients and this can be achieved by "tagging" a destination with a tag object that represents the remote client (for example, it could be the remote client IP address).

Two origins with the same (scheme, host, port) but different tag create two different destinations and therefore two different connection pools. However, also this is not enough.

It is possible that a server speaks different protocols on the same port. A connection may start by speaking one protocol, for example HTTP/1.1, but then be upgraded to speak a different protocol, for example HTTP/2. After a connection has been upgraded to a second protocol, it cannot speak the first protocol anymore, so it can only be used to communicate using the second protocol.

Two origins with the same (scheme, host, port) but different protocol create two different destinations and therefore two different connection pools.

Therefore an origin is identified by the tuple (scheme, host, port, tag, protocol).

HttpClient Connection Pooling

A destination manages a org.eclipse.jetty.client.ConnectionPool, where connections to a particular origin are pooled for performance reasons: opening a connection is a costly operation and it’s better to reuse them for multiple requests.

Remember that to select a specific destination you must select a specific origin, and that an origin is identified by the tuple (scheme, host, port, tag, protocol), so you can have multiple destinations for the same host and port.

You can access the ConnectionPool in this way:

HttpClient httpClient = new HttpClient();
httpClient.start();

ConnectionPool connectionPool = httpClient.getDestinations().stream()
    // Cast to HttpDestination.
    .map(HttpDestination.class::cast)
    // Find the destination by filtering on the Origin.
    .filter(destination -> destination.getOrigin().getAddress().getHost().equals("domain.com"))
    .findAny()
    // Get the ConnectionPool.
    .map(HttpDestination::getConnectionPool)
    .orElse(null);

Jetty’s client library provides the following ConnectionPool implementations:

  • DuplexConnectionPool, historically the first implementation, only used by the HTTP/1.1 transport.

  • MultiplexConnectionPool, the generic implementation valid for any transport where connections are reused with a MRU (most recently used) algorithm (that is, the connections most recently returned to the connection pool are the more likely to be used again).

  • RoundRobinConnectionPool, similar to MultiplexConnectionPool but where connections are reused with a round-robin algorithm.

The ConnectionPool implementation can be customized for each destination in by setting a ConnectionPool.Factory on the HttpClientTransport:

HttpClient httpClient = new HttpClient();
httpClient.start();

// The max number of connections in the pool.
int maxConnectionsPerDestination = httpClient.getMaxConnectionsPerDestination();

// The max number of requests per connection (multiplexing).
// Start with 1, since this value is dynamically set to larger values if
// the transport supports multiplexing requests on the same connection.
int maxRequestsPerConnection = 1;

HttpClientTransport transport = httpClient.getTransport();

// Set the ConnectionPool.Factory using a lambda.
transport.setConnectionPoolFactory(destination ->
    new RoundRobinConnectionPool(destination,
        maxConnectionsPerDestination,
        destination,
        maxRequestsPerConnection));

HttpClient Request Processing

diag 4760564aad148998e116357e8f3a5450

When a request is sent, an origin is computed from the request; HttpClient uses that origin to find (or create if it does not exist) the correspondent destination. The request is then queued onto the destination, and this causes the destination to ask its connection pool for a free connection. If a connection is available, it is returned, otherwise a new connection is created. Once the destination has obtained the connection, it dequeues the request and sends it over the connection.

The first request to a destination triggers the opening of the first connection. A second request with the same origin sent after the first request/response cycle is completed will reuse the same connection. A second request with the same origin sent concurrently with the first request will cause the opening of a second connection. The configuration parameter HttpClient.maxConnectionsPerDestination (see also the configuration section) controls the max number of connections that can be opened for a destination.

If opening connections to a given origin takes a long time, then requests for that origin will queue up in the corresponding destination.

Each connection can handle a limited number of concurrent requests. For HTTP/1.1, this number is always 1: there can only be one outstanding request for each connection. For HTTP/2 this number is determined by the server max_concurrent_stream setting (typically around 100, i.e. there can be up to 100 outstanding requests for every connection).

When a destination has maxed out its number of connections, and all connections have maxed out their number of outstanding requests, more requests sent to that destination will be queued. When the request queue is full, the request will be failed. The configuration parameter HttpClient.maxRequestsQueuedPerDestination (see also the configuration section) controls the max number of requests that can be queued for a destination.

HttpClient API Usage

HttpClient provides two types of APIs: a blocking API and a non-blocking API.

HttpClient Blocking APIs

The simpler way to perform a HTTP request is the following:

HttpClient httpClient = new HttpClient();
httpClient.start();

// Perform a simple GET and wait for the response.
ContentResponse response = httpClient.GET("http://domain.com/path?query");

The method HttpClient.GET(…​) performs a HTTP GET request to the given URI and returns a ContentResponse when the request/response conversation completes successfully.

The ContentResponse object contains the HTTP response information: status code, headers and possibly content. The content length is limited by default to 2 MiB; for larger content see [client-http-content-response].

If you want to customize the request, for example by issuing a HEAD request instead of a GET, and simulating a browser user agent, you can do it in this way:

ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
    .method(HttpMethod.HEAD)
    .agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0")
    .send();

This is a shorthand for:

Request request = httpClient.newRequest("http://domain.com/path?query");
request.method(HttpMethod.HEAD);
request.agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0");
ContentResponse response = request.send();

You first create a request object using httpClient.newRequest(…​), and then you customize it using the fluent API style (that is, a chained invocation of methods on the request object). When the request object is customized, you call request.send() that produces the ContentResponse when the request/response conversation is complete.

Simple POST requests also have a shortcut method:

ContentResponse response = httpClient.POST("http://domain.com/entity/1")
    .param("p", "value")
    .send();

The POST parameter values added via the param() method are automatically URL-encoded.

Jetty’s HttpClient automatically follows redirects, so it handles the typical web pattern POST/Redirect/GET, and the response object contains the content of the response of the GET request. Following redirects is a feature that you can enable/disable on a per-request basis or globally.

File uploads also require one line, and make use of java.nio.file classes:

ContentResponse response = httpClient.POST("http://domain.com/upload")
    .file(Paths.get("file_to_upload.txt"), "text/plain")
    .send();

It is possible to impose a total timeout for the request/response conversation using the Request.timeout(…​) method as follows:

ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
    .timeout(5, TimeUnit.SECONDS)
    .send();

In the example above, when the 5 seconds expire, the request is aborted and a java.util.concurrent.TimeoutException is thrown.

HttpClient Non-Blocking APIs

So far we have shown how to use Jetty HTTP client in a blocking style - that is, the thread that issues the request blocks until the request/response conversation is complete.

This section will look at Jetty’s HttpClient non-blocking, asynchronous APIs that are perfectly suited for large content downloads, for parallel processing of requests/responses and in cases where performance and efficient thread and resource utilization is a key factor.

The asynchronous APIs rely heavily on listeners that are invoked at various stages of request and response processing. These listeners are implemented by applications and may perform any kind of logic. The implementation invokes these listeners in the same thread that is used to process the request or response. Therefore, if the application code in these listeners takes a long time to execute, the request or response processing is delayed until the listener returns.

If you need to execute application code that takes long time inside a listener, you must spawn your own thread.

Request and response processing are executed by two different threads and therefore may happen concurrently. A typical example of this concurrent processing is an echo server, where a large upload may be concurrent with the large download echoed back. As a side note, remember that responses may be processed and completed before requests; a typical example is a large upload that triggers a quick response - for example an error - by the server: the response may arrive and be completed while the request content is still being uploaded.

The application thread that calls Request.send(Response.CompleteListener) performs the processing of the request until either the request is fully processed or until it would block on I/O, then it returns (and therefore never blocks). If it would block on I/O, the thread asks the I/O system to emit an event when the I/O will be ready to continue, then returns. When such an event is fired, a thread taken from the HttpClient thread pool will resume the processing of the request.

Response are processed from the I/O thread that fires the event that bytes are ready to be read. Response processing continues until either the response is fully processed or until it would block for I/O. If it would block for I/O, the thread asks the I/O system to emit an event when the I/O will be ready to continue, then returns. When such an event is fired, a thread taken from the HttpClient thread pool will resume the processing of the response.

When the request and the response are both fully processed, the thread that finished the last processing (usually the thread that processes the response, but may also be the thread that processes the request - if the request takes more time than the response to be processed) is used to dequeue the next request for the same destination and processes it.

A simple non-blocking GET request that discards the response content can be written in this way:

httpClient.newRequest("http://domain.com/path")
    .send(result ->
    {
        // Your logic here
    });

Method Request.send(Response.CompleteListener) returns void and does not block; the Response.CompleteListener lambda provided as a parameter is notified when the request/response conversation is complete, and the Result parameter allows you to access the request and response objects as well as failures, if any.

You can impose a total timeout for the request/response conversation in the same way used by the synchronous API:

httpClient.newRequest("http://domain.com/path")
    .timeout(3, TimeUnit.SECONDS)
    .send(result ->
    {
        /* Your logic here */
    });

The example above will impose a total timeout of 3 seconds on the request/response conversation.

The HTTP client APIs use listeners extensively to provide hooks for all possible request and response events:

httpClient.newRequest("http://domain.com/path")
    // Add request hooks.
    .onRequestQueued(request -> { /* ... */ })
    .onRequestBegin(request -> { /* ... */ })
    .onRequestHeaders(request -> { /* ... */ })
    .onRequestCommit(request -> { /* ... */ })
    .onRequestContent((request, content) -> { /* ... */ })
    .onRequestFailure((request, failure) -> { /* ... */ })
    .onRequestSuccess(request -> { /* ... */ })
    // Add response hooks.
    .onResponseBegin(response -> { /* ... */ })
    .onResponseHeader((response, field) -> true)
    .onResponseHeaders(response -> { /* ... */ })
    .onResponseContentAsync((response, buffer, callback) -> callback.succeeded())
    .onResponseFailure((response, failure) -> { /* ... */ })
    .onResponseSuccess(response -> { /* ... */ })
    // Result hook.
    .send(result -> { /* ... */ });

This makes Jetty HTTP client suitable for HTTP load testing because, for example, you can accurately time every step of the request/response conversation (thus knowing where the request/response time is really spent).

Have a look at the Request.Listener class to know about request events, and to the Response.Listener class to know about response events.

HttpClient Content Handling

Request Content Handling

Jetty’s HttpClient provides a number of utility classes off the shelf to handle request content.

You can provide request content as String, byte[], ByteBuffer, java.nio.file.Path, InputStream, and provide your own implementation of org.eclipse.jetty.client.api.Request.Content. Here’s an example that provides the request content using java.nio.file.Paths:

ContentResponse response = httpClient.POST("http://domain.com/upload")
    .body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt")))
    .send();

Alternatively, you can use FileInputStream via the InputStreamRequestContent utility class:

ContentResponse response = httpClient.POST("http://domain.com/upload")
    .body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt")))
    .send();

Since InputStream is blocking, then also the send of the request will block if the input stream blocks, even in case of usage of the non-blocking HttpClient APIs.

If you have already read the content in memory, you can pass it as a byte[] (or a String) using the BytesRequestContent (or StringRequestContent) utility class:

ContentResponse bytesResponse = httpClient.POST("http://domain.com/upload")
    .body(new BytesRequestContent("text/plain", bytes))
    .send();

ContentResponse stringResponse = httpClient.POST("http://domain.com/upload")
    .body(new StringRequestContent("text/plain", string))
    .send();

If the request content is not immediately available, but your application will be notified of the content to send, you can use AsyncRequestContent in this way:

AsyncRequestContent content = new AsyncRequestContent();
httpClient.POST("http://domain.com/upload")
    .body(content)
    .send(result ->
    {
        // Your logic here
    });

// Content not available yet here.

// An event happens in some other class, in some other thread.
class ContentPublisher
{
    void publish(ByteBufferPool bufferPool, byte[] bytes, boolean lastContent)
    {
        // Wrap the bytes into a new ByteBuffer.
        ByteBuffer buffer = ByteBuffer.wrap(bytes);

        // Offer the content, and release the ByteBuffer
        // to the pool when the Callback is completed.
        content.offer(buffer, Callback.from(() -> bufferPool.release(buffer)));

        // Close AsyncRequestContent when all the content is arrived.
        if (lastContent)
            content.close();
    }
}

While the request content is awaited and consequently uploaded by the client application, the server may be able to respond (at least with the response headers) completely asynchronously. In this case, Response.Listener callbacks will be invoked before the request is fully sent. This allows fine-grained control of the request/response conversation: for example the server may reject contents that are too big, send a response to the client, which in turn may stop the content upload.

Another way to provide request content is by using an OutputStreamRequestContent, which allows applications to write request content when it is available to the OutputStream provided by OutputStreamRequestContent:

OutputStreamRequestContent content = new OutputStreamRequestContent();

// Use try-with-resources to close the OutputStream when all content is written.
try (OutputStream output = content.getOutputStream())
{
    httpClient.POST("http://localhost:8080/")
        .body(content)
        .send(result ->
        {
            // Your logic here
        });

    // Content not available yet here.

    // Content is now available.
    byte[] bytes = new byte[]{'h', 'e', 'l', 'l', 'o'};
    output.write(bytes);
}
// End of try-with-resource, output.close() called automatically to signal end of content.
Response Content Handling

Jetty’s HttpClient allows applications to handle response content in different ways.

You can buffer the response content in memory; this is done when using the blocking APIs and the content is buffered within a ContentResponse up to 2 MiB.

If you want to control the length of the response content (for example limiting to values smaller than the default of 2 MiB), then you can use a org.eclipse.jetty.client.util.FutureResponseListener in this way:

Request request = httpClient.newRequest("http://domain.com/path");

// Limit response content buffer to 512 KiB.
FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024);

request.send(listener);

// Wait at most 5 seconds for request+response to complete.
ContentResponse response = listener.get(5, TimeUnit.SECONDS);

If the response content length is exceeded, the response will be aborted, and an exception will be thrown by method get(…​).

You can buffer the response content in memory also using the non-blocking APIs, via the BufferingResponseListener utility class:

httpClient.newRequest("http://domain.com/path")
    // Buffer response content up to 8 MiB
    .send(new BufferingResponseListener(8 * 1024 * 1024)
    {
        @Override
        public void onComplete(Result result)
        {
            if (!result.isFailed())
            {
                byte[] responseContent = getContent();
                // Your logic here
            }
        }
    });

If you want to avoid buffering, you can wait for the response and then stream the content using the InputStreamResponseListener utility class:

InputStreamResponseListener listener = new InputStreamResponseListener();
httpClient.newRequest("http://domain.com/path")
    .send(listener);

// Wait for the response headers to arrive.
Response response = listener.get(5, TimeUnit.SECONDS);

// Look at the response before streaming the content.
if (response.getStatus() == HttpStatus.OK_200)
{
    // Use try-with-resources to close input stream.
    try (InputStream responseContent = listener.getInputStream())
    {
        // Your logic here
    }
}
else
{
    response.abort(new IOException("Unexpected HTTP response"));
}

Finally, let’s look at the advanced usage of the response content handling.

The response content is provided by the HttpClient implementation to application listeners following a reactive model similar to that of java.util.concurrent.Flow.

The listener that follows this model is Response.DemandedContentListener.

After the response headers have been processed by the HttpClient implementation, Response.DemandedContentListener.onBeforeContent(response, demand) is invoked. This allows the application to control whether to demand the first content or not. The default implementation of this method calls demand.accept(1), which demands one chunk of content to the implementation. The implementation will deliver the chunk of content as soon as it is available.

The chunks of content are delivered to the application by invoking Response.DemandedContentListener.onContent(response, demand, buffer, callback). Applications implement this method to process the content bytes in the buffer. Succeeding the callback signals to the implementation that the application has consumed the buffer so that the implementation can dispose/recycle the buffer. Failing the callback signals to the implementation to fail the response (no more content will be delivered, and the response failed event will be emitted).

Succeeding the callback must be done only after the buffer bytes have been consumed. When the callback is succeeded, the HttpClient implementation may reuse the buffer and overwrite the bytes with different bytes; if the application looks at the buffer after having succeeded the callback is may see other, unrelated, bytes.

The application uses the demand object to demand more content chunks. Applications will typically demand for just one more content via demand.accept(1), but may decide to demand for more via demand.accept(2) or demand "infinitely" once via demand.accept(Long.MAX_VALUE). Applications that demand for more than 1 chunk of content must be prepared to receive all the content that they have demanded.

Demanding for content and consuming the content are orthogonal activities.

An application can demand "infinitely" and store aside the pairs (buffer, callback) to consume them later. If not done carefully, this may lead to excessive memory consumption, since the buffers are not consumed. Succeeding the callbacks will result in the buffers to be disposed/recycled and may be performed at any time.

An application can also demand one chunk of content, consume it (by succeeding the associated callback) and then not demand for more content until a later time.

Subclass Response.AsyncContentListener overrides the behavior of Response.DemandedContentListener; when an application implementing its onContent(response, buffer, callback) succeeds the callback, it will have both the effect of disposing/recycling the buffer and the effect of demanding one more chunk of content.

Subclass Response.ContentListener overrides the behavior of Response.AsyncContentListener; when an application implementing its onContent(response, buffer) returns from the method itself, it will both the effect of disposing/recycling the buffer and the effect of demanding one more chunk of content.

Previous examples of response content handling were inefficient because they involved copying the buffer bytes, either to accumulate them aside so that the application could use them when the request was completed, or because they were provided to an API such as InputStream that made use of byte[] (and therefore a copy from ByteBuffer to byte[] is necessary).

An application that implements a forwarder between two servers can be implemented efficiently by handling the response content without copying the buffer bytes as in the following example:

// Prepare a request to server1, the source.
Request request1 = httpClient.newRequest(host1, port1)
    .path("/source");

// Prepare a request to server2, the sink.
AsyncRequestContent content2 = new AsyncRequestContent();
Request request2 = httpClient.newRequest(host2, port2)
    .path("/sink")
    .body(content2);

request1.onResponseContentDemanded(new Response.DemandedContentListener()
{
    @Override
    public void onBeforeContent(Response response, LongConsumer demand)
    {
        request2.onRequestCommit(request ->
        {
            // Only when the request to server2 has been sent,
            // then demand response content from server1.
            demand.accept(1);
        });

        // Send the request to server2.
        request2.send(result -> System.getLogger("forwarder").log(INFO, "Forwarding to server2 complete"));
    }

    @Override
    public void onContent(Response response, LongConsumer demand, ByteBuffer content, Callback callback)
    {
        // When response content is received from server1, forward it to server2.
        content2.offer(content, Callback.from(() ->
        {
            // When the request content to server2 is sent,
            // succeed the callback to recycle the buffer.
            callback.succeeded();
            // Then demand more response content from server1.
            demand.accept(1);
        }, callback::failed));
    }
});

// When the response content from server1 is complete,
// complete also the request content to server2.
request1.onResponseSuccess(response -> content2.close());

// Send the request to server1.
request1.send(result -> System.getLogger("forwarder").log(INFO, "Sourcing from server1 complete"));

HttpClient Configuration

HttpClient has a quite large number of configuration parameters. Please refer to the HttpClient javadocs for the complete list of configurable parameters.

The most common parameters are:

  • HttpClient.idleTimeout: same as ClientConnector.idleTimeout described in this section.

  • HttpClient.connectBlocking: same as ClientConnector.connectBlocking described in this section.

  • HttpClient.connectTimeout: same as ClientConnector.connectTimeout described in this section.

  • HttpClient.maxConnectionsPerDestination: the max number of TCP connections that are opened for a particular destination (defaults to 64).

  • HttpClient.maxRequestsQueuedPerDestination: the max number of requests queued (defaults to 1024).

HttpClient TLS Configuration

HttpClient supports HTTPS requests out-of-the-box like a browser does.

The support for HTTPS request is provided by a SslContextFactory.Client, typically configured in the ClientConnector. If not explicitly configured, the ClientConnector will allocate a default one when started.

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();

ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(sslContextFactory);

HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
httpClient.start();

The default SslContextFactory.Client verifies the certificate sent by the server by verifying the certificate chain. This means that requests to public websites that have a valid certificate (such as https://google.com) will work out-of-the-box.

However, requests made to sites (typically localhost) that have an invalid (for example, expired or with a wrong host) or self-signed certificate will fail (like they will in a browser).

Certificate validation is performed at two levels: at the TLS implementation level (in the JDK) and - optionally - at the application level.

By default, certificate validation at the TLS level is enabled, while certificate validation at the application level is disabled.

You can configure the SslContextFactory.Client to skip certificate validation at the TLS level:

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
// Disable certificate validation at the TLS level.
sslContextFactory.setEndpointIdentificationAlgorithm(null);

You can enable certificate validation at the application level:

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
// Only allow subdomains of domain.com.
sslContextFactory.setHostnameVerifier((hostName, session) -> hostName.endsWith(".domain.com"));

Please refer to the SslContextFactory.Client javadocs for the complete list of configurable parameters.

Jetty’s HttpClient supports cookies out of the box. The HttpClient instance receives cookies from HTTP responses and stores them in a java.net.CookieStore, a class that is part of the JDK. When new requests are made, the cookie store is consulted and if there are matching cookies (that is, cookies that are not expired and that match domain and path of the request) then they are added to the requests.

Applications can programmatically access the cookie store to find the cookies that have been set:

CookieStore cookieStore = httpClient.getCookieStore();
List<HttpCookie> cookies = cookieStore.get(URI.create("http://domain.com/path"));

Applications can also programmatically set cookies as if they were returned from a HTTP response:

CookieStore cookieStore = httpClient.getCookieStore();
HttpCookie cookie = new HttpCookie("foo", "bar");
cookie.setDomain("domain.com");
cookie.setPath("/");
cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1));
cookieStore.add(URI.create("http://domain.com"), cookie);

Cookies may be added explicitly only for a particular request:

ContentResponse response = httpClient.newRequest("http://domain.com/path")
    .cookie(new HttpCookie("foo", "bar"))
    .send();

You can remove cookies that you do not want to be sent in future HTTP requests:

CookieStore cookieStore = httpClient.getCookieStore();
URI uri = URI.create("http://domain.com");
List<HttpCookie> cookies = cookieStore.get(uri);
for (HttpCookie cookie : cookies)
{
    cookieStore.remove(uri, cookie);
}

If you want to totally disable cookie handling, you can install a HttpCookieStore.Empty. This must be done when HttpClient is used in a proxy application, in this way:

httpClient.setCookieStore(new HttpCookieStore.Empty());

You can enable cookie filtering by installing a cookie store that performs the filtering logic in this way:

class GoogleOnlyCookieStore extends HttpCookieStore
{
    @Override
    public void add(URI uri, HttpCookie cookie)
    {
        if (uri.getHost().endsWith("google.com"))
            super.add(uri, cookie);
    }
}

httpClient.setCookieStore(new GoogleOnlyCookieStore());

The example above will retain only cookies that come from the google.com domain or sub-domains.

Special Characters in Cookies

Jetty is compliant with RFC6265, and as such care must be taken when setting a cookie value that includes special characters such as ;.

Previously, Version=1 cookies defined in RFC2109 (and continued in RFC2965) allowed for special/reserved characters to be enclosed within double quotes when declared in a Set-Cookie response header:

Set-Cookie: foo="bar;baz";Version=1;Path="/secur"

This was added to the HTTP Response as follows:

protected void service(HttpServletRequest request, HttpServletResponse response)
{
    javax.servlet.http.Cookie cookie = new Cookie("foo", "bar;baz");
    cookie.setPath("/secur");
    response.addCookie(cookie);
}

The introduction of RFC6265 has rendered this approach no longer possible; users are now required to encode cookie values that use these special characters. This can be done utilizing javax.servlet.http.Cookie as follows:

javax.servlet.http.Cookie cookie = new Cookie("foo", URLEncoder.encode("bar;baz", "UTF-8"));

Jetty validates all cookie names and values being added to the HttpServletResponse via the addCookie(Cookie) method. If an illegal value is discovered Jetty will throw an IllegalArgumentException with the details.

HttpClient Authentication Support

Jetty’s HttpClient supports the BASIC and DIGEST authentication mechanisms defined by RFC 7235, as well as the SPNEGO authentication mechanism defined in RFC 4559.

The HTTP conversation - the sequence of related HTTP requests - for a request that needs authentication is the following:

diag 136e615bb97d8356ceb17a851c86817a

Upon receiving a HTTP 401 response code, HttpClient looks at the WWW-Authenticate response header (the server challenge) and then tries to match configured authentication credentials to produce an Authentication header that contains the authentication credentials to access the resource.

You can configure authentication credentials in the HttpClient instance as follows:

// Add authentication credentials.
AuthenticationStore auth = httpClient.getAuthenticationStore();

URI uri1 = new URI("http://mydomain.com/secure");
auth.addAuthentication(new BasicAuthentication(uri1, "MyRealm", "userName1", "password1"));

URI uri2 = new URI("http://otherdomain.com/admin");
auth.addAuthentication(new BasicAuthentication(uri1, "AdminRealm", "admin", "password"));

Authentications are matched against the server challenge first by mechanism (e.g. BASIC or DIGEST), then by realm and then by URI.

If an Authentication match is found, the application does not receive events related to the HTTP 401 response. These events are handled internally by HttpClient which produces another (internal) request similar to the original request but with an additional Authorization header.

If the authentication is successful, the server responds with a HTTP 200 and HttpClient caches the Authentication.Result so that subsequent requests for a matching URI will not incur in the additional rountrip caused by the HTTP 401 response.

It is possible to clear Authentication.Results in order to force authentication again:

httpClient.getAuthenticationStore().clearAuthenticationResults();

Authentication results may be preempted to avoid the additional roundtrip due to the server challenge in this way:

AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri = URI.create("http://domain.com/secure");
auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));

In this way, requests for the given URI are enriched immediately with the Authorization header, and the server should respond with HTTP 200 (and the resource content) rather than with the 401 and the challenge.

It is also possible to preempt the authentication for a single request only, in this way:

URI uri = URI.create("http://domain.com/secure");
Authentication.Result authn = new BasicAuthentication.BasicResult(uri, "username", "password");
Request request = httpClient.newRequest(uri);
authn.apply(request);
request.send();

See also the proxy authentication section for further information about how authentication works with HTTP proxies.

HttpClient Proxy Support

Jetty’s HttpClient can be configured to use proxies to connect to destinations.

Two types of proxies are available out of the box: a HTTP proxy (provided by class org.eclipse.jetty.client.HttpProxy) and a SOCKS 4 proxy (provided by class org.eclipse.jetty.client.Socks4Proxy). Other implementations may be written by subclassing ProxyConfiguration.Proxy.

The following is a typical configuration:

HttpProxy proxy = new HttpProxy("proxyHost", 8888);

// Do not proxy requests for localhost:8080.
proxy.getExcludedAddresses().add("localhost:8080");

// Add the new proxy to the list of proxies already registered.
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
proxyConfig.getProxies().add(proxy);

ContentResponse response = httpClient.GET("http://domain.com/path");

You specify the proxy host and proxy port, and optionally also the addresses that you do not want to be proxied, and then add the proxy configuration on the ProxyConfiguration instance.

Configured in this way, HttpClient makes requests to the HTTP proxy (for plain-text HTTP requests) or establishes a tunnel via HTTP CONNECT (for encrypted HTTPS requests).

Proxying is supported for both HTTP/1.1 and HTTP/2.

Proxy Authentication Support

Jetty’s HttpClient supports proxy authentication in the same way it supports server authentication.

In the example below, the proxy requires BASIC authentication, but the server requires DIGEST authentication, and therefore:

AuthenticationStore auth = httpClient.getAuthenticationStore();

// Proxy credentials.
URI proxyURI = new URI("http://proxy.net:8080");
auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass"));

// Server credentials.
URI serverURI = new URI("http://domain.com/secure");
auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass"));

// Proxy configuration.
ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
HttpProxy proxy = new HttpProxy("proxy.net", 8080);
proxyConfig.getProxies().add(proxy);

ContentResponse response = httpClient.newRequest(serverURI).send();

The HTTP conversation for successful authentications on both the proxy and the server is the following:

diag e7fd1b7ba54e07262bd726c9fe979363

The application does not receive events related to the responses with code 407 and 401 since they are handled internally by HttpClient.

Similarly to the authentication section, the proxy authentication result and the server authentication result can be preempted to avoid, respectively, the 407 and 401 roundtrips.

HttpClient Pluggable Transports

Jetty’s HttpClient can be configured to use different transports to carry the semantic of HTTP requests and responses.

This means that the intention of a client to request resource /index.html using the GET method can be carried over the network in different formats.

A HttpClient transport is the component that is in charge of converting a high-level, semantic, HTTP requests such as “GET` resource `/index.html” into the specific format understood by the server (for example, HTTP/2), and to convert the server response from the specific format (HTTP/2) into high-level, semantic objects that can be used by applications.

The most common protocol format is HTTP/1.1, a textual protocol with lines separated by \r\n:

GET /index.html HTTP/1.1\r\n
Host: domain.com\r\n
...
\r\n

However, the same request can be made using FastCGI, a binary protocol:

x01 x01 x00 x01 x00 x08 x00 x00
x00 x01 x01 x00 x00 x00 x00 x00
x01 x04 x00 x01 xLL xLL x00 x00
x0C x0B  D   O   C   U   M   E
 N   T   _   U   R   I   /   i
 n   d   e   x   .   h   t   m
 l
...

Similarly, HTTP/2 is a binary protocol that transports the same information in a yet different format.

A protocol may be negotiated between client and server. A request for a resource may be sent using one protocol (for example, HTTP/1.1), but the response may arrive in a different protocol (for example, HTTP/2).

HttpClient supports 3 static transports, each speaking only one protocol: HTTP/1.1, HTTP/2 and FastCGI, all of them with 2 variants: clear-text and TLS encrypted.

HttpClient also supports one dynamic transport, that can speak different protocols and can select the right protocol by negotiating it with the server or by explicit indication from applications.

Applications are typically not aware of the actual protocol being used. This allows them to write their logic against a high-level API that hides the details of the specific protocol being used over the network.

HTTP/1.1 Transport

HTTP/1.1 is the default transport.

// No transport specified, using default.
HttpClient httpClient = new HttpClient();
httpClient.start();

If you want to customize the HTTP/1.1 transport, you can explicitly configure it in this way:

// Configure HTTP/1.1 transport.
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP();
transport.setHeaderCacheSize(16384);

HttpClient client = new HttpClient(transport);
client.start();

HTTP/2 Transport

The HTTP/2 transport can be configured in this way:

// The HTTP2Client powers the HTTP/2 transport.
HTTP2Client h2Client = new HTTP2Client();
h2Client.setInitialSessionRecvWindow(64 * 1024 * 1024);

// Create and configure the HTTP/2 transport.
HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client);
transport.setUseALPN(true);

HttpClient client = new HttpClient(transport);
client.start();

HTTP2Client is the lower-level client that provides an API based on HTTP/2 concepts such as sessions, streams and frames that are specific to HTTP/2. See the HTTP/2 client section for more information.

HttpClientTransportOverHTTP2 uses HTTP2Client to format high-level semantic HTTP requests (like "GET resource /index.html") into the HTTP/2 specific format.

FastCGI Transport

The FastCGI transport can be configured in this way:

String scriptRoot = "/var/www/wordpress";
HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(scriptRoot);

HttpClient client = new HttpClient(transport);
client.start();

In order to make requests using the FastCGI transport, you need to have a FastCGI server such as PHP-FPM (see also http://php.net/manual/en/install.fpm.php).

The FastCGI transport is primarily used by Jetty’s FastCGI support to serve PHP pages (WordPress for example).

Dynamic Transport

The static transports work well if you know in advance the protocol you want to speak with the server, or if the server only supports one protocol (such as FastCGI).

With the advent of HTTP/2, however, servers are now able to support multiple protocols, at least both HTTP/1.1 and HTTP/2.

The HTTP/2 protocol is typically negotiated between client and server. This negotiation can happen via ALPN, a TLS extension that allows the client to tell the server the list of protocol that the client supports, so that the server can pick one of the client supported protocols that also the server supports; or via HTTP/1.1 upgrade by means of the Upgrade header.

Applications can configure the dynamic transport with one or more application protocols such as HTTP/1.1 or HTTP/2. The implementation will take care of using TLS for HTTPS URIs, using ALPN, negotiating protocols, upgrading from one protocol to another, etc.

By default, the dynamic transport only speaks HTTP/1.1:

// Dynamic transport speaks HTTP/1.1 by default.
HttpClientTransportDynamic transport = new HttpClientTransportDynamic();

HttpClient client = new HttpClient(transport);
client.start();

The dynamic transport can be configured with just one protocol, making it equivalent to the corresponding static transport:

ClientConnector connector = new ClientConnector();

// Equivalent to HttpClientTransportOverHTTP.
HttpClientTransportDynamic http11Transport = new HttpClientTransportDynamic(connector, HttpClientConnectionFactory.HTTP11);

// Equivalent to HttpClientTransportOverHTTP2.
HTTP2Client http2Client = new HTTP2Client(connector);
HttpClientTransportDynamic http2Transport = new HttpClientTransportDynamic(connector, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client));

The dynamic transport, however, has been implemented to support multiple transports, in particular both HTTP/1.1 and HTTP/2:

ClientConnector connector = new ClientConnector();

ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;

HTTP2Client http2Client = new HTTP2Client(connector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);

HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);

HttpClient client = new HttpClient(transport);
client.start();
The order in which the protocols are specified to HttpClientTransportDynamic indicates what is the client preference. If the protocol is negotiated via ALPN, it is the server that decides what is the protocol to use for the communication, regardless of the client preference. If the protocol is not negotiated, the client preference is honored.

Provided that the server supports both HTTP/1.1 and HTTP/2 clear-text, client applications can explicitly hint the version they want to use:

ClientConnector connector = new ClientConnector();
ClientConnectionFactory.Info http1 = HttpClientConnectionFactory.HTTP11;
HTTP2Client http2Client = new HTTP2Client(connector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
HttpClientTransportDynamic transport = new HttpClientTransportDynamic(connector, http1, http2);
HttpClient client = new HttpClient(transport);
client.start();

// The server supports both HTTP/1.1 and HTTP/2 clear-text on port 8080.

// Make a clear-text request without explicit version.
// The first protocol specified to HttpClientTransportDynamic
// is picked, in this example will be HTTP/1.1.
ContentResponse http1Response = client.newRequest("host", 8080).send();

// Make a clear-text request with explicit version.
// Clear-text HTTP/2 is used for this request.
ContentResponse http2Response = client.newRequest("host", 8080)
    // Specify the version explicitly.
    .version(HttpVersion.HTTP_2)
    .send();

// Make a clear-text upgrade request from HTTP/1.1 to HTTP/2.
// The request will start as HTTP/1.1, but the response will be HTTP/2.
ContentResponse upgradedResponse = client.newRequest("host", 8080)
    .headers(headers -> headers
        .put(HttpHeader.UPGRADE, "h2c")
        .put(HttpHeader.HTTP2_SETTINGS, "")
        .put(HttpHeader.CONNECTION, "Upgrade, HTTP2-Settings"))
    .send();

In case of TLS encrypted communication using the HTTPS scheme, things are a little more complicated.

If the client application explicitly specifies the HTTP version, then ALPN is not used on the client. By specifying the HTTP version explicitly, the client application has prior-knowledge of what HTTP version the server supports, and therefore ALPN is not needed. If the server does not support the HTTP version chosen by the client, then the communication will fail.

If the client application does not explicitly specify the HTTP version, then ALPN will be used on the client. If the server also supports ALPN, then the protocol will be negotiated via ALPN and the server will choose the protocol to use. If the server does not support ALPN, the client will try to use the first protocol configured in HttpClientTransportDynamic, and the communication may succeed or fail depending on whether the server supports the protocol chosen by the client.

HTTP/2 Client Library

In the vast majority of cases, client applications should use the generic, high-level, HTTP client library that also provides HTTP/2 support via the pluggable HTTP/2 transport or the dynamic transport.

The high-level HTTP library supports cookies, authentication, redirection, connection pooling and a number of other features that are absent in the low-level HTTP/2 library.

The HTTP/2 client library has been designed for those applications that need low-level access to HTTP/2 features such as sessions, streams and frames, and this is quite a rare use case.

See also the correspondent HTTP/2 server library.

Introducing HTTP2Client

The Maven artifact coordinates for the HTTP/2 client library are the following:

<dependency>
  <groupId>org.eclipse.jetty.http2</groupId>
  <artifactId>http2-client</artifactId>
  <version>10.0.0.beta1</version>
</dependency>

The main class is named org.eclipse.jetty.http2.client.HTTP2Client, and must be created, configured and started before use:

// Instantiate HTTP2Client.
HTTP2Client http2Client = new HTTP2Client();

// Configure HTTP2Client, for example:
http2Client.setStreamIdleTimeout(15000);

// Start HTTP2Client.
http2Client.start();

When your application stops, or otherwise does not need HTTP2Client anymore, it should stop the HTTP2Client instance (or instances) that were started:

// Stop HTTP2Client.
http2Client.stop();

HTTP2Client allows client applications to connect to an HTTP/2 server. A session represents a single TCP connection to an HTTP/2 server and is defined by class org.eclipse.jetty.http2.api.Session. A session typically has a long life - once the TCP connection is established, it remains open until it is not used anymore (and therefore it is closed by the idle timeout mechanism), until a fatal error occurs (for example, a network failure), or if one of the peers decides unilaterally to close the TCP connection.

HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent on the same TCP connection. Each request/response cycle is represented by a stream. Therefore, a single session manages multiple concurrent streams. A stream has typically a very short life compared to the session: a stream only exists for the duration of the request/response cycle and then disappears.

HTTP/2 Flow Control

The HTTP/2 protocol is flow controlled (see the specification). This means that a sender and a receiver maintain a flow control window that tracks the number of data bytes sent and received, respectively. When a sender sends data bytes, it reduces its flow control window. When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application. The application consumes the data bytes and tells back the receiver that it has consumed the data bytes. The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.

A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes.

HTTP/2 defines two flow control windows: one for each session, and one for each stream. Let’s see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes.

The sender opens a session, and then opens stream_1 on that session, and sends 80 data bytes. At this point the session flow control window is 40 bytes (120 - 80), and stream_1's flow control window is 20 bytes (100 - 80). The sender now opens stream_2 on the same session and sends 40 data bytes. At this point, the session flow control window is 0 bytes (40 - 40), while stream_2's flow control window is 60 (100 - 40). Since now the session flow control window is 0, the sender cannot send more data bytes, neither on stream_1 nor on stream_2 despite both have their stream flow control windows greater than 0.

The receiver consumes stream_2's 40 data bytes and sends a message to the sender with this information. At this point, the session flow control window is 40 (0 40), stream_1's flow control window is still 20 and stream_2's flow control window is 100 (60 40). If the sender opens stream_3 and would like to send 50 data bytes, it would only be able to send 40 because that is the maximum allowed by the session flow control window at this point.

It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a WINDOW_UPDATE frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to 0.

How a client application should handle HTTP/2 flow control is discussed in details in this section.

Connecting to the Server

The first thing an application should do is to connect to the server and obtain a Session. The following example connects to the server on a clear-text port:

// Address of the server's clear-text port.
SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);

// Connect to the server, the CompletableFuture will be
// notified when the connection is succeeded (or failed).
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());

// Block to obtain the Session.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
Session session = sessionCF.get();

The following example connects to the server on an encrypted port:

HTTP2Client http2Client = new HTTP2Client();
http2Client.start();

ClientConnector connector = http2Client.getClientConnector();

// Address of the server's encrypted port.
SocketAddress serverAddress = new InetSocketAddress("localhost", 8443);

// Connect to the server, the CompletableFuture will be
// notified when the connection is succeeded (or failed).
CompletableFuture<Session> sessionCF = http2Client.connect(connector.getSslContextFactory(), serverAddress, new Session.Listener.Adapter());

// Block to obtain the Session.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
Session session = sessionCF.get();
Applications must know in advance whether they want to connect to a clear-text or encrypted port, and pass the SslContextFactory parameter accordingly to the connect(…​) method.
Configuring the Session

The connect(…​) method takes a Session.Listener parameter. This listener’s onPreface(…​) method is invoked just before establishing the connection to the server to gather the client configuration to send to the server. Client applications can override this method to change the default configuration:

SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
http2Client.connect(serverAddress, new Session.Listener.Adapter()
{
    @Override
    public Map<Integer, Integer> onPreface(Session session)
    {
        Map<Integer, Integer> configuration = new HashMap<>();

        // Disable push from the server.
        configuration.put(SettingsFrame.ENABLE_PUSH, 0);

        // Override HTTP2Client.initialStreamRecvWindow for this session.
        configuration.put(SettingsFrame.INITIAL_WINDOW_SIZE, 1024 * 1024);

        return configuration;
    }
});

The Session.Listener is notified of session events originated by the server such as receiving a SETTINGS frame from the server, or the server closing the connection, or the client timing out the connection due to idleness. Please refer to the Session.Listener javadocs for the complete list of events.

Once a Session has been established, the communication with the server happens by exchanging frames, as specified in the HTTP/2 specification.

Sending a Request

Sending an HTTP request to the server, and receiving a response, creates a stream that encapsulates the exchange of HTTP/2 frames that compose the request and the response.

In order to send an HTTP request to the server, the client must send a HEADERS frame. HEADERS frames carry the request method, the request URI and the request headers. Sending the HEADERS frame opens the Stream:

SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
Session session = sessionCF.get();

// Configure the request headers.
HttpFields requestHeaders = HttpFields.build()
    .put(HttpHeader.USER_AGENT, "Jetty HTTP2Client 10.0.0.beta1");

// The request metadata with method, URI and headers.
MetaData.Request request = new MetaData.Request("GET", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);

// The HTTP/2 HEADERS frame, with endStream=true
// to signal that this request has no content.
HeadersFrame headersFrame = new HeadersFrame(request, null, true);

// Open a Stream by sending the HEADERS frame.
session.newStream(headersFrame, new Stream.Listener.Adapter());

Note how Session.newStream(…​) takes a Stream.Listener parameter. This listener is notified of stream events originated by the server such as receiving HEADERS or DATA frames that are part of the response, discussed in more details in the section below. Please refer to the Stream.Listener javadocs for the complete list of events.

HTTP requests may have content, which is sent using the Stream APIs:

SocketAddress serverAddress = new InetSocketAddress("localhost", 8080);
CompletableFuture<Session> sessionCF = http2Client.connect(serverAddress, new Session.Listener.Adapter());
Session session = sessionCF.get();

// Configure the request headers.
HttpFields requestHeaders = HttpFields.build()
    .put(HttpHeader.CONTENT_TYPE, "application/json");

// The request metadata with method, URI and headers.
MetaData.Request request = new MetaData.Request("POST", HttpURI.from("http://localhost:8080/path"), HttpVersion.HTTP_2, requestHeaders);

// The HTTP/2 HEADERS frame, with endStream=false to
// signal that there will be more frames in this stream.
HeadersFrame headersFrame = new HeadersFrame(request, null, false);

// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter());

// Block to obtain the Stream.
// Alternatively you can use the CompletableFuture APIs to avoid blocking.
Stream stream = streamCF.get();

// The request content, in two chunks.
String content1 = "{\"greet\": \"hello world\"}";
ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
String content2 = "{\"user\": \"jetty\"}";
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);

// Send the first DATA frame on the stream, with endStream=false
// to signal that there are more frames in this stream.
CompletableFuture<Stream> dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));

// Only when the first chunk has been sent we can send the second,
// with endStream=true to signal that there are no more frames.
dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true)));
When sending two DATA frames consecutively, the second call to Stream.data(…​) must be done only when the first is completed, or a WritePendingException will be thrown. Use the Callback APIs or CompletableFuture APIs to ensure that the second Stream.data(…​) call is performed when the first completed successfully.

Receiving a Response

Response events are delivered to the Stream.Listener passed to Session.newStream(…​).

An HTTP response is typically composed of a HEADERS frame containing the HTTP status code and the response headers, and optionally one or more DATA frames containing the response content bytes.

The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a HEADERS frame.

A client application can therefore receive the HTTP/2 frames sent by the server by implementing the relevant methods in Stream.Listener:

// Open a Stream by sending the HEADERS frame.
session.newStream(headersFrame, new Stream.Listener.Adapter()
{
    @Override
    public void onHeaders(Stream stream, HeadersFrame frame)
    {
        MetaData metaData = frame.getMetaData();

        // Is this HEADERS frame the response or the trailers?
        if (metaData.isResponse())
        {
            MetaData.Response response = (MetaData.Response)metaData;
            System.getLogger("http2").log(INFO, "Received response {0}", response);
        }
        else
        {
            System.getLogger("http2").log(INFO, "Received trailers {0}", metaData.getFields());
        }
    }

    @Override
    public void onData(Stream stream, DataFrame frame, Callback callback)
    {
        // Get the content buffer.
        ByteBuffer buffer = frame.getData();

        // Consume the buffer, here - as an example - just log it.
        System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);

        // Tell the implementation that the buffer has been consumed.
        callback.succeeded();

        // By returning from the method, implicitly tell the implementation
        // to deliver to this method more DATA frames when they are available.
    }
});
Returning from the onData(…​) method implicitly demands for more DATA frames (unless the one just delivered was the last). Additional DATA frames may be delivered immediately if they are available or later, asynchronously, when they arrive.

Applications that consume the content buffer within onData(…​) (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer. This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers.

Alternatively, a client application may store away both the buffer and the callback to consume the buffer bytes later, or pass both the buffer and the callback to another asynchronous API (this is typical in proxy applications).

Completing the Callback is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more DATA frames without stalling.

Applications can also precisely control when to demand more DATA frames, by implementing the onDataDemanded(…​) method instead of onData(…​):

class Chunk
{
    private final ByteBuffer buffer;
    private final Callback callback;

    Chunk(ByteBuffer buffer, Callback callback)
    {
        this.buffer = buffer;
        this.callback = callback;
    }
}

// A queue that consumers poll to consume content asynchronously.
Queue<Chunk> dataQueue = new ConcurrentLinkedQueue<>();

// Implementation of Stream.Listener.onDataDemanded(...)
// in case of asynchronous content consumption and demand.
Stream.Listener listener = new Stream.Listener.Adapter()
{
    @Override
    public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
    {
        // Get the content buffer.
        ByteBuffer buffer = frame.getData();

        // Store buffer to consume it asynchronously, and wrap the callback.
        dataQueue.offer(new Chunk(buffer, Callback.from(() ->
        {
            // When the buffer has been consumed, then:
            // A) succeed the nested callback.
            callback.succeeded();
            // B) demand more DATA frames.
            stream.demand(1);
        }, callback::failed)));

        // Do not demand more content here, to avoid to overflow the queue.
    }
};
Applications that implement onDataDemanded(…​) must remember to call Stream.demand(…​). If they don’t, the implementation will not deliver DATA frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.

Resetting a Request or Response

In HTTP/2, clients and servers have the ability to tell to the other peer that they are not interested anymore in either the request or the response, using a RST_STREAM frame.

The HTTP2Client APIs allow client applications to send and receive this "reset" frame:

// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
{
    @Override
    public void onReset(Stream stream, ResetFrame frame)
    {
        // The server reset this stream.
    }
});
Stream stream = streamCF.get();

// Reset this stream (for example, the user closed the application).
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);

Receiving HTTP/2 Pushes

HTTP/2 servers have the ability to push resources related to a primary resource. When an HTTP/2 server pushes a resource, it sends to the client a PUSH_PROMISE frame that contains the request URI and headers that a client would use to request explicitly that resource.

Client applications can be configured to tell the server to never push resources, see this section.

Client applications can listen to the push events, and act accordingly:

// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
{
    @Override
    public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
    {
        // The "request" the client would make for the pushed resource.
        MetaData.Request pushedRequest = frame.getMetaData();
        // The pushed "request" URI.
        HttpURI pushedURI = pushedRequest.getURI();
        // The pushed "request" headers.
        HttpFields pushedRequestHeaders = pushedRequest.getFields();

        // If needed, retrieve the primary stream that triggered the push.
        Stream primaryStream = pushedStream.getSession().getStream(frame.getStreamId());

        // Return a Stream.Listener to listen for the pushed "response" events.
        return new Stream.Listener.Adapter()
        {
            @Override
            public void onHeaders(Stream stream, HeadersFrame frame)
            {
                // Handle the pushed stream "response".

                MetaData metaData = frame.getMetaData();
                if (metaData.isResponse())
                {
                    // The pushed "response" headers.
                    HttpFields pushedResponseHeaders = metaData.getFields();
                }
            }

            @Override
            public void onData(Stream stream, DataFrame frame, Callback callback)
            {
                // Handle the pushed stream "response" content.

                // The pushed stream "response" content bytes.
                ByteBuffer buffer = frame.getData();
                // Consume the buffer and complete the callback.
                callback.succeeded();
            }
        };
    }
});

If a client application does not want to handle a particular HTTP/2 push, it can just reset the pushed stream to tell the server to stop sending bytes for the pushed stream:

// Open a Stream by sending the HEADERS frame.
CompletableFuture<Stream> streamCF = session.newStream(headersFrame, new Stream.Listener.Adapter()
{
    @Override
    public Stream.Listener onPush(Stream pushedStream, PushPromiseFrame frame)
    {
        // Reset the pushed stream to tell the server we are not interested.
        pushedStream.reset(new ResetFrame(pushedStream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);

        // Not interested in listening to pushed response events.
        return null;
    }
});

Client Libraries I/O Architecture

The Jetty client libraries provide the basic components and APIs to implement a network client.

They build on the common Jetty I/O Architecture and provide client specific concepts (such as establishing a connection to a server).

There are conceptually two layers that compose the Jetty client libraries:

  1. The network layer, that handles the low level I/O and deals with buffers, threads, etc.

  2. The protocol layer, that handles the parsing of bytes read from the network and the generation of bytes to write to the network.

Client Libraries Network Layer

The Jetty client libraries use the common I/O design described in this section. The main client-side component is the ClientConnector.

The ClientConnector primarily wraps the SelectorManager and aggregates other four components:

  • a thread pool (in form of an java.util.concurrent.Executor)

  • a scheduler (in form of org.eclipse.jetty.util.thread.Scheduler)

  • a byte buffer pool (in form of org.eclipse.jetty.io.ByteBufferPool)

  • a TLS factory (in form of org.eclipse.jetty.util.ssl.SslContextFactory.Client)

The ClientConnector is where you want to set those components after you have configured them. If you don’t explicitly set those components on the ClientConnector, then appropriate defaults will be chosen when the ClientConnector starts.

The simplest example that creates and starts a ClientConnector is the following:

ClientConnector clientConnector = new ClientConnector();
clientConnector.start();

A more typical example:

// Create and configure the SslContextFactory.
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.addExcludeProtocols("TLSv1", "TLSv1.1");

// Create and configure the thread pool.
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("client");

// Create and configure the ClientConnector.
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(sslContextFactory);
clientConnector.setExecutor(threadPool);
clientConnector.start();

A more advanced example that customizes the ClientConnector by overriding some of its methods:

class CustomClientConnector extends ClientConnector
{
    @Override
    protected SelectorManager newSelectorManager()
    {
        return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors())
        {
            @Override
            protected void endPointOpened(EndPoint endpoint)
            {
                System.getLogger("endpoint").log(INFO, "opened %s", endpoint);
            }

            @Override
            protected void endPointClosed(EndPoint endpoint)
            {
                System.getLogger("endpoint").log(INFO, "closed %s", endpoint);
            }
        };
    }
}

// Create and configure the thread pool.
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setName("client");

// Create and configure the scheduler.
Scheduler scheduler = new ScheduledExecutorScheduler("scheduler-client", false);

// Create and configure the custom ClientConnector.
CustomClientConnector clientConnector = new CustomClientConnector();
clientConnector.setExecutor(threadPool);
clientConnector.setScheduler(scheduler);
clientConnector.start();

Since ClientConnector is the component that handles the low-level network, it is also the component where you want to configure the low-level network configuration.

The most common parameters are:

  • ClientConnector.selectors: the number of java.nio.Selectors components (defaults to 1) that are present to handle the SocketChannels opened by the ClientConnector. You typically want to increase the number of selectors only for those use cases where each selector should handle more than few hundreds concurrent socket events. For example, one selector typically runs well for 250 concurrent socket events; as a rule of thumb, you can multiply that number by 10 to obtain the number of opened sockets a selector can handle (2500), based on the assumption that not all the 2500 sockets will be active at the same time.

  • ClientConnector.idleTimeout: the duration of time after which ClientConnector closes a socket due to inactivity (defaults to 30 seconds). This is an important parameter to configure, and you typically want the client idle timeout to be shorter than the server idle timeout, to avoid race conditions where the client attempts to use a socket just before the client-side idle timeout expires, but the server-side idle timeout has already expired and the is already closing the socket.

  • ClientConnector.connectBlocking: whether the operation of connecting a socket to the server (i.e. SocketChannel.connect(SocketAddress)) must be a blocking or a non-blocking operation (defaults to false). For localhost or same datacenter hosts you want to set this parameter to true because DNS resolution will be immediate (and likely never fail). For generic Internet hosts (e.g. when you are implementing a web spider) you want to set this parameter to false.

  • ClientConnector.connectTimeout: the duration of time after which ClientConnector aborts a connection attempt to the server (defaults to 5 seconds). This time includes the DNS lookup time and the TCP connect time.

Please refer to the ClientConnector javadocs for the complete list of configurable parameters.

Client Libraries Protocol Layer

The protocol layer builds on top of the network layer to generate the bytes to be written to the network and to parse the bytes read from the network.

Recall from this section that Jetty uses the Connection abstraction to produce and interpret the network bytes.

On the client side, a ClientConnectionFactory implementation is the component that creates Connection instances based on the protocol that the client wants to "speak" with the server.

Applications use ClientConnector.connect(SocketAddress, Map<String, Object>) to establish a TCP connection to the server, and must tell ClientConnector how to create the Connection for that particular TCP connection, and how to notify back the application when the connection creation succeeds or fails.

This is done by passing a ClientConnectionFactory (that creates Connection instances) and a Promise (that is notified of connection creation success or failure) in the context Map as follows:

class CustomConnection extends AbstractConnection
{
    public CustomConnection(EndPoint endPoint, Executor executor)
    {
        super(endPoint, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();
        System.getLogger("connection").log(INFO, "Opened connection {0}", this);
    }

    @Override
    public void onFillable()
    {
    }
}

ClientConnector clientConnector = new ClientConnector();
clientConnector.start();

String host = "serverHost";
int port = 8080;
SocketAddress address = new InetSocketAddress(host, port);

// The ClientConnectionFactory that creates CustomConnection instances.
ClientConnectionFactory connectionFactory = (endPoint, context) ->
{
    System.getLogger("connection").log(INFO, "Creating connection for {0}", endPoint);
    return new CustomConnection(endPoint, clientConnector.getExecutor());
};

// The Promise to notify of connection creation success or failure.
CompletableFuture<CustomConnection> connectionPromise = new Promise.Completable<>();

// Populate the context with the mandatory keys to create and obtain connections.
Map<String, Object> context = new HashMap<>();
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);

// Use the Connection when it's available.

// Use it in a non-blocking way via CompletableFuture APIs.
connectionPromise.whenComplete((connection, failure) ->
{
    System.getLogger("connection").log(INFO, "Created connection for {0}", connection);
});

// Alternatively, you can block waiting for the connection (or a failure).
// CustomConnection connection = connectionPromise.get();

When a Connection is created successfully, its onOpen() method is invoked, and then the promise is completed successfully.

It is now possible to write a super-simple telnet client that reads and writes string lines:

class TelnetConnection extends AbstractConnection
{
    private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private Consumer<String> consumer;

    public TelnetConnection(EndPoint endPoint, Executor executor)
    {
        super(endPoint, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();

        // Declare interest for fill events.
        fillInterested();
    }

    @Override
    public void onFillable()
    {
        try
        {
            ByteBuffer buffer = BufferUtil.allocate(1024);
            while (true)
            {
                int filled = getEndPoint().fill(buffer);
                if (filled > 0)
                {
                    while (buffer.hasRemaining())
                    {
                        // Search for newline.
                        byte read = buffer.get();
                        if (read == '\n')
                        {
                            // Notify the consumer of the line.
                            consumer.accept(bytes.toString(StandardCharsets.UTF_8));
                            bytes.reset();
                        }
                        else
                        {
                            bytes.write(read);
                        }
                    }
                }
                else if (filled == 0)
                {
                    // No more bytes to fill, declare
                    // again interest for fill events.
                    fillInterested();
                    return;
                }
                else
                {
                    // The other peer closed the
                    // connection, close it back.
                    getEndPoint().close();
                    return;
                }
            }
        }
        catch (Exception x)
        {
            getEndPoint().close(x);
        }
    }

    public void onLine(Consumer<String> consumer)
    {
        this.consumer = consumer;
    }

    public void writeLine(String line, Callback callback)
    {
        line = line + "\r\n";
        getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8)));
    }
}

ClientConnector clientConnector = new ClientConnector();
clientConnector.start();

String host = "wikipedia.org";
int port = 80;
SocketAddress address = new InetSocketAddress(host, port);

ClientConnectionFactory connectionFactory = (endPoint, context) ->
    new TelnetConnection(endPoint, clientConnector.getExecutor());

CompletableFuture<TelnetConnection> connectionPromise = new Promise.Completable<>();

Map<String, Object> context = new HashMap<>();
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);

connectionPromise.whenComplete((connection, failure) ->
{
    if (failure == null)
    {
        // Register a listener that receives string lines.
        connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));

        // Write a line.
        connection.writeLine("" +
            "GET / HTTP/1.0\r\n" +
            "", Callback.NOOP);
    }
    else
    {
        failure.printStackTrace();
    }
});

Note how a very basic "telnet" API that applications could use is implemented in the form of the onLine(Consumer<String>) for the non-blocking receiving side and writeLine(String, Callback) for the non-blocking sending side. Note also how the onFillable() method implements some basic "parsing" by looking up the \n character in the buffer.

The "telnet" client above looks like a super-simple HTTP client because HTTP/1.0 can be seen as a line-based protocol. HTTP/1.0 was used just as an example, but we could have used any other line-based protocol such as SMTP, provided that the server was able to understand it.

This is very similar to what the Jetty client implementation does for real network protocols. Real network protocols are of course more complicated and so is the implementation code that handles them, but the general ideas are similar.

The Jetty client implementation provides a number of ClientConnectionFactory implementations that can be composed to produce and interpret the network bytes.

For example, it is simple to modify the above example to use the TLS protocol so that you will be able to connect to the server on port 443, typically reserved for the encrypted HTTP protocol.

The differences between the clear-text version and the TLS encrypted version are minimal:

class TelnetConnection extends AbstractConnection
{
    private final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private Consumer<String> consumer;

    public TelnetConnection(EndPoint endPoint, Executor executor)
    {
        super(endPoint, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();

        // Declare interest for fill events.
        fillInterested();
    }

    @Override
    public void onFillable()
    {
        try
        {
            ByteBuffer buffer = BufferUtil.allocate(1024);
            while (true)
            {
                int filled = getEndPoint().fill(buffer);
                if (filled > 0)
                {
                    while (buffer.hasRemaining())
                    {
                        // Search for newline.
                        byte read = buffer.get();
                        if (read == '\n')
                        {
                            // Notify the consumer of the line.
                            consumer.accept(bytes.toString(StandardCharsets.UTF_8));
                            bytes.reset();
                        }
                        else
                        {
                            bytes.write(read);
                        }
                    }
                }
                else if (filled == 0)
                {
                    // No more bytes to fill, declare
                    // again interest for fill events.
                    fillInterested();
                    return;
                }
                else
                {
                    // The other peer closed the
                    // connection, close it back.
                    getEndPoint().close();
                    return;
                }
            }
        }
        catch (Exception x)
        {
            getEndPoint().close(x);
        }
    }

    public void onLine(Consumer<String> consumer)
    {
        this.consumer = consumer;
    }

    public void writeLine(String line, Callback callback)
    {
        line = line + "\r\n";
        getEndPoint().write(callback, ByteBuffer.wrap(line.getBytes(StandardCharsets.UTF_8)));
    }
}

ClientConnector clientConnector = new ClientConnector();
clientConnector.start();

// Use port 443 to contact the server using encrypted HTTP.
String host = "wikipedia.org";
int port = 443;
SocketAddress address = new InetSocketAddress(host, port);

ClientConnectionFactory connectionFactory = (endPoint, context) ->
    new TelnetConnection(endPoint, clientConnector.getExecutor());

// Wrap the "telnet" ClientConnectionFactory with the SslClientConnectionFactory.
connectionFactory = new SslClientConnectionFactory(clientConnector.getSslContextFactory(),
    clientConnector.getByteBufferPool(), clientConnector.getExecutor(), connectionFactory);

// We will obtain a SslConnection now.
CompletableFuture<SslConnection> connectionPromise = new Promise.Completable<>();

Map<String, Object> context = new HashMap<>();
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, connectionFactory);
context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, connectionPromise);
clientConnector.connect(address, context);

connectionPromise.whenComplete((sslConnection, failure) ->
{
    if (failure == null)
    {
        // Unwrap the SslConnection to access the "line" APIs in TelnetConnection.
        TelnetConnection connection = (TelnetConnection)sslConnection.getDecryptedEndPoint().getConnection();
        // Register a listener that receives string lines.
        connection.onLine(line -> System.getLogger("app").log(INFO, "line: {0}", line));

        // Write a line.
        connection.writeLine("" +
            "GET / HTTP/1.0\r\n" +
            "", Callback.NOOP);
    }
    else
    {
        failure.printStackTrace();
    }
});

The differences with the clear-text version are only:

  • Change the port from 80 to 443.

  • Wrap the ClientConnectionFactory with SslClientConnectionFactory.

  • Unwrap the SslConnection to access TelnetConnection.

Server Libraries

The Eclipse Jetty Project provides server-side libraries that allow you to embed an HTTP or WebSocket server in your applications. A typical example is a HTTP server that needs to expose a REST endpoint. Another example is a proxy application that receives HTTP requests and forwards them to third party services possibly using also the Jetty client libraries.

While historically Jetty is an HTTP server, it is possible to use the Jetty server-side libraries to write a generic network server that interprets any network protocol (not only HTTP).

If you are interested in the low-level details of how the Eclipse Jetty server libraries work, or are interested in writing a custom protocol, look at the Server I/O Architecture.

The Jetty server-side libraries provide:

  • HTTP support for HTTP/1.0, HTTP/1.1, HTTP/2, clear-text or encrypted, for applications that want to embed Jetty as a generic HTTP server or proxy, via the HTTP libraries

  • HTTP/2 low-level support, for applications that want to explicitly handle low-level HTTP/2 sessions, streams and frames, via the HTTP/2 libraries

  • WebSocket support, for applications that want to embed a WebSocket server, via the WebSocket libraries

HTTP Server Libraries

The Eclipse Jetty Project has historically provided libraries to embed an HTTP server and a Servlet Container.

The Maven artifact coordinates are:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>10.0.0.beta1</version>
</dependency>

An org.eclipse.jetty.server.Server instance is the central component that links together a collection of Connectors and a collection of Handlers, with threads from a ThreadPool doing the work.

diag 05cfcead1245ff0fbcf3af8873998cee

The components that accept connections from clients are org.eclipse.jetty.server.Connector implementations.

When a Jetty server interprets the HTTP protocol (both HTTP/1.1 and HTTP/2), 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 Handlers 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.

If you are interested in writing your server application based on the Servlet APIs, jump to this section.

Server Request Processing

The Jetty HTTP request processing is outlined below in the diagram 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.

diag 3ca149653ee220164efd33ca869b97b5

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. 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 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.Listeners 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, System.nanoTime());
    }

    @Override
    public void onComplete(Request request)
    {
        long begin = times.remove(request);
        long elapsed = System.nanoTime() - 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();

Server Connectors

A Connector is the component that handles incoming requests from clients, and works in conjunction with ConnectionFactory instances.

The primary implementation is org.eclipse.jetty.server.ServerConnector. ServerConnector uses a java.nio.channels.ServerSocketChannel to listen to a TCP port and to accept TCP connections.

Since ServerConnector wraps a ServerSocketChannel, it can be configured in a similar way, for example the port to listen to, the network 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, 1, 1, new HttpConnectionFactory());

// Configure TCP parameters.

// The TCP port to listen to.
connector.setPort(8080);
// The TCP address to bind to.
connector.setHost("127.0.0.1");
// The TCP accept queue size.
connector.setAcceptQueueSize(128);

server.addConnector(connector);
server.start();

The acceptors are threads (typically only one) that compete to accept TCP connections on the listening port. 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 ServerConnector.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:

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 TCP connection, ServerConnector asks a ConnectionFactory to create a Connection object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol (see this section for more details about Connection objects).

A ServerConnector can be configured with one or more ConnectionFactorys. If no ConnectionFactory is specified then HttpConnectionFactory is implicitly configured.

Configuring 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();

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();
Configuring 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 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 ConnectionFactorys 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 Handlers.

Configuring 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 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 ConnectionFactorys:

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 ConnectionFactorys 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.

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, http11, h2);
connector.setPort(8443);

server.addConnector(connector);
server.start();

Note how the ConnectionFactorys passed to ServerConnector are in order: TLS, ALPN, HTTP/1.1, HTTP/2.

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 ConnectionFactorys supported by the ServerConnector, the ConnectionFactory correspondent to the negotiated protocol.

Server Handlers

An org.eclipse.jetty.server.Handler is the component that processes incoming HTTP requests and eventually produces HTTP responses.

Handlers can be organized in different ways:

  • in a sequence, where Handlers are invoked one after the other

    • HandlerCollection invokes all Handlers one after the other

    • HandlerList invokes Handlerss until one calls Request.setHandled(true) to indicate that the request has been handled and no further Handler 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: Handlers 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).

Handlers 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 Handlers, preferring instead to use existing Handlers 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 Handlers 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 Handlers.

Using Provided 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.

Custom and Utility Handlers

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 Handlers. However, both of these have no knowledge of the concept of context and just iterate through the sequence of Handlers.

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 Handlers.

If ContextHandlerCollection does not find a match, it just returns. What happens next depends on the Handler tree structure: other Handlers 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 ByteBuffers 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>10.0.0.beta1</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 many 3xx 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 the Server 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

Handlers are easy to write, but often web applications have already been written using the Servlet APIs, using Servlets and Filters.

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>10.0.0.beta1</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 Handlers) 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 Filters 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 ServletContextHandlers as last Handlers 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 Servlets and Filters, WebAppContext reads WEB-INF/web.xml to add Servlets and Filters, 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();
WebAppContext Class Loading

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 while the second is the application-wrapped 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 Handlers needs to be linked together to work properly.

Securing HTTP Server Applications

TODO

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.
— RFC 2518 section 10.1

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).

HTTP/2 Server Library

In the vast majority of cases, server applications should use the generic, high-level, HTTP server library that also provides HTTP/2 support via the HTTP/2 ConnectionFactorys as described in details here.

The low-level HTTP/2 server library has been designed for those applications that need low-level access to HTTP/2 features such as sessions, streams and frames, and this is quite a rare use case.

See also the correspondent HTTP/2 client library.

Introduction

The Maven artifact coordinates for the HTTP/2 client library are the following:

<dependency>
  <groupId>org.eclipse.jetty.http2</groupId>
  <artifactId>http2-server</artifactId>
  <version>10.0.0.beta1</version>
</dependency>

HTTP/2 is a multiplexed protocol: it allows multiple HTTP/2 requests to be sent on the same TCP connection. Each request/response cycle is represented by a stream. Therefore, a single session manages multiple concurrent streams. A stream has typically a very short life compared to the session: a stream only exists for the duration of the request/response cycle and then disappears.

HTTP/2 Flow Control

The HTTP/2 protocol is flow controlled (see the specification). This means that a sender and a receiver maintain a flow control window that tracks the number of data bytes sent and received, respectively. When a sender sends data bytes, it reduces its flow control window. When a receiver receives data bytes, it also reduces its flow control window, and then passes the received data bytes to the application. The application consumes the data bytes and tells back the receiver that it has consumed the data bytes. The receiver then enlarges the flow control window, and arranges to send a message to the sender with the number of bytes consumed, so that the sender can enlarge its flow control window.

A sender can send data bytes up to its whole flow control window, then it must stop sending until it receives a message from the receiver that the data bytes have been consumed, which enlarges the flow control window, which allows the sender to send more data bytes.

HTTP/2 defines two flow control windows: one for each session, and one for each stream. Let’s see with an example how they interact, assuming that in this example the session flow control window is 120 bytes and the stream flow control window is 100 bytes.

The sender opens a session, and then opens stream_1 on that session, and sends 80 data bytes. At this point the session flow control window is 40 bytes (120 - 80), and stream_1's flow control window is 20 bytes (100 - 80). The sender now opens stream_2 on the same session and sends 40 data bytes. At this point, the session flow control window is 0 bytes (40 - 40), while stream_2's flow control window is 60 (100 - 40). Since now the session flow control window is 0, the sender cannot send more data bytes, neither on stream_1 nor on stream_2 despite both have their stream flow control windows greater than 0.

The receiver consumes stream_2's 40 data bytes and sends a message to the sender with this information. At this point, the session flow control window is 40 (0 40), stream_1's flow control window is still 20 and stream_2's flow control window is 100 (60 40). If the sender opens stream_3 and would like to send 50 data bytes, it would only be able to send 40 because that is the maximum allowed by the session flow control window at this point.

It is therefore very important that applications notify the fact that they have consumed data bytes as soon as possible, so that the implementation (the receiver) can send a message to the sender (in the form of a WINDOW_UPDATE frame) with the information to enlarge the flow control window, therefore reducing the possibility that sender stalls due to the flow control windows being reduced to 0.

How a server application should handle HTTP/2 flow control is discussed in details in this section.

Server Setup

The low-level HTTP/2 support is provided by org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory and org.eclipse.jetty.http2.api.server.ServerSessionListener:

// Create a Server instance.
Server server = new Server();

ServerSessionListener sessionListener = new ServerSessionListener.Adapter();

// Create a ServerConnector with RawHTTP2ServerConnectionFactory.
RawHTTP2ServerConnectionFactory http2 = new RawHTTP2ServerConnectionFactory(sessionListener);

// Configure RawHTTP2ServerConnectionFactory, for example:

// Configure the max number of concurrent requests.
http2.setMaxConcurrentStreams(128);
// Enable support for CONNECT.
http2.setConnectProtocolEnabled(true);

// Create the ServerConnector.
ServerConnector connector = new ServerConnector(server, http2);

// Add the Connector to the Server
server.addConnector(connector);

// Start the Server so it starts accepting connections from clients.
server.start();

Where server applications using the high-level server library deal with HTTP requests and responses in Handlers, server applications using the low-level HTTP/2 server library deal directly with HTTP/2 sessions, streams and frames in a ServerSessionListener implementation.

The ServerSessionListener interface defines a number of methods that are invoked by the implementation upon the occurrence of HTTP/2 events, and that server applications can override to react to those events.

Please refer to the `ServerSessionListener`javadocs for the complete list of events.

The first event is the accept event and happens when a client opens a new TCP connection to the server and the server accepts the connection. This is the first occasion where server applications have access to the HTTP/2 Session object:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public void onAccept(Session session)
    {
        InetSocketAddress remoteAddress = session.getRemoteAddress();
        System.getLogger("http2").log(INFO, "Connection from {0}", remoteAddress);
    }
};

After connecting to the server, a compliant HTTP/2 client must send the HTTP/2 client preface, and when the server receives it, it generates the preface event on the server. This is where server applications can customize the connection settings by returning a map of settings that the implementation will send to the client:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public Map<Integer, Integer> onPreface(Session session)
    {
        // Customize the settings, for example:
        Map<Integer, Integer> settings = new HashMap<>();

        // Tell the client that HTTP/2 push is disabled.
        settings.put(SettingsFrame.ENABLE_PUSH, 0);

        return settings;
    }
};

Receiving a Request

Receiving an HTTP request from the client, and sending a response, creates a stream that encapsulates the exchange of HTTP/2 frames that compose the request and the response.

An HTTP request is made of a HEADERS frame, that carries the request method, the request URI and the request headers, and optional DATA frames that carry the request content.

Receiving the HEADERS frame opens the Stream:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
    {
        // This is the "new stream" event, so it's guaranteed to be a request.
        MetaData.Request request = (MetaData.Request)frame.getMetaData();

        // Return a Stream.Listener to handle the request events,
        // for example request content events or a request reset.
        return new Stream.Listener.Adapter();
    }
};

Server applications should return a Stream.Listener implementation from onNewStream(…​) to be notified of events generated by the client, such as DATA frames carrying request content, or a RST_STREAM frame indicating that the client wants to reset the request, or an idle timeout event indicating that the client was supposed to send more frames but it did not.

The example below shows how to receive request content:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
    {
        MetaData.Request request = (MetaData.Request)frame.getMetaData();
        // Return a Stream.Listener to handle the request events.
        return new Stream.Listener.Adapter()
        {
            @Override
            public void onData(Stream stream, DataFrame frame, Callback callback)
            {
                // Get the content buffer.
                ByteBuffer buffer = frame.getData();

                // Consume the buffer, here - as an example - just log it.
                System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);

                // Tell the implementation that the buffer has been consumed.
                callback.succeeded();

                // By returning from the method, implicitly tell the implementation
                // to deliver to this method more DATA frames when they are available.
            }
        };
    }
};
Returning from the onData(…​) method implicitly demands for more DATA frames (unless the one just delivered was the last). Additional DATA frames may be delivered immediately if they are available or later, asynchronously, when they arrive.

Applications that consume the content buffer within onData(…​) (for example, writing it to a file, or copying the bytes to another storage) should succeed the callback as soon as they have consumed the content buffer. This allows the implementation to reuse the buffer, reducing the memory requirements needed to handle the content buffers.

Alternatively, a client application may store away both the buffer and the callback to consume the buffer bytes later, or pass both the buffer and the callback to another asynchronous API (this is typical in proxy applications).

Completing the Callback is very important not only to allow the implementation to reuse the buffer, but also tells the implementation to enlarge the stream and session flow control windows so that the sender will be able to send more DATA frames without stalling.

Applications can also precisely control when to demand more DATA frames, by implementing the onDataDemanded(…​) method instead of onData(…​):

class Chunk
{
    private final ByteBuffer buffer;
    private final Callback callback;

    Chunk(ByteBuffer buffer, Callback callback)
    {
        this.buffer = buffer;
        this.callback = callback;
    }
}

// A queue that consumers poll to consume content asynchronously.
Queue<Chunk> dataQueue = new ConcurrentLinkedQueue<>();

// Implementation of Stream.Listener.onDataDemanded(...)
// in case of asynchronous content consumption and demand.
Stream.Listener listener = new Stream.Listener.Adapter()
{
    @Override
    public void onDataDemanded(Stream stream, DataFrame frame, Callback callback)
    {
        // Get the content buffer.
        ByteBuffer buffer = frame.getData();

        // Store buffer to consume it asynchronously, and wrap the callback.
        dataQueue.offer(new Chunk(buffer, Callback.from(() ->
        {
            // When the buffer has been consumed, then:
            // A) succeed the nested callback.
            callback.succeeded();
            // B) demand more DATA frames.
            stream.demand(1);
        }, callback::failed)));

        // Do not demand more content here, to avoid to overflow the queue.
    }
};
Applications that implement onDataDemanded(…​) must remember to call Stream.demand(…​). If they don’t, the implementation will not deliver DATA frames and the application will stall threadlessly until an idle timeout fires to close the stream or the session.

Sending a Response

After receiving an HTTP request, a server application must send an HTTP response.

An HTTP response is typically composed of a HEADERS frame containing the HTTP status code and the response headers, and optionally one or more DATA frames containing the response content bytes.

The HTTP/2 protocol also supports response trailers (that is, headers that are sent after the response content) that also are sent using a HEADERS frame.

A server application can send a response in this way:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
    {
        // Send a response after reading the request.
        MetaData.Request request = (MetaData.Request)frame.getMetaData();
        if (frame.isEndStream())
        {
            respond(stream, request);
            return null;
        }
        else
        {
            return new Stream.Listener.Adapter()
            {
                @Override
                public void onData(Stream stream, DataFrame frame, Callback callback)
                {
                    // Consume the request content.
                    callback.succeeded();
                    if (frame.isEndStream())
                        respond(stream, request);
                }
            };
        }
    }

    private void respond(Stream stream, MetaData.Request request)
    {
        // Prepare the response HEADERS frame.

        // The response HTTP status and HTTP headers.
        MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);

        if (HttpMethod.GET.is(request.getMethod()))
        {
            // The response content.
            ByteBuffer resourceBytes = getResourceBytes(request);

            // Send the HEADERS frame with the response status and headers,
            // and a DATA frame with the response content bytes.
            stream.headers(new HeadersFrame(stream.getId(), response, null, false))
                .thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true)));
        }
        else
        {
            // Send just the HEADERS frame with the response status and headers.
            stream.headers(new HeadersFrame(stream.getId(), response, null, true));
        }
    }
};

Resetting a Request

A server application may decide that it does not want to accept the request. For example, it may throttle the client because it sent too many requests in a time window, or the request is invalid (and does not deserve a proper HTTP response), etc.

A request can be reset in this way:

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    @Override
    public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
    {
        float requestRate = calculateRequestRate();

        if (requestRate > maxRequestRate)
        {
            stream.reset(new ResetFrame(stream.getId(), ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
            return null;
        }
        else
        {
            // The request is accepted.
            MetaData.Request request = (MetaData.Request)frame.getMetaData();
            // Return a Stream.Listener to handle the request events.
            return new Stream.Listener.Adapter();
        }
    }
};

HTTP/2 Push of Resources

A server application may push secondary resources related to a primary resource.

A client may inform the server that it does not accept pushed resources(see this section of the specification) via a SETTINGS frame. Server applications must track SETTINGS frames and verify whether the client supports HTTP/2 push, and only push if the client supports it:

// The favicon bytes.
ByteBuffer faviconBuffer = BufferUtil.toBuffer(Resource.newResource("/path/to/favicon.ico"), true);

ServerSessionListener sessionListener = new ServerSessionListener.Adapter()
{
    // By default, push is enabled.
    private boolean pushEnabled = true;

    @Override
    public void onSettings(Session session, SettingsFrame frame)
    {
        // Check whether the client sent an ENABLE_PUSH setting.
        Map<Integer, Integer> settings = frame.getSettings();
        Integer enablePush = settings.get(SettingsFrame.ENABLE_PUSH);
        if (enablePush != null)
            pushEnabled = enablePush == 1;
    }

    @Override
    public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
    {
        MetaData.Request request = (MetaData.Request)frame.getMetaData();
        if (pushEnabled && request.getURIString().endsWith("/index.html"))
        {
            // Push the favicon.
            HttpURI pushedURI = HttpURI.build(request.getURI()).path("/favicon.ico");
            MetaData.Request pushedRequest = new MetaData.Request("GET", pushedURI, HttpVersion.HTTP_2, HttpFields.EMPTY);
            PushPromiseFrame promiseFrame = new PushPromiseFrame(stream.getId(), 0, pushedRequest);
            stream.push(promiseFrame, new Stream.Listener.Adapter())
                .thenCompose(pushedStream ->
                {
                    // Send the favicon "response".
                    MetaData.Response pushedResponse = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
                    return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
                        .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer, true)));
                });
        }
        // Return a Stream.Listener to handle the request events.
        return new Stream.Listener.Adapter();
    }
};

WebSocket Server Libraries

TODO

Server Libraries I/O Architecture

The Jetty server libraries provide the basic components and APIs to implement a network server.

They build on the common Jetty I/O Architecture and provide server specific concepts.

The main I/O server-side class is org.eclipse.jetty.server.ServerConnector.

A ServerConnector manages a list of factories.

Creating a Custom Protocol

You can create custom protocols with Jetty. This page provides an example of how to do so, with Telnet as the protocol.

To create a custom Telnet protocol, complete the following tasks:

  • Implement a TelnetServerConnectionFactory.

  • Implement a TelnetServerConnection by extending o.e.j.io.AbstractConnection.

  • Create a parser/interpreter for the bytes you receive (this is totally independent from Jetty).

  • If needed, design an API for the application to use to process the bytes received (also independent from Jetty). The API likely has a respond back primitive that uses a Jetty provided EndPoint and EndPoint.write(Callback, Buffer…​) to write the response bytes.

Implementing a TelnetServerConnectionFactory

Begin with an org.eclipse.jetty.server.ServerConnector, which you can use as is. ServerConnector takes a o.e.j.server.ConnectionFactory, which creates o.e.j.io.Connection objects that interpret the bytes the connector receives. You must implement ConnectionFactory with a TelnetServerConnectionFactory, where you return a Connection implementation (for example, TelnetServerConnection).

Implementing the TelnetServerConnection

For the Connection implementation you need to extend from o.e.j.io.AbstractConnection because it provides many facilities that you would otherwise need to re-implement from scratch.

For each Connection instance there is associated an o.e.j.io.EndPoint instance. Think of EndPoint as a specialized version of JDK’s SocketChannel. You use the EndPoint to read, write, and close. You don’t need to implement EndPoint, because Jetty provides concrete classes for you to use.

The Connection is the passive side (that is, Jetty calls it when there is data to read), while the EndPoint is the active part (that is, applications call it to write data to the other end). When there is data to read, Jetty calls AbstractConnection.onFillable(), which you must implement in your TelnetServerConnection.

A typical implementation reads bytes from the EndPoint by calling EndPoint.fill(ByteBuffer). For examples, look at both the simpler SPDYConnection (in the SPDY client package, but server also uses it), and the slightly more complex HttpConnection.

Parsing the Bytes Received

After you read the bytes, you need to parse them. For the Telnet protocol there is not much to parse, but perhaps you have your own commands that you want to interpret and execute. Therefore typically every connection has an associated parser instance. In turn, a parser usually emits parse events that a parser listener interprets, as the following examples illustrate:

  • In HTTP, the Jetty HTTP parser parses the request line (and emits a parser event), then parses the headers (and emits a parser event for each) until it recognizes the end of the headers (and emits another parser event). At that point, the interpreter or parser listener (which for HTTP is o.e.j.server.HttpChannel) has all the information necessary to build a HttpServletRequest object and can call the user code (the web application, that is, servlets/filters).

  • In SPDY, the Jetty SPDY parser parses a SPDY frame (and emits a parser event), and the parser listener (an instance of o.e.j.spdy.StandardSession) interprets the parser events and calls user code (application-provided listeners).

With ConnectionFactory, Connection, parser, and parser listeners in place, you have configured the read side.

Designing an API to Process Bytes

At this point, server applications typically write data back to the client.

The Servlet API (for HTTP) or application-provided listeners (for SPDY) expose an interface to web applications so that they can write data back to the client. The implementation of those interfaces must link back to the EndPoint instance associated with the Connection instance so that it can write data via EndPoint.write(Callback, ByteBuffer…​). This is an asynchronous call, and it notifies the callback when all the buffers have been fully written.

For example, in the Servlet API, applications use a ServletOutputStream to write the response content. ServletOutputStream is an abstract class that Jetty implements, enabling Jetty to handle the writes from the web application; the writes eventually end up in an EndPoint.write(…​) call.

Tips for Designing an API

If you want to write a completely asynchronous implementation, your API to write data to the client must have a callback/promise concept: “Call me back when you are done, and (possibly) give me the result of the computation."

SPDY’s Stream class is a typical example. Notice how the methods there exist in two versions, a synchronous (blocking) one, and an asynchronous one that takes as last parameter a Callback (if no result is needed), or a Promise (if a result is needed). It is trivial to write the synchronous version in terms of the asynchronous version.

You can use EndPoint.write(Callback, ByteBuffer…​) in a blocking way as follows:

FutureCallback callback = new FutureCallback();
endPoint.write(callback, buffers);
callback.get();

With the snippet above your API can be synchronous or asynchronous (your choice), but implemented synchronously.

OLD DOCUMENTATION

Embedding

Jetty Embedded HelloWorld

This section provides a tutorial that shows how you can quickly develop embedded code against the Jetty API.

Downloading the Jars

Jetty is decomposed into many jars and dependencies to achieve a minimal footprint by selecting the minimal set of jars. Typically it is best to use something like Maven to manage jars, however this tutorial uses an aggregate Jar that contains all of the required Jetty classes in one Jar. You can manually download the aggregate jetty-all.jar using curl or a browser.

The central Maven repository has started to aggressively reject/deny access to the repository from the wget command line tool (due to abusive use of the tool by some groups). The administrators of the central maven repository have stated that the recommended command line download tool is now curl.
The jetty-all jar referenced in this section is for example purposes only and should not be used outside of this context. Please consider using Maven to manage your project dependencies.

Use curl as follows:

> mkdir Demo
> cd Demo
> curl -o jetty-all-uber.jar https://repo1.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/10.0.0.beta1/jetty-all-10.0.0.beta1-uber.jar

Writing a HelloWorld Example

The Embedding Jetty section contains many examples of writing against the Jetty API. This tutorial uses a simple HelloWorld handler with a main method to run the server. You can either download or create in an editor the file HelloWorld.java with the following content:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloWorld extends AbstractHandler
{
    @Override
    public void handle(String target,
                       Request baseRequest,
                       HttpServletRequest request,
                       HttpServletResponse response) throws IOException,
        ServletException
    {
        // Declare response encoding and types
        response.setContentType("text/html; charset=utf-8");

        // Declare response status code
        response.setStatus(HttpServletResponse.SC_OK);

        // Write back response
        response.getWriter().println("<h1>Hello World</h1>");

        // Inform jetty that this request has now been handled
        baseRequest.setHandled(true);
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = new Server(port);
        server.setHandler(new HelloWorld());

        server.start();
        server.join();
    }
}

Compiling the HelloWord example

The following command compiles the HelloWorld class:

> mkdir classes
> javac -d classes -cp jetty-all-uber.jar HelloWorld.java

Running the Handler and Server

The following command runs the HelloWorld example:

> java -cp classes:jetty-all-uber.jar org.eclipse.jetty.embedded.HelloWorld

You can now point your browser at http://localhost:8080 to see your hello world page.

Next Steps

To learn more about Jetty, take these next steps:

Embedding Jetty

Jetty has a slogan, "Don’t deploy your application in Jetty, deploy Jetty in your application!" What this means is that as an alternative to bundling your application as a standard WAR to be deployed in Jetty, Jetty is designed to be a software component that can be instantiated and used in a Java program just like any POJO. Put another way, running Jetty in embedded mode means putting an HTTP module into your application, rather than putting your application into an HTTP server.

This tutorial takes you step-by-step from the simplest Jetty server instantiation to running multiple web applications with standards-based deployment descriptors. The source for most of these examples is part of the standard Jetty project.

Overview

To embed a Jetty server the following steps are typical and are illustrated by the examples in this tutorial:

  1. Create a Server instance.

  2. Add/Configure Connectors.

  3. Add/Configure Handlers and/or Contexts and/or Servlets.

  4. Start the Server.

  5. Wait on the server or do something else with your thread.

Creating the Server

The following code from SimplestServer.java instantiates and runs the simplest possible Jetty server:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;

/**
 * The simplest possible Jetty server.
 */
// TODO: remove this class, only used in documentation.
public class SimplestServer
{
    public static Server createServer(int port)
    {
        Server server = new Server(port);
        // This has a connector listening on port specified
        // and no handlers, meaning all requests will result
        // in a 404 response
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);
        server.start();
        server.join();
    }
}

This runs an HTTP server on port 8080. It is not a very useful server as it has no handlers, and thus returns a 404 error for every request.

Using Handlers

To produce a response to a request, Jetty requires that you set a Handler on the server. A handler may:

  • Examine/modify the HTTP request.

  • Generate the complete HTTP response.

  • Call another Handler (see HandlerWrapper).

  • Select one or many Handlers to call (see HandlerCollection).

HelloWorld Handler

The following code based on HelloHandler.java shows a simple hello world handler:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloHandler extends AbstractHandler
{
    final String greeting;
    final String body;

    public HelloHandler()
    {
        this("Hello World");
    }

    public HelloHandler(String greeting)
    {
        this(greeting, null);
    }

    public HelloHandler(String greeting, String body)
    {
        this.greeting = greeting;
        this.body = body;
    }

    @Override
    public void handle(String target,
                       Request baseRequest,
                       HttpServletRequest request,
                       HttpServletResponse response) throws IOException,
        ServletException
    {
        response.setContentType("text/html; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);

        PrintWriter out = response.getWriter();

        out.println("<h1>" + greeting + "</h1>");
        if (body != null)
        {
            out.println(body);
        }

        baseRequest.setHandled(true);
    }
}

The parameters passed to the handle method are:

  • target – the target of the request, which is either a URI or a name from a named dispatcher.

  • baseRequest – the Jetty mutable request object, which is always unwrapped.

  • request – the immutable request object, which may have been wrapped by a filter or servlet.

  • response – the response, which may have been wrapped by a filter or servlet.

The handler sets the response status, content-type, and marks the request as handled before it generates the body of the response using a writer.

Running HelloWorldHandler

To allow a Handler to handle HTTP requests, you must add it to a Server instance. The following code from OneHandler.java shows how a Jetty server can use the HelloWorld handler:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;

public class OneHandler
{
    public static Server createServer(int port)
    {
        Server server = new Server(port);
        server.setHandler(new HelloHandler());
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);
        server.start();
        server.join();
    }
}

One or more handlers do all request handling in Jetty. Some handlers select other specific handlers (for example, a ContextHandlerCollection uses the context path to select a ContextHandler); others use application logic to generate a response (for example, the ServletHandler passes the request to an application Servlet), while others do tasks unrelated to generating the response (for example, RequestLogHandler or StatisticsHandler).

Later sections describe how you can combine handlers like aspects. You can see some of the handlers available in Jetty in the org.eclipse.jetty.server.handler package.

Handler Collections and Wrappers

Complex request handling is typically built from multiple Handlers that you can combine in various ways. Jetty has several implementations of the HandlerContainer interface:

HandlerCollection

Holds a collection of other handlers and calls each handler in order. This is useful for combining statistics and logging handlers with the handler that generates the response.

HandlerList

A Handler Collection that calls each handler in turn until either an exception is thrown, the response is committed or the request.isHandled() returns true. You can use it to combine handlers that conditionally handle a request, such as calling multiple contexts until one matches a virtual host.

HandlerWrapper

A Handler base class that you can use to daisy chain handlers together in the style of aspect-oriented programming. For example, a standard web application is implemented by a chain of a context, session, security and servlet handlers.

ContextHandlerCollection

A specialized HandlerCollection that uses the longest prefix of the request URI (the contextPath) to select a contained ContextHandler to handle the request.

Scoped Handlers

Much of the standard Servlet container in Jetty is implemented with HandlerWrappers that daisy chain handlers together: ContextHandler to SessionHandler to SecurityHandler to ServletHandler. However, because of the nature of the servlet specification, this chaining cannot be a pure nesting of handlers as the outer handlers sometimes need information that the inner handlers process. For example, when a ContextHandler calls some application listeners to inform them of a request entering the context, it must already know which servlet the ServletHandler will dispatch the request to so that the servletPath method returns the correct value.

The HandlerWrapper is specialized to the ScopedHandler abstract class, which supports a daisy chain of scopes. For example if a ServletHandler is nested within a ContextHandler, the order and nesting of execution of methods is:

Server.handle(...)
  ContextHandler.doScope(...)
    ServletHandler.doScope(...)
      ContextHandler.doHandle(...)
        ServletHandler.doHandle(...)
          SomeServlet.service(...)

Thus when the ContextHandler handles the request, it does so within the scope the ServletHandler has established.

Resource Handler

The FileServer example shows how you can use a ResourceHandler to serve static content from the current working directory:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;

/**
 * Simple Jetty FileServer.
 * This is a simple example of Jetty configured as a FileServer.
 */
public class FileServer
{
    public static Server createServer(int port, Resource baseResource) throws Exception
    {
        // Create a basic Jetty server object that will listen on port 8080.  Note that if you set this to port 0
        // then a randomly available port will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // Create the ResourceHandler. It is the object that will actually handle the request for a given file. It is
        // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples.
        ResourceHandler resourceHandler = new ResourceHandler();

        // Configure the ResourceHandler. Setting the resource base indicates where the files should be served out of.
        // In this example it is the current directory but it can be configured to anything that the jvm has access to.
        resourceHandler.setDirectoriesListed(true);
        resourceHandler.setWelcomeFiles(new String[]{"index.html"});
        resourceHandler.setBaseResource(baseResource);

        // Add the ResourceHandler to the server.
        server.setHandler(new HandlerList(resourceHandler, new DefaultHandler()));

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Path userDir = Paths.get(System.getProperty("user.dir"));
        PathResource pathResource = new PathResource(userDir);

        Server server = createServer(port, pathResource);

        // Start things up! By using the server.join() the server thread will join with the current thread.
        // See "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()" for more details.
        server.start();
        server.join();
    }
}

Notice that a HandlerList is used with the ResourceHandler and a DefaultHandler, so that the DefaultHandler generates a good 404 response for any requests that do not match a static resource.

Embedding Connectors

In the previous examples, the Server instance is passed a port number and it internally creates a default instance of a Connector that listens for requests on that port. However, often when embedding Jetty it is desirable to explicitly instantiate and configure one or more Connectors for a Server instance.

One Connector

The following example, OneConnector.java, instantiates, configures, and adds a single HTTP connector instance to the server:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;

/**
 * A Jetty server with one connectors.
 */
public class OneConnector
{
    public static Server createServer(int port) throws Exception
    {
        // The Server
        Server server = new Server();

        // HTTP connector
        ServerConnector http = new ServerConnector(server);
        http.setHost("localhost");
        http.setPort(port);
        http.setIdleTimeout(30000);

        // Set the connector
        server.addConnector(http);

        // Set a handler
        server.setHandler(new HelloHandler());
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start the server
        server.start();
        server.join();
    }
}

In this example the connector handles the HTTP protocol, as that is the default for the ServerConnector class.

Many Connectors

When configuring multiple connectors (for example, HTTP and HTTPS), it may be desirable to share configuration of common parameters for HTTP. To achieve this you need to explicitly configure the ServerConnector class with ConnectionFactory instances, and provide them with common HTTP configuration.

The ManyConnectors example, configures a server with two ServerConnector instances: the http connector has a HTTPConnectionFactory instance; the https connector has a SslConnectionFactory chained to a HttpConnectionFactory. Both HttpConnectionFactory are configured based on the same HttpConfiguration instance, however the HTTPS factory uses a wrapped configuration so that a SecureRequestCustomizer can be added.

Embedding Servlets

Servlets are the standard way to provide application logic that handles HTTP requests. Servlets are similar to a Jetty Handler except that the request object is not mutable and thus cannot be modified. Servlets are handled in Jetty by a ServletHandler. It uses standard path mappings to match a Servlet to a request; sets the requests servletPath and pathInfo; passes the request to the servlet, possibly via Filters to produce a response.

The MinimalServlets example creates a ServletHandler instance and configures a single HelloServlet:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class MinimalServlets
{

    public static Server createServer(int port)
    {
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // The ServletHandler is a dead simple way to create a context handler
        // that is backed by an instance of a Servlet.
        // This handler then needs to be registered with the Server object.
        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        // Passing in the class for the Servlet allows jetty to instantiate an
        // instance of that Servlet and mount it on a given context path.

        // IMPORTANT:
        // This is a raw Servlet, not a Servlet that has been configured
        // through a web.xml @WebServlet annotation, or anything similar.
        handler.addServletWithMapping(HelloServlet.class, "/*");

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server thread is done executing.
        server.join();
    }

    @SuppressWarnings("serial")
    public static class HelloServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest request,
                             HttpServletResponse response) throws IOException
        {
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("text/html");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
        }
    }
}

Embedding Contexts

A ContextHandler is a ScopedHandler that responds only to requests that have a URI prefix that matches the configured context path. Requests that match the context path have their path methods updated accordingly and the contexts scope is available, which optionally may include:

  • A Classloader that is set as the Thread context classloader while request handling is in scope.

  • A set of attributes that is available via the ServletContext API.

  • A set of init parameters that is available via the ServletContext API.

  • A base Resource which is used as the document root for static resource requests via the ServletContext API.

  • A set of virtual host names.

The following OneContext example shows a context being established that wraps the HelloHandler:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;

public class OneContext
{
    public static Server createServer(int port)
    {
        Server server = new Server(port);

        // Add a single handler on context "/hello"
        ContextHandler context = new ContextHandler();
        context.setContextPath("/hello");
        context.setHandler(new HelloHandler());

        // Can be accessed using http://localhost:8080/hello

        server.setHandler(context);
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start the server
        server.start();
        server.join();
    }
}

When many contexts are present, you can embed a ContextHandlerCollection to efficiently examine a request URI to then select the matching ContextHandler(s) for the request. The ManyContexts example shows how many such contexts you can configure:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;

public class ManyContexts
{
    public static Server createServer(int port)
    {
        Server server = new Server(port);

        ContextHandler context = new ContextHandler("/");
        context.setContextPath("/");
        context.setHandler(new HelloHandler("Root Hello"));

        ContextHandler contextFR = new ContextHandler("/fr");
        contextFR.setHandler(new HelloHandler("Bonjour"));

        ContextHandler contextIT = new ContextHandler("/it");
        contextIT.setHandler(new HelloHandler("Buongiorno"));

        ContextHandler contextV = new ContextHandler("/");
        contextV.setVirtualHosts(new String[]{"127.0.0.2"});
        contextV.setHandler(new HelloHandler("Virtual Hello"));

        ContextHandlerCollection contexts = new ContextHandlerCollection(
            context, contextFR, contextIT, contextV
        );

        server.setHandler(contexts);
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);
        server.start();
        server.dumpStdErr();
        server.join();
    }
}

Embedding ServletContexts

A ServletContextHandler is a specialization of ContextHandler with support for standard sessions and Servlets. The following OneServletContext example instantiates a DefaultServlet to server static content from /tmp/ and a DumpServlet that creates a session and dumps basic details about the request:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ListenerHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;

import static javax.servlet.DispatcherType.ASYNC;
import static javax.servlet.DispatcherType.REQUEST;

public class OneServletContext
{
    public static Server createServer(int port, Resource baseResource)
    {
        Server server = new Server(port);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.setBaseResource(baseResource);
        server.setHandler(context);

        // add hello servlet
        context.addServlet(HelloServlet.class, "/hello/*");

        // Add dump servlet on multiple url-patterns
        ServletHolder debugHolder = new ServletHolder("debug", DumpServlet.class);
        context.addServlet(debugHolder, "/dump/*");
        context.addServlet(debugHolder, "*.dump");

        // add default servlet (for error handling and static resources)
        context.addServlet(DefaultServlet.class, "/");

        // sprinkle in a few filters to demonstrate behaviors
        context.addFilter(TestFilter.class, "/test/*", EnumSet.of(REQUEST));
        context.addFilter(TestFilter.class, "*.test", EnumSet.of(REQUEST, ASYNC));

        // and a few listeners to show other ways of working with servlets
        context.getServletHandler().addListener(new ListenerHolder(InitListener.class));
        context.getServletHandler().addListener(new ListenerHolder(RequestListener.class));

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"));

        Server server = createServer(port, new PathResource(tempDir));

        server.start();
        server.dumpStdErr();
        server.join();
    }

    public static class TestFilter implements Filter
    {
        @Override
        public void init(FilterConfig filterConfig)
        {
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
        {
            if (response instanceof HttpServletResponse)
            {
                HttpServletResponse httpServletResponse = (HttpServletResponse)response;
                httpServletResponse.setHeader("X-TestFilter", "true");
            }
            chain.doFilter(request, response);
        }

        @Override
        public void destroy()
        {

        }
    }

    public static class InitListener implements ServletContextListener
    {
        @Override
        public void contextInitialized(ServletContextEvent sce)
        {
            sce.getServletContext().setAttribute("X-Init", "true");
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce)
        {
        }
    }

    public static class RequestListener implements ServletRequestListener
    {
        @Override
        public void requestInitialized(ServletRequestEvent sre)
        {
            sre.getServletRequest().setAttribute("X-ReqListener", "true");
        }

        @Override
        public void requestDestroyed(ServletRequestEvent sre)
        {
        }
    }
}

Embedding Web Applications

A WebAppContext is an extension of a ServletContextHandler that uses the standard layout and web.xml to configure the servlets, filters and other features from a web.xml and/or annotations. The following OneWebApp example configures the Jetty test webapp. Web applications can use resources the container provides, and in this case a LoginService is needed and also configured:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.File;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configurations;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebApp
{
    public static Server createServer(int port)
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // The WebAppContext is the entity that controls the environment in
        // which a web application lives and breathes. In this example the
        // context path is being set to "/" so it is suitable for serving root
        // context requests and then we see it setting the location of the war.
        // A whole host of other configurations are available, ranging from
        // configuring to support annotation scanning in the webapp (through
        // PlusConfiguration) to choosing where the webapp will unpack itself.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        File warFile = JettyDistribution.resolve("demo-base/webapps/async-rest.war").toFile();
        webapp.setWar(warFile.getAbsolutePath());

        // A WebAppContext is a ContextHandler as well so it needs to be set to
        // the server so it is aware of where to send the appropriate requests.
        server.setHandler(webapp);
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        Configurations.setServerDefault(server);

        // Start things up!
        server.start();

        server.dumpStdErr();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        server.join();
    }
}

Like Jetty XML

The typical way to configure an instance of the Jetty server is via jetty.xml and associated configuration files. However the Jetty XML configuration format is just a simple rendering of what you can do in code; it is very simple to write embedded code that does precisely what the jetty.xml configuration does. The LikeJettyXml example following renders in code the behavior obtained from the configuration files:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.FileNotFoundException;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
import org.eclipse.jetty.deploy.bindings.DebugListenerBinding;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.rewrite.handler.MsieSslRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.rewrite.handler.ValidUrlRule;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.AsyncRequestLogWriter;
import org.eclipse.jetty.server.CustomRequestLog;
import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnectionStatistics;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.webapp.Configurations;

/**
 * Starts the Jetty Distribution's demo-base directory using entirely
 * embedded jetty techniques.
 */
public class LikeJettyXml
{
    public static Server createServer(int port, int securePort, boolean addDebugListener) throws Exception
    {
        // Path to as-built jetty-distribution directory
        Path jettyHomeBuild = JettyDistribution.get();

        // Find jetty home and base directories
        String homePath = System.getProperty("jetty.home", jettyHomeBuild.toString());
        Path homeDir = Paths.get(homePath);

        String basePath = System.getProperty("jetty.base", homeDir.resolve("demo-base").toString());
        Path baseDir = Paths.get(basePath);

        // Configure jetty.home and jetty.base system properties
        String jettyHome = homeDir.toAbsolutePath().toString();
        String jettyBase = baseDir.toAbsolutePath().toString();
        System.setProperty("jetty.home", jettyHome);
        System.setProperty("jetty.base", jettyBase);

        // === jetty.xml ===
        // Setup Threadpool
        QueuedThreadPool threadPool = new QueuedThreadPool();
        threadPool.setMaxThreads(500);

        // Server
        Server server = new Server(threadPool);

        // Scheduler
        server.addBean(new ScheduledExecutorScheduler(null, false));

        // HTTP Configuration
        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSecureScheme("https");
        httpConfig.setSecurePort(securePort);
        httpConfig.setOutputBufferSize(32768);
        httpConfig.setRequestHeaderSize(8192);
        httpConfig.setResponseHeaderSize(8192);
        httpConfig.setSendServerVersion(true);
        httpConfig.setSendDateHeader(false);
        // httpConfig.addCustomizer(new ForwardedRequestCustomizer());

        // Handler Structure
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        server.setHandler(new HandlerList(contexts, new DefaultHandler()));

        // === jetty-jmx.xml ===
        MBeanContainer mbContainer = new MBeanContainer(
            ManagementFactory.getPlatformMBeanServer());
        server.addBean(mbContainer);

        // === jetty-http.xml ===
        ServerConnector http = new ServerConnector(server,
            new HttpConnectionFactory(httpConfig));
        http.setPort(port);
        http.setIdleTimeout(30000);
        server.addConnector(http);

        // === jetty-https.xml ===
        // SSL Context Factory
        Path keystorePath = Paths.get("src/main/resources/etc/keystore.p12").toAbsolutePath();
        if (!Files.exists(keystorePath))
            throw new FileNotFoundException(keystorePath.toString());
        SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
        sslContextFactory.setKeyStorePath(keystorePath.toString());
        sslContextFactory.setKeyStorePassword("storepwd");
        sslContextFactory.setTrustStorePath(keystorePath.toString());
        sslContextFactory.setTrustStorePassword("storepwd");

        // SSL HTTP Configuration
        HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
        httpsConfig.addCustomizer(new SecureRequestCustomizer());

        // SSL Connector
        ServerConnector sslConnector = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
            new HttpConnectionFactory(httpsConfig));
        sslConnector.setPort(securePort);
        server.addConnector(sslConnector);

        // === jetty-deploy.xml ===
        DeploymentManager deployer = new DeploymentManager();
        if (addDebugListener)
        {
            DebugListener debug = new DebugListener(System.err, true, true, true);
            server.addBean(debug);
            deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
        }
        deployer.setContexts(contexts);
        deployer.setContextAttribute(
            "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
            ".*/jetty-servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");

        WebAppProvider webAppProvider = new WebAppProvider();
        webAppProvider.setMonitoredDirName(jettyBase + "/webapps");
        webAppProvider.setDefaultsDescriptor(jettyHome + "/etc/webdefault.xml");
        webAppProvider.setScanInterval(1);
        webAppProvider.setExtractWars(true);
        webAppProvider.setConfigurationManager(new PropertiesConfigurationManager());

        deployer.addAppProvider(webAppProvider);
        server.addBean(deployer);

        // === setup jetty plus ==
        Configurations.setServerDefault(server).add(new EnvConfiguration(), new PlusConfiguration(), new AnnotationConfiguration());

        // === jetty-stats.xml ===
        StatisticsHandler stats = new StatisticsHandler();
        stats.setHandler(server.getHandler());
        server.setHandler(stats);
        ServerConnectionStatistics.addToAllConnectors(server);

        // === Rewrite Handler
        RewriteHandler rewrite = new RewriteHandler();
        rewrite.setHandler(server.getHandler());
        server.setHandler(rewrite);
        rewrite.addRule(new MsieSslRule());
        rewrite.addRule(new ValidUrlRule());

        // === jetty-requestlog.xml ===
        AsyncRequestLogWriter logWriter = new AsyncRequestLogWriter(jettyHome + "/logs/yyyy_mm_dd.request.log");
        logWriter.setFilenameDateFormat("yyyy_MM_dd");
        logWriter.setRetainDays(90);
        logWriter.setTimeZone("GMT");
        CustomRequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT + " \"%C\"");
        server.setRequestLog(requestLog);

        // === jetty-lowresources.xml ===
        LowResourceMonitor lowResourcesMonitor = new LowResourceMonitor(server);
        lowResourcesMonitor.setPeriod(1000);
        lowResourcesMonitor.setLowResourcesIdleTimeout(200);
        lowResourcesMonitor.setMonitorThreads(true);
        lowResourcesMonitor.setMaxMemory(0);
        lowResourcesMonitor.setMaxLowResourcesTime(5000);
        server.addBean(lowResourcesMonitor);

        // === test-realm.xml ===
        HashLoginService login = new HashLoginService();
        login.setName("Test Realm");
        login.setConfig(jettyBase + "/etc/realm.properties");
        login.setHotReload(false);
        server.addBean(login);

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443);
        Server server = createServer(port, securePort, true);

        // Extra options
        server.setDumpAfterStart(true);
        server.setDumpBeforeStop(false);
        server.setStopAtShutdown(true);

        // Start the server
        server.start();
        server.join();
    }
}

Embedded Examples

Jetty has a rich history of being embedded into a wide variety of applications. In this section we will walk you through a number of our simple examples under our embedded-jetty-examples project in our git repository.

These files are pulled directly from our git repository when this document is generated. If the line numbers do not line up feel free to fix this documentation in github and give us a pull request, or at least open an issue to notify us of the discrepancy.

Simple File Server

This example shows how to create a simple file server in Jetty. It is perfectly suitable for test cases where you need an actual web server to obtain a file from, it could easily be configured to serve files from a directory under src/test/resources. Note that this does not have any logic for caching of files, either within the server or setting the appropriate headers on the response. It is simply a few lines that illustrate how easy it is to serve out some files.

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;

/**
 * Simple Jetty FileServer.
 * This is a simple example of Jetty configured as a FileServer.
 */
public class FileServer
{
    public static Server createServer(int port, Resource baseResource) throws Exception
    {
        // Create a basic Jetty server object that will listen on port 8080.  Note that if you set this to port 0
        // then a randomly available port will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // Create the ResourceHandler. It is the object that will actually handle the request for a given file. It is
        // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples.
        ResourceHandler resourceHandler = new ResourceHandler();

        // Configure the ResourceHandler. Setting the resource base indicates where the files should be served out of.
        // In this example it is the current directory but it can be configured to anything that the jvm has access to.
        resourceHandler.setDirectoriesListed(true);
        resourceHandler.setWelcomeFiles(new String[]{"index.html"});
        resourceHandler.setBaseResource(baseResource);

        // Add the ResourceHandler to the server.
        server.setHandler(new HandlerList(resourceHandler, new DefaultHandler()));

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Path userDir = Paths.get(System.getProperty("user.dir"));
        PathResource pathResource = new PathResource(userDir);

        Server server = createServer(port, pathResource);

        // Start things up! By using the server.join() the server thread will join with the current thread.
        // See "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()" for more details.
        server.start();
        server.join();
    }
}
Run it!

After you have started things up you should be able to navigate to http://localhost:8080/index.html (assuming one is in the resource base directory) and you are good to go.

Maven Coordinates

To use this example in your project you will need the following Maven dependencies declared.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>

Split File Server

This example builds on the Simple File Server to show how chaining multiple ResourceHandlers together can let you aggregate multiple directories to serve content on a single path and how you can link these together with ContextHandlers.

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.nio.file.Paths;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;

/**
 * A {@link ContextHandlerCollection} handler may be used to direct a request to
 * a specific Context. The URI path prefix and optional virtual host is used to
 * select the context.
 */
public class SplitFileServer
{
    public static Server createServer(int port, Resource baseResource0, Resource baseResource1)
    {
        // Create the Server object and a corresponding ServerConnector and then
        // set the port for the connector. In this example the server will
        // listen on port 8080. If you set this to port 0 then when the server
        // has been started you can called connector.getLocalPort() to
        // programmatically get the port the server started on.
        Server server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(port);
        server.addConnector(connector);

        // Create a Context Handler and ResourceHandler. The ContextHandler is
        // getting set to "/" path but this could be anything you like for
        // building out your url. Note how we are setting the ResourceBase using
        // our jetty maven testing utilities to get the proper resource
        // directory, you needn't use these, you simply need to supply the paths
        // you are looking to serve content from.
        ResourceHandler rh0 = new ResourceHandler();
        rh0.setDirectoriesListed(false);

        ContextHandler context0 = new ContextHandler();
        context0.setContextPath("/");
        context0.setBaseResource(baseResource0);
        context0.setHandler(rh0);

        // Rinse and repeat the previous item, only specifying a different
        // resource base.
        ResourceHandler rh1 = new ResourceHandler();
        rh1.setDirectoriesListed(false);

        ContextHandler context1 = new ContextHandler();
        context1.setContextPath("/");
        context1.setBaseResource(baseResource1);
        context1.setHandler(rh1);

        // Create a ContextHandlerCollection and set the context handlers to it.
        // This will let jetty process urls against the declared contexts in
        // order to match up content.
        ContextHandlerCollection contexts = new ContextHandlerCollection(
            context0, context1
        );
        server.setHandler(contexts);
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Resource resource0 = new PathResource(Paths.get("src/test/resources/dir0"));
        Resource resource1 = new PathResource(Paths.get("src/test/resources/dir1"));

        Server server = createServer(port, resource0, resource1);

        // Dump the server state
        server.setDumpAfterStart(true);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        server.join();
    }
}
Run it!

After you have started things up you should be able to navigate to http://localhost:8090/index.html (assuming one is in the resource base directory) and you are good to go. Any requests for files will be looked for in the first resource handler, then the second, and so on and so forth.

Maven Coordinates

To use this example as is in your project you will need the following maven dependencies declared. We would recommend not using the toolchain dependency in your actual application.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty.toolchain</groupId>
  <artifactId>jetty-test-helper</artifactId>
  <version>2.2</version>
</dependency>

Multiple Connectors

This example shows how to configure Jetty to use multiple connectors, specifically so it can process both http and https requests. Since the meat of this example is the server and connector configuration it only uses a simple HelloHandler but this example should be easily merged with other examples like those deploying servlets or webapps.

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
 * A Jetty server with multiple connectors.
 */
public class ManyConnectors
{
    public static Server createServer(int plainPort, int securePort) throws Exception
    {
        // Since this example shows off SSL configuration, we need a keystore
        // with the appropriate key.
        Path keystorePath = Paths.get("src/main/resources/etc/keystore.p12").toAbsolutePath();
        if (!Files.exists(keystorePath))
            throw new FileNotFoundException(keystorePath.toString());

        // Create a basic jetty server object without declaring the port. Since
        // we are configuring connectors directly we'll be setting ports on
        // those connectors.
        Server server = new Server();

        // HTTP Configuration
        // HttpConfiguration is a collection of configuration information
        // appropriate for http and https. The default scheme for http is
        // <code>http</code> of course, as the default for secured http is
        // <code>https</code> but we show setting the scheme to show it can be
        // done. The port for secured communication is also set here.
        HttpConfiguration httpConfig = new HttpConfiguration();
        httpConfig.setSecureScheme("https");
        httpConfig.setSecurePort(securePort);
        httpConfig.setOutputBufferSize(32768);

        // HTTP connector
        // The first server connector we create is the one for http, passing in
        // the http configuration we configured above so it can get things like
        // the output buffer size, etc. We also set the port (8080) and
        // configure an idle timeout.
        ServerConnector http = new ServerConnector(server,
            new HttpConnectionFactory(httpConfig));
        http.setPort(plainPort);
        http.setIdleTimeout(30000);

        // SSL Context Factory for HTTPS
        // SSL requires a certificate so we configure a factory for ssl contents
        // with information pointing to what keystore the ssl connection needs
        // to know about. Much more configuration is available the ssl context,
        // including things like choosing the particular certificate out of a
        // keystore to be used.

        SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
        sslContextFactory.setKeyStorePath(keystorePath.toString());
        sslContextFactory.setKeyStorePassword("storepwd");

        // OPTIONAL: Un-comment the following to use Conscrypt for SSL instead of
        // the native JSSE implementation.

        //Security.addProvider(new OpenSSLProvider());
        //sslContextFactory.setProvider("Conscrypt");

        // HTTPS Configuration
        // A new HttpConfiguration object is needed for the next connector and
        // you can pass the old one as an argument to effectively clone the
        // contents. On this HttpConfiguration object we add a
        // SecureRequestCustomizer which is how a new connector is able to
        // resolve the https connection before handing control over to the Jetty
        // Server.
        HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
        SecureRequestCustomizer src = new SecureRequestCustomizer();
        src.setStsMaxAge(2000);
        src.setStsIncludeSubDomains(true);
        httpsConfig.addCustomizer(src);

        // HTTPS connector
        // We create a second ServerConnector, passing in the http configuration
        // we just made along with the previously created ssl context factory.
        // Next we set the port and a longer idle timeout.
        ServerConnector https = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
            new HttpConnectionFactory(httpsConfig));
        https.setPort(securePort);
        https.setIdleTimeout(500000);

        // Here you see the server having multiple connectors registered with
        // it, now requests can flow into the server from both http and https
        // urls to their respective ports and be processed accordingly by jetty.
        // A simple handler is also registered with the server so the example
        // has something to pass requests off to.

        // Set the connectors
        server.setConnectors(new Connector[]{http, https});

        // Set a handler
        server.setHandler(new HelloHandler());
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        int securePort = ExampleUtil.getPort(args, "jetty.https.port", 8443);
        Server server = createServer(port, securePort);
        // Start the server
        server.start();
        server.dumpStdErr();
        server.join();
    }
}
Walkthrough

Start things up! By using the server.join() the server thread will join with the current thread. See Thread.join() for more details.

Maven Coordinates

To use this example in your project you will need the following Maven dependencies declared.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-security</artifactId>
  <version>${project.version}</version>
</dependency>

Secured Hello Handler

This example shows how to wrap one handler with another one that handles security. We have a simple Hello Handler that just return a greeting but add on the restriction that to get this greeting you must authenticate. Another thing to remember is that this example uses the ConstraintSecurityHandler which is what supports the security mappings inside of the servlet api, it could be easier to show just the SecurityHandler usage, but the constraint provides more configuration power. If you don’t need that you can drop the Constraint bits and use just the SecurityHandler.

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.FileNotFoundException;
import java.net.URL;
import java.util.Collections;

import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.security.Constraint;

public class SecuredHelloHandler
{
    public static Server createServer(int port) throws FileNotFoundException
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // Since this example is for our test webapp, we need to setup a
        // LoginService so this shows how to create a very simple hashmap based
        // one. The name of the LoginService needs to correspond to what is
        // configured a webapp's web.xml and since it has a lifecycle of its own
        // we register it as a bean with the Jetty server object so it can be
        // started and stopped according to the lifecycle of the server itself.
        // In this example the name can be whatever you like since we are not
        // dealing with webapp realms.
        String realmResourceName = "etc/realm.properties";
        ClassLoader classLoader = SecuredHelloHandler.class.getClassLoader();
        URL realmProps = classLoader.getResource(realmResourceName);
        if (realmProps == null)
            throw new FileNotFoundException("Unable to find " + realmResourceName);

        LoginService loginService = new HashLoginService("MyRealm",
            realmProps.toExternalForm());
        server.addBean(loginService);

        // A security handler is a jetty handler that secures content behind a
        // particular portion of a url space. The ConstraintSecurityHandler is a
        // more specialized handler that allows matching of urls to different
        // constraints. The server sets this as the first handler in the chain,
        // effectively applying these constraints to all subsequent handlers in
        // the chain.
        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
        server.setHandler(security);

        // This constraint requires authentication and in addition that an
        // authenticated user be a member of a given set of roles for
        // authorization purposes.
        Constraint constraint = new Constraint();
        constraint.setName("auth");
        constraint.setAuthenticate(true);
        constraint.setRoles(new String[]{"user", "admin"});

        // Binds a url pattern with the previously created constraint. The roles
        // for this constraint mapping are mined from the Constraint itself
        // although methods exist to declare and bind roles separately as well.
        ConstraintMapping mapping = new ConstraintMapping();
        mapping.setPathSpec("/*");
        mapping.setConstraint(constraint);

        // First you see the constraint mapping being applied to the handler as
        // a singleton list, however you can passing in as many security
        // constraint mappings as you like so long as they follow the mapping
        // requirements of the servlet api. Next we set a BasicAuthenticator
        // instance which is the object that actually checks the credentials
        // followed by the LoginService which is the store of known users, etc.
        security.setConstraintMappings(Collections.singletonList(mapping));
        security.setAuthenticator(new BasicAuthenticator());
        security.setLoginService(loginService);

        // The Hello Handler is the handler we are securing so we create one,
        // and then set it as the handler on the
        // security handler to complain the simple handler chain.
        HelloHandler hh = new HelloHandler();

        // chain the hello handler into the security handler
        security.setHandler(hh);

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        server.join();
    }
}
Run it!

After you have started things up you should be able to navigate to http://localhost:8080/index.html (assuming one is in the resource base directory) and you are good to go.

The Realm Properties File
#
# This file defines users passwords and roles for a HashUserRealm
#
# The format is
#  <username>: <password>[,<rolename> ...]
#
# Passwords may be clear text, obfuscated or checksummed.  The class
# org.eclipse.jetty.util.security.Password should be used to generate obfuscated
# passwords or password checksums
#
# If DIGEST Authentication is used, the password must be in a recoverable
# format, either plain text or OBF:.
#
jetty: MD5:164c88b302622e17050af52c89945d44,user
admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
other: OBF:1xmk1w261u9r1w1c1xmq,user
plain: plain,user
user: password,user

# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
Maven Coordinates

To use this example in your project you will need the following Maven dependencies declared.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>

Minimal Servlet

This example shows the bare minimum required for deploying a servlet into Jetty. Note that this is strictly a servlet, not a servlet in the context of a web application, that example comes later. This is purely just a servlet deployed and mounted on a context and able to process requests. This example is excellent for situations where you have a simple servlet that you need to unit test, just mount it on a context and issue requests using your favorite http client library (like our Jetty client found in [client-http]).

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class MinimalServlets
{

    public static Server createServer(int port)
    {
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // The ServletHandler is a dead simple way to create a context handler
        // that is backed by an instance of a Servlet.
        // This handler then needs to be registered with the Server object.
        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        // Passing in the class for the Servlet allows jetty to instantiate an
        // instance of that Servlet and mount it on a given context path.

        // IMPORTANT:
        // This is a raw Servlet, not a Servlet that has been configured
        // through a web.xml @WebServlet annotation, or anything similar.
        handler.addServletWithMapping(HelloServlet.class, "/*");

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server thread is done executing.
        server.join();
    }

    @SuppressWarnings("serial")
    public static class HelloServlet extends HttpServlet
    {
        @Override
        protected void doGet(HttpServletRequest request,
                             HttpServletResponse response) throws IOException
        {
            response.setStatus(HttpServletResponse.SC_OK);
            response.setContentType("text/html");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
        }
    }
}
Walkthrough

Start things up! By using the server.join() the server thread will join with the current thread. See Thread.join() for more details.

It is really simple to create useful servlets for testing behaviors. Sometimes you need a http server to run a unit test against that will return test content and wiring up a servlet like this makes it trivial.

After you have started things up you should be able to navigate to http://localhost:8080/ and you are good to go.

Maven Coordinates

To use this example in your project you will need the following Maven dependencies declared.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlet</artifactId>
  <version>${project.version}</version>
</dependency>

Web Application

This example shows how to deploy a simple webapp with an embedded instance of Jetty. This is useful when you want to manage the lifecycle of a server programmatically, either within a production application or as a simple way to deploying and debugging a full scale application deployment. In many ways it is easier then traditional deployment since you control the classpath yourself, making this easy to wire up in a test case in Maven and issue requests using your favorite http client library (like our Jetty client found in [client-http]).

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.File;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configurations;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebApp
{
    public static Server createServer(int port)
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // The WebAppContext is the entity that controls the environment in
        // which a web application lives and breathes. In this example the
        // context path is being set to "/" so it is suitable for serving root
        // context requests and then we see it setting the location of the war.
        // A whole host of other configurations are available, ranging from
        // configuring to support annotation scanning in the webapp (through
        // PlusConfiguration) to choosing where the webapp will unpack itself.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        File warFile = JettyDistribution.resolve("demo-base/webapps/async-rest.war").toFile();
        webapp.setWar(warFile.getAbsolutePath());

        // A WebAppContext is a ContextHandler as well so it needs to be set to
        // the server so it is aware of where to send the appropriate requests.
        server.setHandler(webapp);
        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        Configurations.setServerDefault(server);

        // Start things up!
        server.start();

        server.dumpStdErr();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        server.join();
    }
}
Run it!

After you have started things up you should be able to navigate to http://localhost:8080/ and you are good to go.

Maven Coordinates

To use this example in your project you will need the following Maven dependencies declared.

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-webapp</artifactId>
  <version>${project.version}</version>
</dependency>

Web Application with JSP

This example is very similar to the one in the previous section, although it enables the embedded webapp to use JSPs. As of jetty-9.2, we use the JSP engine from Apache, which relies on a Servlet Specification 3.1 style ServletContainerInitializer to initialize itself. To get this to work with Jetty, you need to enable annotations processing, as shown in this example code:

//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.FileNotFoundException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebAppWithJsp
{
    public static Server createServer(int port) throws FileNotFoundException
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then
        // a randomly available port will be assigned that you can either look
        // in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(port);

        // The WebAppContext is the entity that controls the environment in
        // which a web application lives and breathes.
        // In this example the context path is being set to "/" so it
        // is suitable for serving root context
        // requests and then we see it setting the location of the war.
        // A whole host of other configurations are
        // available, ranging from configuring to support annotation scanning in
        // the webapp (through PlusConfiguration), to choosing where
        // the webapp will unpack itself.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        Path warFile = JettyDistribution.resolve("demo-base/webapps/test.war");
        if (!Files.exists(warFile))
        {
            throw new FileNotFoundException(warFile.toString());
        }
        webapp.setWarResource(new PathResource(warFile));
        webapp.setExtractWAR(true);

        // This webapp will use jsps and jstl. We need to enable the
        // AnnotationConfiguration in order to correctly
        // set up the jsp container
        webapp.addConfiguration(new AnnotationConfiguration());

        // Set the ContainerIncludeJarPattern so that jetty examines these
        // container-path jars for tlds, web-fragments etc.
        // If you omit the jar that contains the jstl .tlds, the jsp engine will
        // scan for them instead.
        webapp.setAttribute(
            "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
            ".*/jetty-servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$");

        // A WebAppContext is a ContextHandler as well so it needs to be set to
        // the server so it is aware of where to
        // send the appropriate requests.
        server.setHandler(webapp);

        // Configure a LoginService.
        // Since this example is for our test webapp, we need to setup a
        // LoginService so this shows how to create a very simple hashmap based
        // one. The name of the LoginService needs to correspond to what is
        // configured in the webapp's web.xml and since it has a lifecycle of
        // its own we register it as a bean with the Jetty server object so it
        // can be started and stopped according to the lifecycle of the server
        // itself.
        String realmResourceName = "etc/realm.properties";
        ClassLoader classLoader = OneWebAppWithJsp.class.getClassLoader();
        URL realmProps = classLoader.getResource(realmResourceName);
        if (realmProps == null)
            throw new FileNotFoundException("Unable to find " + realmResourceName);

        HashLoginService loginService = new HashLoginService();
        loginService.setName("Test Realm");
        loginService.setConfig(realmProps.toExternalForm());
        server.addBean(loginService);

        return server;
    }

    public static void main(String[] args) throws Exception
    {
        int port = ExampleUtil.getPort(args, "jetty.http.port", 8080);
        Server server = createServer(port);

        // Start things up!
        server.start();

        server.dumpStdErr();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        server.join();
    }
}
Run it!

After you have started things up you should be able to navigate to http://localhost:8080/jsp/ and click on any of the links to jsps.

Maven Coordinates

To use this example in your project, you will need the following Maven dependencies declared, in addition to those from the previous section:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-annotations</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>apache-jsp</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>apache-jstl</artifactId>
  <version>${project.version}</version>
</dependency>

Adding Examples

If you would like to add an example to this list, fork the documentation project from github (see the blue bar at the bottom of this page) and add the new page. Feel free to add the example contents directly as a [source.java] and we will take it from there.

If you feel and example is missing, feel free to open a bug to ask for it. No guarantees, but the more helpful and demonstrative it is the better.

Maven and Jetty

This chapter explains how to use Jetty with Maven and the Jetty Maven plugin.

Using Maven

Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information.

It is an ideal tool to build a web application project, and such projects can use the jetty-maven-plugin to easily run the web application and save time in development. You can also use Maven to build, test and run a project which embeds Jetty.

Use of Maven and the jetty-maven-plugin is not required. Using Maven for Jetty implementations is a popular choice, but users encouraged to manage their projects in whatever way suits their needs. Other popular tools include Ant and Gradle.

First we’ll have a look at a very simple HelloWorld java application that embeds Jetty, then a simple webapp which makes use of the jetty-maven-plugin to speed up the development cycle.

Using Embedded Jetty with Maven

To understand the basic operations of building and running against Jetty, first review:

Maven uses convention over configuration, so it is best to use the project structure Maven recommends. You can use archetypes to quickly setup Maven projects, but we will set up the structure manually for this simple tutorial example:

> mkdir JettyMavenHelloWorld
> cd JettyMavenHelloWorld
> mkdir -p src/main/java/org/example
Creating the HelloWorld Class

Use an editor to create the file src/main/java/org/example/HelloWorld.java with the following contents:

package org.example;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.IOException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloWorld extends AbstractHandler
{
    public void handle(String target,
                       Request baseRequest,
                       HttpServletRequest request,
                       HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("<h1>Hello World</h1>");
    }

    public static void main(String[] args) throws Exception
    {
        Server server = new Server(8080);
        server.setHandler(new HelloWorld());

        server.start();
        server.join();
    }
}
Creating the POM Descriptor

The pom.xml file declares the project name and its dependencies. Use an editor to create the file pom.xml in the JettyMavenHelloWorld directory with the following contents:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>hello-world</artifactId>
  <version>0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Jetty HelloWorld</name>

  <properties>
      <!-- Adapt this to a version found on
           https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-maven-plugin/
        -->
      <jettyVersion>10.0.0.beta1</jettyVersion>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-server</artifactId>
      <version>${jettyVersion}</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
          <execution><goals><goal>java</goal></goals></execution>
        </executions>
        <configuration>
          <mainClass>org.example.HelloWorld</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
Building and Running Embedded HelloWorld

You can now compile and execute the HelloWorld class by using these commands:

> mvn clean compile exec:java

You can point your browser to http://localhost:8080 to see the Hello World page. You can observe what Maven is doing for you behind the scenes by using the mvn dependency:tree command, which reveals the transitive dependency resolved and downloaded as:

> mvn dependency:tree
[INFO] Scanning for projects...
...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Jetty HelloWorld 0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ hello-world ---
...
[INFO] org.example:hello-world:jar:0.1-SNAPSHOT
[INFO] \- org.eclipse.jetty:jetty-server:jar:9.3.9.v20160517:compile
[INFO]    +- javax.servlet:javax.servlet-api:jar:3.1.0:compile
[INFO]    +- org.eclipse.jetty:jetty-http:jar:9.3.9.v20160517:compile
[INFO]    |  \- org.eclipse.jetty:jetty-util:jar:9.3.9.v20160517:compile
[INFO]    \- org.eclipse.jetty:jetty-io:jar:9.3.9.v20160517:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.145 s
[INFO] Finished at: 2016-08-01T13:46:42-04:00
[INFO] Final Memory: 15M/209M
[INFO] ------------------------------------------------------------------------

Developing a Standard WebApp with Jetty and Maven

The previous section demonstrated how to use Maven with an application that embeds Jetty. Now we will examine instead how to develop a standard webapp with Maven and Jetty. First create the Maven structure (you can use the maven webapp archetype instead if you prefer):

> mkdir JettyMavenHelloWarApp
> cd JettyMavenHelloWebApp
> mkdir -p src/main/java/org/example
> mkdir -p src/main/webapp/WEB-INF
Creating a Servlet

Use an editor to create the file src/main/java/org/example/HelloServlet.java with the following contents:

package org.example;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet
{
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        response.setContentType("text/html");
        response.setStatus(HttpServletResponse.SC_OK);
        response.getWriter().println("<h1>Hello Servlet</h1>");
        response.getWriter().println("session=" + request.getSession(true).getId());
    }
}

You need to declare this servlet in the deployment descriptor, so create the file src/main/webapp/WEB-INF/web.xml and add the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<web-app
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
   metadata-complete="false"
   version="3.1">

  <servlet>
    <servlet-name>Hello</servlet-name>
    <servlet-class>org.example.HelloServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Hello</servlet-name>
    <url-pattern>/hello/*</url-pattern>
  </servlet-mapping>

</web-app>
Creating the POM Descriptor

The pom.xml file declares the project name and its dependencies. Use an editor to create the file pom.xml with the following contents in the JettyMavenHelloWarApp directory, noting particularly the declaration of the jetty-maven-plugin:

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>hello-world</artifactId>
  <version>0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>Jetty HelloWorld WebApp</name>

  <properties>
      <jettyVersion>10.0.0.beta1</jettyVersion>
  </properties>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>${jettyVersion}</version>
      </plugin>
    </plugins>
  </build>

</project>
Building and Running the Web Application

Now you can both build and run the web application without needing to assemble it into a war by using the jetty-maven-plugin via the command:

> mvn jetty:run

You can see the static and dynamic content at http://localhost:8080/hello

There are a great deal of configuration options available for the jetty-maven-plugin to help you build and run your webapp. The full reference is at Configuring the Jetty Maven Plugin.

Building a WAR file

You can create a Web Application Archive (WAR) file from the project with the command:

> mvn package

The resulting war file is in the target directory and may be deployed on any standard servlet server, including Jetty.

Configuring the Jetty Maven Plugin

The Jetty Maven plugin is useful for rapid development and testing. It can optionally periodically scan your project for changes and automatically redeploy the webapp if any are found. This makes the development cycle more productive by eliminating the build and deploy steps: you use your IDE to make changes to the project, and the running web container automatically picks them up, allowing you to test them straight away.

The plugin has been substantially re-architected in jetty-10 to:

  • have less goals

  • make deployment modes (embedded, forked or to a jetty distribution) apply uniformly across all goals

  • simplify configuration options

  • make the purpose and operation of each goal clearer

  • rearchitect with composition rather than inheritance to make future extensions easier

There are now only 4 goals to run a webapp in jetty:

Plus two utility goals:

jetty:run and jetty:start are alike in that they both run an unassembled webapp in jetty,however jetty:run is designed to be used at the command line, whereas jetty:start is specifically designed to be bound to execution phases in the build lifecycle. jetty:run will pause maven while jetty is running, echoing all output to the console, and then stop maven when jetty exits. jetty:start will not pause maven, will write all its output to a file, and will not stop maven when jetty exits.

jetty:run-war and jetty:start-war are similar in that they both run an assembled war file in jetty. However, jetty:run-war is designed to be run at the command line, whereas jetty:start-war is specifically designed to be bound to execution phases in the build lifecycle. jetty:run-war will pause maven while jetty is running, echoing all output to the console, and then stop maven when jetty exits. jetty:start-war will not not pause maven, will write all its output to a file, and will not stop maven when jetty exits.

While the Jetty Maven Plugin can be very useful for development we do not recommend its use in a production capacity. In order for the plugin to work it needs to leverage many internal Maven apis and Maven itself it not a production deployment tool. We recommend either the traditional distribution deployment approach or using embedded Jetty.

Quick Start: Get Up and Running

First, add jetty-maven-plugin to your pom.xml definition:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
</plugin>

Then, from the same directory as your root pom.xml, type:

mvn jetty:run

This starts Jetty and serves up your project on http://localhost:8080/.

Jetty will continue to run until you stop it. By default, it will not automatically restart your webapp: you can force a redeploy by hitting the Enter key. Set a non-zero <scan> value to have jetty scan your webapp for changes and automatically redeploy.

You can terminate the plugin with a ctrl-c in the terminal window where it is running.

The classpath of the running Jetty instance and its deployed webapp are managed by Maven, and may not be exactly what you expect. For example: a webapp’s dependent jars might be referenced via the local repository, or other projects in the reactor, not the WEB-INF/lib directory.

Supported Goals

The goals prefixed with "run" are designed to be used at the command line. They first run a maven build on your project to ensure at least the classes are all built. They then start jetty and pause the maven build process until jetty is manually terminated, at which time the build will also be terminated. Jetty can scan various files in your project for changes and redeploy the webapp as necessary, or you can choose to manually trigger a redeploy if you prefer. All output from jetty is echoed to the console.

The goals prefixed with "start" are designed to be used with build lifecycle bindings in the pom, and not at the command line. No part of your project will be rebuilt by invoking these goals - you should ensure that your bind the execution to a build phase where all necessary parts of your project have been built. Maven will start and terminate jetty at the appropriate points in the build lifecycle, continuing with the build. Jetty will not scan any files in your project for changes, and your webapp will not be redeployed either automatically or manually. Output from jetty is directed to a file in the target directory.

To see a list of all goals supported by the Jetty Maven plugin, do:

mvn jetty:help

To see the detailed list of parameters that can be configured for a particular goal, in addition to its description, do:

mvn jetty:help -Ddetail=true -Dgoal= <goal name>

Deployment Modes

All of the "run" and "start" goals can deploy your webapp either into the running maven process, or forked into a new child process, or forked into a jetty distribution on disk.

This is controlled by setting the deployMode configuration parameter in the pom, but can also be set by defining the maven property 'jetty.deployMode'.

Embedded

deployMode of EMBED. This is the "classic" jetty maven plugin deployment mode, running in-process with maven. This is the default mode.

These extra configuration parameters are available:

httpConnector

Optional. NOTE to configure a https connector, you will need to use xml configuration files instead, setting the jettyXmls parameter. This parameter can only be used to configure a standard http connector. If not specified, Jetty will create a ServerConnector instance listening on port 8080. You can change this default port number by using the system property jetty.http.port on the command line, for example, mvn -Djetty.http.port=9999 jetty:run. Alternatively, you can use this configuration element to set up the information for the ServerConnector. The following are the valid configuration sub-elements:

port

The port number for the connector to listen on. By default it is 8080.

host

The particular interface for the connector to listen on. By default, all interfaces.

name

The name of the connector, which is useful for configuring contexts to respond only on particular connectors.

idleTimeout

Maximum idle time for a connection. You could instead configure the connectors in a standard jetty xml config file and put its location into the jettyXml parameter. Note that since Jetty 9.0 it is no longer possible to configure a https connector directly in the pom.xml: you need to use jetty xml config files to do it.

loginServices

Optional. A list of org.eclipse.jetty.security.LoginService implementations. Note that there is no default realm. If you use a realm in your web.xml you can specify a corresponding realm here. You could instead configure the login services in a jetty xml file and add its location to the jettyXml parameter. See Configuring Security.

requestLog

Optional. An implementation of the org.eclipse.jetty.server.RequestLog request log interface. An implementation that respects the NCSA format is available as org.eclipse.jetty.server.NCSARequestLog. There are three other ways to configure the RequestLog:

  • In a jetty xml config file, as specified in the jettyXml parameter.

  • In a context xml config file, as specified in the contextXml parameter.

  • In the webApp element.

See Configuring Request Logs for more information.

server

Optional as of Jetty 9.3.1. This would configure an instance of the org.eclipse.jetty.server.Server for the plugin to use, however it is usually not necessary to configure this, as the plugin will automatically configure one for you. In particular, if you use the jettyXmls element, then you generally don’t want to define this element, as you are probably using the jettyXmls file/s to configure up a Server with a special constructor argument, such as a custom threadpool. If you define both a server element and use a jettyXmls element which points to a config file that has a line like <Configure id="Server" class="org.eclipse.jetty.server.Server"> then the the xml configuration will override what you configure for the server in the pom.xml.

useProvidedScope

Default value is false. If true, the dependencies with <scope>provided</scope> are placed onto the container classpath. Be aware that this is not the webapp classpath, as provided indicates that these dependencies would normally be expected to be provided by the container. You should very rarely ever need to use this. See Container Classpath vs WebApp Classpath.

Forked

deployMode of FORK. This is similar to the old "jetty:run-forked" goal - a separate process is forked to run your webapp embedded into jetty. These extra configuration parameters are available:

env

Optional. Map of key/value pairs to pass as environment to the forked JVM.

jvmArgs

Optional. A string representing arbitrary arguments to pass to the forked JVM.

forkWebXml

Optional. Defaults to target/fork-web.xml. This is the location of a quickstart web xml file that will be generated during the forking of the jetty process. You should not need to set this parameter, but it is available if you wish to control the name and location of that file.

useProvidedScope

Default value is false. If true, the dependencies with <scope>provided</scope> are placed onto the container classpath. Be aware that this is NOT the webapp classpath, as "provided" indicates that these dependencies would normally be expected to be provided by the container. You should very rarely ever need to use this. See Container Classpath vs WebApp Classpath.

In a jetty distribution

deployMode of DISTRO. This is similar to the old "jetty:run-distro" goal - your webapp is deployed into a dynamically downloaded, unpacked and configured jetty distribution. A separate process is forked to run the distro. These extra configuration parameters are available:

jettyBase

Optional. The location of an existing jetty base directory to use to deploy the webapp. The existing base will be copied to the target/ directory before the webapp is deployed. If there is no existing jetty base, a fresh one will be made in target/jetty-base.

jettyHome

Optional. The location of an existing unpacked jetty distribution. If one does not exist, a fresh jetty distribution will be downloaded from maven and installed to the target directory.

jvmArgs

Optional. A string representing arguments that should be passed to the jvm of the child process running the distro.

modules

Optional. An array of names of additional jetty modules that the jetty child process will activate. Use this to change the container classpath instead of useProvidedScope. These modules are enabled by default: server,http,webapp,deploy.

Common Configuration

The following configuration parameters are common to all of the "run-" and "start-" goals:

deployMode

One of EMBED, FORK or DISTRO. Default EMBED. Can also be configured by setting the Maven property jetty.deployMode. This parameter determines whether the webapp will run in jetty in-process with Maven, forked into a new process, or deployed into a jetty distribution. See Deployment Modes.

jettyXmls

Optional. A comma separated list of locations of jetty xml files to apply in addition to any plugin configuration parameters. You might use it if you have other webapps, handlers, specific types of connectors etc., to deploy, or if you have other Jetty objects that you cannot configure from the plugin.

skip

Default is false. If true, the execution of the plugin exits. Same as setting the SystemProperty -Djetty.skip on the command line. This is most useful when configuring Jetty for execution during integration testing and you want to skip the tests.

excludedGoals

Optional. A list of Jetty plugin goal names that will cause the plugin to print an informative message and exit. Useful if you want to prevent users from executing goals that you know cannot work with your project.

supportedPackagings

Optional. Defaults to war. This is a list of maven <packaging> types that can work with the jetty plugin. Usually, only war projects are suitable, however, you may configure other types. The plugin will refuse to start if the <packaging> type in the pom is not in list of supportedPackagings.

systemProperties

Optional. Allows you to configure System properties for the execution of the plugin. For more information, see Setting System Properties.

systemPropertiesFile

Optional. A file containing System properties to set for the execution of the plugin. By default, settings that you make here do not override any system properties already set on the command line, by the JVM, or in the POM via systemProperties. Read Setting System Properties for how to force overrides.

jettyProperties

Optional. A map of property name, value pairs. Allows you to configure standard jetty properties.

Container Classpath vs WebApp Classpath

The Servlet Specification makes a strong distinction between the classpath for a webapp, and the classpath of the container. When running in maven, the plugin’s classpath is equivalent to the container classpath. It will make a classpath for the webapp to be deployed comprised of <dependencies> specified in the pom.

If your production environment places specific jars onto the container’s classpath, the equivalent way to do this with maven is to define these as <dependencies> for the plugin itself, not the project. See configuring maven plugins. This is suitable if you are using either EMBED or FORK mode. If you are using DISTRO mode, then you should configure the modules parameter with the names of the jetty modules that place these jars onto the container classpath.

Note that in EMBED or FORK mode, you could also influence the container classpath by setting the useProvidedScope parameter to true: this will place any dependencies with <scope>provided<scope> onto the plugin’s classpath. Use this very cautiously: as the plugin already automatically places most jetty jars onto the classpath, you could wind up with duplicate jars.

jetty:run

The run goal deploys a webapp that is not first built into a WAR. A virtual webapp is constructed from the project’s sources and its dependencies. It looks for the constituent parts of a webapp in the maven default project locations, although you can override these in the plugin configuration. For example, by default it looks for:

  • resources in ${project.basedir}/src/main/webapp

  • classes in ${project.build.outputDirectory}

  • web.xml in ${project.basedir}/src/main/webapp/WEB-INF/

The plugin first runs a maven parallel build to ensure that the classes are built and up-to-date before deployment. If you change the source of a class and your IDE automatically compiles it in the background, the plugin picks up the changed class (note you need to configure a non-zero scan interval for automatic redeployment, otherwise redeployment only occurs if you hit the Enter key).

If the plugin is invoked in a multi-module build, any dependencies that are also in the maven reactor are used from their compiled classes. Prior to jetty-9.4.7 any dependencies needed to be built first.

Once invoked, you can configure the plugin to run continuously, scanning for changes in the project and automatically performing a hot redeploy when necessary. Any changes you make are immediately reflected in the running instance of Jetty, letting you quickly jump from coding to testing, rather than going through the cycle of: code, compile, reassemble, redeploy, test.

The maven build will be paused until jetty exits, at which time maven will also exit.

Stopping jetty is accomplished by typing cntrl-c at the command line.

Output from jetty will be logged to the console.

Here is an example, which turns on scanning for changes every ten seconds, and sets the webapp context path to /test:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <scan>10</scan>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
  </configuration>
</plugin>
Configuration
webApp

This is an instance of org.eclipse.jetty.maven.plugin.MavenWebAppContext, which is an extension to the class org.eclipse.jetty.webapp.WebAppContext. You can use any of the setter methods on this object to configure your webapp. Here are a few of the most useful ones:

contextPath

The context path for your webapp. By default, this is set to /. If using a custom value for this parameter, you should include the leading /, example /mycontext.

descriptor

The path to the web.xml file for your webapp. By default, the plugin will look in src/main/webapp/WEB-INF/web.xml.

defaultsDescriptor

The path to a webdefault.xml file that will be applied to your webapp before the web.xml. If you don’t supply one, Jetty uses a default file baked into the jetty-webapp.jar.

overrideDescriptor

The path to a web.xml file that Jetty applies after reading your web.xml. You can use this to replace or add configuration.

jettyEnvXml

Optional. Location of a jetty-env.xml file, which allows you to make JNDI bindings that satisfy env-entry, resource-env-ref, and resource-ref linkages in the web.xml that are scoped only to the webapp and not shared with other webapps that you might be deploying at the same time (for example, by using a jettyXml file).

tempDirectory

The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running. The default is ${project.build.outputDirectory}/tmp.

baseResource

The path from which Jetty serves static resources. Defaults to src/main/webapp. If this location does not exist (because, for example, your project does not use static content), then the plugin will synthesize a virtual static resource location of target/webapp-synth.

resourceBases

Use instead of baseResource if you have multiple directories from which you want to serve static content. This is an array of directory locations, either as urls or file paths.

baseAppFirst

Defaults to "true". Controls whether any overlaid wars are added before or after the original base resource(s) of the webapp. See the section on overlaid wars for more information.

containerIncludeJarPattern

Defaults to ./jetty-servlet-api-[/]\.jar$|.javax.servlet.jsp.jstl-[/]\.jar|.taglibs-standard-impl-.\.jar. This is a pattern that is applied to the names of the jars on the container’s classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc. This is analogous to the context attribute org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern that is documented here. You can define extra patterns of jars that will be included in the scan.

webInfIncludeJarPattern

Defaults to matching all of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib). You can make this pattern more restrictive to only match certain jars by using this setter. This is analogous to the context attribute org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern that is documented here.

contextXml

The path to a context xml file that is applied to your webapp AFTER the webApp element.

classesDirectory

Location of your compiled classes for the webapp. You should rarely need to set this parameter. Instead, you should set <build><outputDirectory> in your pom.xml.

testClassesDirectory

Location of the compiled test classes for your webapp. By default this is ${project.build.testOutputDirectory}.

useTestScope

If true, the classes from testClassesDirectory and dependencies of scope "test" are placed first on the classpath. By default this is false.

scan

The pause in seconds between sweeps of the webapp to check for changes and automatically hot redeploy if any are detected. By default this is 0, which disables hot deployment scanning. Redeployment is by hitting Enter key. A number greater than 0 enables it.

scanTargetPatterns

Optional. List of extra directories with glob-style include/excludes patterns (see javadoc for FileSystem.getPathMatcher) to specify other files to periodically scan for changes.

scanClassesPattern

Optional. Include and exclude patterns that can be applied to the classesDirectory for the purposes of scanning, it does not affect the classpath. If a file or directory is excluded by the patterns then a change in that file (or subtree in the case of a directory) is ignored and will not cause the webapp to redeploy. Patterns are specified as a relative path using a glob-like syntax as described in the javadoc for FileSystem.getPathMatcher.

scanTestClassesPattern

Optional. Include and exclude patterns that can be applied to the testClassesDirectory for the purposes of scanning, it does not affect the classpath. If a file or directory is excluded by the patterns then a change in that file (or subtree in the case of a directory) is ignored and will not cause the webapp to redeploy. Patterns are specified as a relative path using a glob-like syntax as described in the javadoc for FileSystem.getPathMatcher.

See Deployment Modes for other configuration parameters available when using the run goal in EMBED, FORK or DISTRO modes.

Here’s an example of a pom configuration for the plugin with the run goal:

<project>
...
  <plugins>
...
    <plugin>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-maven-plugin</artifactId>
      <version>10.0.0.beta1</version>
      <configuration>
        <webApp>
          <contextPath>/</contextPath>
          <descriptor>${project.basedir}/src/over/here/web.xml</descriptor>
          <jettyEnvXml>${project.basedir}/src/over/here/jetty-env.xml</jettyEnvXml>
          <baseResource>${project.basedir}/src/staticfiles</baseResource>
        </webApp>
        <classesDirectory>${project.basedir}/somewhere/else</classesDirectory>
        <scanClassesPattern>
          <excludes>
             <exclude>**/Foo.class</exclude>
          </excludes>
        </scanClassesPattern>
        <scanTargetPatterns>
          <scanTargetPattern>
            <directory>src/other-resources</directory>
            <includes>
              <include>**/*.xml</include>
              <include>**/*.properties</include>
            </includes>
            <excludes>
              <exclude>**/myspecial.xml</exclude>
              <exclude>**/myspecial.properties</exclude>
            </excludes>
          </scanTargetPattern>
        </scanTargetPatterns>
      </configuration>
    </plugin>
  </plugins>
...
</project>

If, for whatever reason, you cannot run on an unassembled webapp, the goal run-war works on assembled webapps.

jetty:run-war

When invoked at the command line this goal first executes a maven build of your project to the package phase.

By default it then deploys the resultant war to jetty, but you can use this goal instead to deploy any war file by simply setting the <webApp><war> configuration parameter to its location.

If you set a non-zero scan, Jetty watches your pom.xml and the WAR file; if either changes, it redeploys the war. With a zero scan interval, redeployment is manual via hitting the Enter key.

The maven build is held up until jetty exits, which is achieved by typing cntrl-c at the command line.

All jetty output is directed to the console.

Configuration

Configuration parameters are:

webApp
war

The location of the built WAR file. This defaults to ${project.build.directory}/${project.build.finalName}.war. You can set it to the location of any pre-built war file.

contextPath

The context path for your webapp. By default, this is set to /. If using a custom value for this parameter, you should include the leading /, example /mycontext.

defaultsDescriptor

The path to a webdefault.xml file that will be applied to your webapp before the web.xml. If you don’t supply one, Jetty uses a default file baked into the jetty-webapp.jar.

overrideDescriptor

The path to a web.xml file that Jetty applies after reading your web.xml. You can use this to replace or add configuration.

containerIncludeJarPattern

Defaults to ./jetty-servlet-api-[/]\.jar$|.javax.servlet.jsp.jstl-[/]\.jar|.taglibs-standard-impl-.\.jar. This is a pattern that is applied to the names of the jars on the container’s classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc. This is analogous to the context attribute org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern that is documented here. You can define extra patterns of jars that will be included in the scan.

webInfIncludeJarPattern

Defaults to matching all of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib). You can make this pattern more restrictive to only match certain jars by using this setter. This is analogous to the context attribute org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern that is documented here.

tempDirectory

The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running. The default is ${project.build.outputDirectory}/tmp.

contextXml

The path to a context xml file that is applied to your webapp AFTER the webApp element.

scan

The pause in seconds between sweeps of the webapp to check for changes and automatically hot redeploy if any are detected. By default this is 0, which disables hot deployment scanning. Redeployment is by hitting Enter key. A number greater than 0 enables it.

scanTargetPatterns

Optional. List of directories with ant-style include/excludes patterns to specify other files to periodically scan for changes.

See Deployment Modes for other configuration parameters available when using the run-war goal in EMBED, FORK or DISTRO modes.

jetty:start

This is similar to the jetty:run goal, however it is not designed to be run from the command line and does not first execute the build up until the test-compile phase to ensure that all necessary classes and files of the webapp have been generated. It will not scan your project for changes and restart your webapp. It does not pause maven until jetty is stopped.

Instead, it is designed to be used with build phase bindings in your pom. For example to you can have maven start your webapp at the beginning of your tests and stop at the end.

If the plugin is invoked as part of a multi-module build, any dependencies that are also in the maven reactor are used from their compiled classes. Prior to jetty-9.4.7 any dependencies needed to be built first.

Here’s an example of using the pre-integration-test and post-integration-test Maven build phases to trigger the execution and termination of Jetty:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <scan>10</scan>
    <stopKey>foo</stopKey>
    <stopPort>9999</stopPort>
  </configuration>
  <executions>
    <execution>
      <id>start-jetty</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>start</goal>
      </goals>
      <configuration>
        <scan>0</scan>
      </configuration>
    </execution>
    <execution>
      <id>stop-jetty</id>
      <phase>post-integration-test</phase>
       <goals>
         <goal>stop</goal>
       </goals>
     </execution>
  </executions>
</plugin>

This goal will generate output from jetty into the target/jetty-start.out file.

Configuration

These configuration parameters are available:

webApp

This is an instance of org.eclipse.jetty.maven.plugin.MavenWebAppContext, which is an extension to the class org.eclipse.jetty.webapp.WebAppContext. You can use any of the setter methods on this object to configure your webapp. Here are a few of the most useful ones:

contextPath

The context path for your webapp. By default, this is set to /. If using a custom value for this parameter, you should include the leading /, example /mycontext.

descriptor

The path to the web.xml file for your webapp. The default is src/main/webapp/WEB-INF/web.xml.

defaultsDescriptor

The path to a webdefault.xml file that will be applied to your webapp before the web.xml. If you don’t supply one, Jetty uses a default file baked into the jetty-webapp.jar.

overrideDescriptor

The path to a web.xml file that Jetty applies after reading your web.xml. You can use this to replace or add configuration.

jettyEnvXml

Optional. Location of a jetty-env.xml file, which allows you to make JNDI bindings that satisfy env-entry, resource-env-ref, and resource-ref linkages in the web.xml that are scoped only to the webapp and not shared with other webapps that you might be deploying at the same time (for example, by using a jettyXml file).

tempDirectory

The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running. The default is ${project.build.outputDirectory}/tmp.

baseResource

The path from which Jetty serves static resources. Defaults to src/main/webapp.

resourceBases

Use instead of baseResource if you have multiple directories from which you want to serve static content. This is an array of directory names.

baseAppFirst

Defaults to "true". Controls whether any overlaid wars are added before or after the original base resource(s) of the webapp. See the section on overlaid wars for more information.

containerIncludeJarPattern

Defaults to ./jetty-servlet-api-[/]\.jar$|.javax.servlet.jsp.jstl-[/]\.jar|.taglibs-standard-impl-.\.jar. This is a pattern that is applied to the names of the jars on the container’s classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc. This is analogous to the context attribute org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern that is documented here. You can define extra patterns of jars that will be included in the scan.

webInfIncludeJarPattern

Defaults to matching all of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib). You can make this pattern more restrictive to only match certain jars by using this setter. This is analogous to the context attribute org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern that is documented here.

contextXml

The path to a context xml file that is applied to your webapp AFTER the webApp element.

classesDirectory

Location of your compiled classes for the webapp. You should rarely need to set this parameter. Instead, you should set build outputDirectory in your pom.xml.

testClassesDirectory

Location of the compiled test classes for your webapp. By default this is ${project.build.testOutputDirectory}.

useTestScope

If true, the classes from testClassesDirectory and dependencies of scope "test" are placed first on the classpath. By default this is false.

stopPort

Optional. Port to listen on for stop commands. Useful to use in conjunction with the stop and start goals.

stopKey

Optional. Used in conjunction with stopPort for stopping jetty. Useful to use in conjunction with the stop and start goals.

These additional configuration parameters are available when running in FORK or DISTRO mode:

maxChildStartChecks

Default is 10. This is maximum number of times the parent process checks to see if the forked jetty process has started correctly

maxChildStartCheckMs

Default is 200. This is the time in milliseconds between checks on the startup of the forked jetty process.

jetty:start-war

Similarly to the jetty:start goal, jetty:start-war is designed to be bound to build lifecycle phases in your pom.

It will not scan your project for changes and restart your webapp. It does not pause maven until jetty is stopped.

By default, if your pom is for a webapp project, it will deploy the war file for the project to jetty. However, like the jetty:run-war project, you can nominate any war file to deploy by defining its location in the <webApp><war> parameter.

If the plugin is invoked as part of a multi-module build, any dependencies that are also in the maven reactor are used from their compiled classes. Prior to jetty-9.4.7 any dependencies needed to be built first.

This goal will generate output from jetty into the target/jetty-start-war.out file.

Configuration

These configuration parameters are available:

webApp
war

The location of the built WAR file. This defaults to ${project.build.directory}/${project.build.finalName}.war. You can set it to the location of any pre-built war file.

contextPath

The context path for your webapp. By default, this is set to /. If using a custom value for this parameter, you should include the leading /, example /mycontext.

defaultsDescriptor

The path to a webdefault.xml file that will be applied to your webapp before the web.xml. If you don’t supply one, Jetty uses a default file baked into the jetty-webapp.jar.

overrideDescriptor

The path to a web.xml file that Jetty applies after reading your web.xml. You can use this to replace or add configuration.

containerIncludeJarPattern

Defaults to ./jetty-servlet-api-[/]\.jar$|.javax.servlet.jsp.jstl-[/]\.jar|.taglibs-standard-impl-.\.jar. This is a pattern that is applied to the names of the jars on the container’s classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc. This is analogous to the context attribute org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern that is documented here. You can define extra patterns of jars that will be included in the scan.

webInfIncludeJarPattern

Defaults to matching all of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib). You can make this pattern more restrictive to only match certain jars by using this setter. This is analogous to the context attribute org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern that is documented here.

tempDirectory

The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running. The default is ${project.build.outputDirectory}/tmp.

contextXml

The path to a context xml file that is applied to your webapp AFTER the webApp element.

stopPort

Optional. Port to listen on for stop commands. Useful to use in conjunction with the stop.

stopKey

Optional. Used in conjunction with stopPort for stopping jetty. Useful to use in conjunction with the stop.

These additional configuration parameters are available when running in FORK or DISTRO mode:

maxChildStartChecks

Default is 10. This is maximum number of times the parent process checks to see if the forked jetty process has started correctly

maxChildStartCheckMs

Default is 200. This is the time in milliseconds between checks on the startup of the forked jetty process.

jetty:stop

The stop goal stops a FORK or DISTRO mode running instance of Jetty. To use it, you need to configure the plugin with a special port number and key. That same port number and key will also be used by the other goals that start jetty.

Configuration
stopPort

A port number for Jetty to listen on to receive a stop command to cause it to shutdown.

stopKey

A string value sent to the stopPort to validate the stop command.

stopWait

The maximum time in seconds that the plugin will wait for confirmation that Jetty has stopped. If false or not specified, the plugin does not wait for confirmation but exits after issuing the stop command.

Here’s a configuration example:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <stopPort>9966</stopPort>
    <stopKey>foo</stopKey>
    <stopWait>10</stopWait>
  </configuration>
</plugin>

Then, while Jetty is running (in another window), type:

mvn jetty:stop

The stopPort must be free on the machine you are running on. If this is not the case, you will get an "Address already in use" error message after the "Started ServerConnector …​" message.

jetty:effective-web-xml

This goal calculates a synthetic web.xml (the "effective web.xml") according to the rules of the Servlet Specification taking into account all sources of discoverable configuration of web components in your application: descriptors (webdefault.xml, web.xml, web-fragment.xml`s, `web-override.xml) and discovered annotations (@WebServlet, @WebFilter, @WebListener). No programmatic declarations of servlets, filters and listeners can be taken into account.

You can calculate the effective web.xml for any pre-built war file by setting the <webApp><war> parameter, or you can calculate it for the unassembled webapp by setting all of the usual <webApp> parameters as for jetty:run.

Other useful information about your webapp that is produced as part of the analysis is also stored as context parameters in the effective-web.xml. The effective-web.xml can be used in conjunction with the Quickstart feature to quickly start your webapp (note that Quickstart is not appropriate for the mvn jetty goals).

The effective web.xml from these combined sources is generated into a file, which by default is target/effective-web.xml, but can be changed by setting the effectiveWebXml configuration parameter.

Configuration
effectiveWebXml

The full path name of a file into which you would like the effective web xml generated.

webApp
war

The location of the built WAR file. This defaults to ${project.build.directory}/${project.build.finalName}.war. You can set it to the location of any pre-built war file. Or you can leave it blank and set up the other webApp parameters as per jetty:run, as well as the webAppSourceDirectory, classes and testClasses parameters.

contextPath

The context path for your webapp. By default, this is set to /. If using a custom value for this parameter, you should include the leading /, example /mycontext.

defaultsDescriptor

The path to a webdefault.xml file that will be applied to your webapp before the web.xml. If you don’t supply one, Jetty uses a default file baked into the jetty-webapp.jar.

overrideDescriptor

The path to a web.xml file that Jetty applies after reading your web.xml. You can use this to replace or add configuration.

containerIncludeJarPattern

Defaults to ./jetty-servlet-api-[/]\.jar$|.javax.servlet.jsp.jstl-[/]\.jar|.taglibs-standard-impl-.\.jar. This is a pattern that is applied to the names of the jars on the container’s classpath (ie the classpath of the plugin, not that of the webapp) that should be scanned for fragments, tlds, annotations etc. This is analogous to the context attribute org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern that is documented here. You can define extra patterns of jars that will be included in the scan.

webInfIncludeJarPattern

Defaults to matching all of the dependency jars for the webapp (ie the equivalent of WEB-INF/lib). You can make this pattern more restrictive to only match certain jars by using this setter. This is analogous to the context attribute org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern that is documented here.

tempDirectory

The path to a dir that Jetty can use to expand or copy jars and jsp compiles when your webapp is running. The default is ${project.build.outputDirectory}/tmp.

contextXml

The path to a context xml file that is applied to your webapp AFTER the webApp element.

You can also generate the origin of each element into the effective web.xml file. The origin is either a descriptor eg web.xml,web-fragment.xml,override-web.xml file, or an annotation eg @WebServlet. Some examples of elements with origin attribute information are:

<listener origin="DefaultsDescriptor(file:///path/to/distro/etc/webdefault.xml):21">
<listener origin="WebDescriptor(file:///path/to/base/webapps/test-spec/WEB-INF/web.xml):22">
<servlet-class origin="FragmentDescriptor(jar:file:///path/to/base/webapps/test-spec/WEB-INF/lib/test-web-fragment.jar!/META-INF/web-fragment.xml):23">
<servlet-class origin="@WebServlet(com.acme.test.TestServlet):24">

To generate origin information, use the following configuration parameters on the webApp element:

originAttribute

The name of the attribute that will contain the origin. By default it is origin.

generateOrigin

False by default. If true, will force the generation of the originAttribute onto each element.

Using Overlaid wars

If your webapp depends on other war files, the jetty:run and jetty:start goals are able to merge resources from all of them. It can do so based on the settings of the maven-war-plugin, or if your project does not use the maven-war-plugin to handle the overlays, it can fall back to a simple algorithm to determine the ordering of resources.

With maven-war-plugin

The maven-war-plugin has a rich set of capabilities for merging resources. The jetty:run and jetty:start goals are able to interpret most of them and apply them during execution of your unassembled webapp. This is probably best seen by looking at a concrete example.

Suppose your webapp depends on the following wars:

<dependency>
  <groupId>com.acme</groupId>
  <artifactId>X</artifactId>
  <type>war</type>
</dependency>
<dependency>
  <groupId>com.acme</groupId>
  <artifactId>Y</artifactId>
  <type>war</type>
</dependency>

Containing:

WebAppX:

 /foo.jsp
 /bar.jsp
 /WEB-INF/web.xml

WebAppY:

 /bar.jsp
 /baz.jsp
 /WEB-INF/web.xml
 /WEB-INF/special.xml

They are configured for the maven-war-plugin:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <overlays>
      <overlay>
        <groupId>com.acme</groupId>
        <artifactId>X</artifactId>
        <excludes>
          <exclude>bar.jsp</exclude>
        </excludes>
      </overlay>
      <overlay>
        <groupId>com.acme</groupId>
        <artifactId>Y</artifactId>
        <excludes>
          <exclude>baz.jsp</exclude>
        </excludes>
      </overlay>
      <overlay>
      </overlay>
    </overlays>
  </configuration>
</plugin>

Then executing jetty:run would yield the following ordering of resources: com.acme.X.war : com.acme.Y.war: ${project.basedir}/src/main/webapp. Note that the current project’s resources are placed last in the ordering due to the empty <overlay/> element in the maven-war-plugin. You can either use that, or specify the <baseAppFirst>false</baseAppFirst> parameter to the jetty-maven-plugin.

Moreover, due to the exclusions specified above, a request for the resource ` bar.jsp` would only be satisfied from com.acme.Y.war. Similarly as baz.jsp is excluded, a request for it would result in a 404 error.

Without maven-war-plugin

The algorithm is fairly simple, is based on the ordering of declaration of the dependent wars, and does not support exclusions. The configuration parameter <baseAppFirst> (see for example jetty:run for more information) can be used to control whether your webapp’s resources are placed first or last on the resource path at runtime.

For example, suppose our webapp depends on these two wars:

<dependency>
  <groupId>com.acme</groupId>
  <artifactId>X</artifactId>
  <type>war</type>
</dependency>
<dependency>
  <groupId>com.acme</groupId>
  <artifactId>Y</artifactId>
  <type>war</type>
</dependency>

Suppose the webapps contain:

WebAppX:

 /foo.jsp
 /bar.jsp
 /WEB-INF/web.xml

WebAppY:

 /bar.jsp
 /baz.jsp
 /WEB-INF/web.xml
 /WEB-INF/special.xml

Then our webapp has available these additional resources:

/foo.jsp (X)
/bar.jsp (X)
/baz.jsp (Y)
/WEB-INF/web.xml (X)
/WEB-INF/special.xml (Y)

Configuring Security Settings

You can configure LoginServices in the plugin. Here’s an example of setting up the HashLoginService for a webapp:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <scan>10</scan>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
    <loginServices>
      <loginService implementation="org.eclipse.jetty.security.HashLoginService">
        <name>Test Realm</name>
        <config>${project.basedir}/src/etc/realm.properties</config>
      </loginService>
    </loginServices>
  </configuration>
</plugin>

Using Multiple Webapp Root Directories

If you have external resources that you want to incorporate in the execution of a webapp, but which are not assembled into war files, you can’t use the overlaid wars method described above, but you can tell Jetty the directories in which these external resources are located. At runtime, when Jetty receives a request for a resource, it searches all the locations to retrieve the resource. It’s a lot like the overlaid war situation, but without the war.

Here is a configuration example:

<configuration>
  <webApp>
    <contextPath>/${build.finalName}</contextPath>
    <baseResource implementation="org.eclipse.jetty.util.resource.ResourceCollection">
      <resourcesAsCSV>src/main/webapp,/home/johndoe/path/to/my/other/source,/yet/another/folder</resourcesAsCSV>
    </baseResource>
  </webApp>
</configuration>

Running More than One Webapp

With jetty:run

You can use either a jetty.xml file to configure extra (pre-compiled) webapps that you want to deploy, or you can use the <contextHandlers> configuration element to do so. If you want to deploy webapp A, and webapps B and C in the same Jetty instance:

Putting the configuration in webapp A’s pom.xml:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <scan>10</scan>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
    <contextHandlers>
      <contextHandler implementation="org.eclipse.jetty.maven.plugin.MavenWebAppContext">
        <war>${project.basedir}../../B.war</war>
        <contextPath>/B</contextPath>
      </contextHandler>
      <contextHandler implementation="org.eclipse.jetty.maven.plugin.MavenWebAppContext">
        <war>${project.basedir}../../C.war</war>
        <contextPath>/C</contextPath>
      </contextHandler>
    </contextHandlers>
  </configuration>
</plugin>
If the ContextHandler you are deploying is a webapp, it is essential that you use an org.eclipse.jetty.maven.plugin.MavenWebAppContext instance rather than a standard org.eclipse.jetty.webapp.WebAppContext instance. Only the former will allow the webapp to function correctly in the maven environment.

Alternatively, add a jetty.xml file to webapp A. Copy the jetty.xml file from the Jetty distribution, and then add WebAppContexts for the other 2 webapps:

<Ref refid="Contexts">
  <Call name="addHandler">
    <Arg>
      <New class="org.eclipse.jetty.maven.plugin.MavenWebAppContext">
        <Set name="contextPath">/B</Set>
        <Set name="war">../../B.war</Set>
      </New>
    </Arg>
  </Call>
  <Call>
    <Arg>
      <New class="org.eclipse.jetty.maven.plugin.MavenWebAppContext">
        <Set name="contextPath">/C</Set>
        <Set name="war">../../C.war</Set>
      </New>
    </Arg>
  </Call>
</Ref>

Then configure the location of this jetty.xml file into webapp A’s jetty plugin:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>10.0.0.beta1</version>
  <configuration>
    <scan>10</scan>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
    <jettyXml>src/main/etc/jetty.xml</jettyXml>
  </configuration>
</plugin>

For either of these solutions, the other webapps must already have been built, and they are not automatically monitored for changes. You can refer either to the packed WAR file of the pre-built webapps or to their expanded equivalents.

Setting System Properties

You can specify property name/value pairs that Jetty sets as System properties for the execution of the plugin. This feature is useful to tidy up the command line and save a lot of typing.

However, sometimes it is not possible to use this feature to set System properties - sometimes the software component using the System property is already initialized by the time that maven runs (in which case you will need to provide the System property on the command line), or by the time that Jetty runs. In the latter case, you can use the maven properties plugin to define the system properties instead. Here’s an example that configures the logback logging system as the Jetty logger:

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>properties-maven-plugin</artifactId>
  <version>1.0-alpha-2</version>
  <executions>
    <execution>
      <goals>
        <goal>set-system-properties</goal>
      </goals>
      <configuration>
        <properties>
          <property>
            <name>logback.configurationFile</name>
            <value>${project.baseUri}/resources/logback.xml</value>
          </property>
        </properties>
      </configuration>
    </execution>
  </executions>
</plugin>
If a System property is already set (for example, from the command line or by the JVM itself), then by default these configured properties DO NOT override them. However, they can override system properties set from a file instead, see specifying system properties in a file.
Specifying System Properties in the POM

Here’s an example of how to specify System properties in the POM:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <configuration>
    <systemProperties>
        <fooprop>222</fooprop>
    </systemProperties>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
  </configuration>
</plugin>
Specifying System Properties in a File

You can also specify your System properties in a file. System properties you specify in this way do not override System properties that set on the command line, by the JVM, or directly in the POM via systemProperties.

Suppose we have a file called mysys.props which contains the following:

fooprop=222

This can be configured on the plugin like so:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <configuration>
    <systemPropertiesFile>${project.basedir}/mysys.props</systemPropertiesFile>
    <webApp>
      <contextPath>/test</contextPath>
    </webApp>
  </configuration>
</plugin>

You can instead specify the file by setting the System property jetty.systemPropertiesFile on the command line.

Files Scanned by the Jetty Maven Plugin

If you set a non zero scanIntervalSeconds configuration parameter, the jetty-maven-plugin will scan certain files for changes, and redeploy the webapp if necessary. The files that are scanned depend on the goal being executed.

Scanner Matrix

Goal Files

jetty:run

  • pom.xml

  • <dependencies>

  • <classesDirectory>

  • <testClassesDirectory>

  • <webApp><descriptor/></webApp> or src/main/webapp/WEB-INF/web.xml

  • <webApp><jettyEnvXml/></webApp> or src/main/webapp/WEB-INF/jetty-web.xml

  • <webApp><baseResource>/WEB-INF/jetty-web.xml

  • <scanTargets>

  • <scanTargetPatterns>

  • any defaultsDescriptor for the webapp

  • any overrideDescriptor for the webapp

  • any dependencies that are wars or zips

jetty:run-war

  • pom.xml

  • <war>

Jetty Jspc Maven Plugin

This plugin will help you pre-compile your jsps and works in conjunction with the Maven war plugin to put them inside an assembled war.

Configuration

Here’s the basic setup required to put the jspc plugin into your build:

<plugin>
  <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-jspc-maven-plugin</artifactId>
   <version>10.0.0.beta1</version>
   <executions>
     <execution>
       <id>jspc</id>
       <goals>
         <goal>jspc</goal>
       </goals>
       <configuration>
       </configuration>
     </execution>
   </executions>
 </plugin>

The configurable parameters are as follows:

webXmlFragment

Default value: $\{project.basedir}/target/webfrag.xml

File into which to generate the servlet declarations. Will be merged with an existing web.xml.

webAppSourceDirectory

Default value: $\{project.basedir}/src/main/webapp

Root of resources directory where jsps, tags etc are located.

webXml

Default value: $\{project.basedir}/src/main/webapp/WEB-INF/web.xml

The web.xml file to use to merge with the generated fragments.

includes

Default value: \/.jsp, \/.jspx

The comma separated list of patterns for file extensions to be processed.

excludes

Default value: \/.svn\/

The comma separated list of patterns for file extensions to be skipped.

classesDirectory

Default value: $\{project.build.outputDirectory}

Location of classes for the webapp.

generatedClasses

Default value: $\{project.build.outputDirectory}

Location to put the generated classes for the jsps.

insertionMarker

Default value: none

A marker string in the src web.xml file which indicates where to merge in the generated web.xml fragment. Note that the marker string will NOT be preserved during the insertion. Can be left blank, in which case the generated fragment is inserted just before the line containing </web-app>.

useProvidedScope

Default value: false

If true, jars of dependencies marked with <scope>provided</scope> will be placed on the compilation classpath.

mergeFragment

Default value: true

Whether or not to merge the generated fragment file with the source web.xml. The merged file will go into the same directory as the webXmlFragment.

keepSources

Default value: false

If true, the generated .java files are not deleted at the end of processing.

sourceVersion

Introduced in Jetty 9.3.6. Java version of jsp source files. Defaults to 1.7.

targetVersion

Introduced in Jetty 9.3.6. Java version of class files generated from jsps. Defaults to 1.7.

tldJarNamePatterns

Default value: .taglibs[/]\.jar|.jstl-impl[/]\.jar$

Patterns of jars on the 'system' (ie container) path that contain tlds. Use | to separate each pattern.

jspc

Default value: the org.apache.jasper.JspC instance being configured.

The JspC class actually performs the pre-compilation. All setters on the JspC class are available. You can download the javadoc here.

Taking all the default settings, here’s how to configure the war plugin to use the generated web.xml that includes all of the jsp servlet declarations:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <webXml>${project.basedir}/target/web.xml</webXml>
  </configuration>
</plugin>

Precompiling only for Production Build

As compiling jsps is usually done during preparation for a production release and not usually done during development, it is more convenient to put the plugin setup inside a <profile> which which can be deliberately invoked during prep for production.

For example, the following profile will only be invoked if the flag -Dprod is present on the run line:

<profiles>
    <profile>
      <id>prod</id>
      <activation>
        <property><name>prod</name></property>
      </activation>
      <build>
      <plugins>
        <plugin>
          <groupId>org.eclipse.jetty</groupId>
          <artifactId>jetty-jspc-maven-plugin</artifactId>
          <version>10.0.0.beta1</version>
          <!-- put your configuration in here -->
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-war-plugin</artifactId>
          <!-- put your configuration in here -->
        </plugin>
      </plugins>
      </build>
    </profile>
  </profiles>

The following invocation would cause your code to be compiled, the jsps to be compiled, the <servlet> and <servlet-mapping>s inserted in the web.xml and your webapp assembled into a war:

$ mvn -Dprod package

Precompiling Jsps with Overlaid Wars

Precompiling jsps with an overlaid war requires a bit more configuration. This is because you need to separate the steps of unpacking the overlaid war and then repacking the final target war so the jetty-jspc-maven-plugin has the opportunity to access the overlaid resources.

In the example we’ll show, we will use an overlaid war. The overlaid war will provide the web.xml file but the jsps will be in src/main/webapp (i.e. part of the project that uses the overlay). We will unpack the overlaid war file, compile the jsps and merge their servlet definitions into the extracted web.xml, then pack everything into a war.

Here’s an example configuration of the war plugin that separate those phases into an unpack phase, and then a packing phase:

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <executions>
      <execution>
        <id>unpack</id>
        <goals><goal>exploded</goal></goals>
        <phase>generate-resources</phase>
        <configuration>
          <webappDirectory>target/foo</webappDirectory>
          <overlays>
            <overlay />
            <overlay>
              <groupId>org.eclipse.jetty</groupId>
              <artifactId>test-jetty-webapp</artifactId>
            </overlay>
          </overlays>
        </configuration>
      </execution>
      <execution>
        <id>pack</id>
        <goals><goal>war</goal></goals>
        <phase>package</phase>
        <configuration>
          <warSourceDirectory>target/foo</warSourceDirectory>
          <webXml>target/web.xml</webXml>
        </configuration>
      </execution>
    </executions>
</plugin>

Now you also need to configure the jetty-jspc-maven-plugin so that it can use the web.xml that was extracted by the war unpacking and merge in the generated definitions of the servlets. This is in target/foo/WEB-INF/web.xml. Using the default settings, the web.xml merged with the jsp servlet definitions will be put into target/web.xml.

<plugin>
    <groupId>org.eclipse.jetty</groupId>
     <artifactId>jetty-jspc-maven-plugin</artifactId>
     <version>10.0.0.beta1</version>
     <executions>
       <execution>
         <id>jspc</id>
         <goals>
           <goal>jspc</goal>
         </goals>
         <configuration>
            <webXml>target/foo/WEB-INF/web.xml</webXml>
            <includes>**/*.foo</includes>
            <excludes>**/*.fff</excludes>
        </configuration>
      </execution>
    </executions>
</plugin>

WebSocket Introduction

WebSocket is a new protocol for bidirectional communications initiated via HTTP/1.1 upgrade and providing basic message framing, layered over TCP. It is based on a low-level framing protocol that delivers messages in either UTF-8 TEXT or BINARY format.

A single message in WebSocket can be of any size (the underlying framing however does have a single frame limit of 63-bits). There can be an unlimited number of messages sent. Messages are sent sequentially, the base protocol does not support interleaved messages.

A WebSocket connection goes through some basic state changes:

Table 1. WebSocket connection states
State Description

CONNECTING

A HTTP Upgrade to WebSocket is in progress

OPEN

The HTTP Upgrade succeeded and the socket is now open and ready to read / write

CLOSING

A WebSocket Close Handshake has been started

CLOSED

WebSocket is now closed, no more read/write possible

When a WebSocket is closed, a status code and short reason string is provided.

What Jetty provides

Jetty provides an implementation of the following standards and specs.

RFC-6455

The WebSocket Protocol

We support the version 13 of the released and final spec.

Jetty tests its WebSocket protocol implementation using the autobahn testsuite.

The early drafts of WebSocket were supported in Jetty 7 and Jetty 8, but this support has been removed in Jetty 9. This means that Jetty 9 will not support the old browsers that implemented the early drafts of WebSocket. (such as Safari 5.0 or Opera 12)
Want to know if the browser you are targeting supports WebSocket? Use caniuse.com/websockets to find out.
JSR-356

The Java WebSocket API (javax.websocket)

This is the official Java API for working with WebSockets.

Unstable standards and specs:

perframe-compression

Per Frame Compression Extension.

An early extension draft from the Google/Chromium team that would provide WebSocket frame compression. perframe-compression using deflate algorithm is present on many versions of Chrome/Chromium.

Jetty’s support for perframe-compression is based on the draft-04 spec. This standard is being replaced with permessage-compression.

permessage-compression

Per Frame Compression Extension.

This is the replacement for perframe-compression, switching the compression to being based on the entire message, not the individual frames.

WebSocket APIs

APIs and libraries to implement your WebSockets using Jetty.

Jetty WebSocket API

The basic common API for creating and working with WebSockets using Jetty.

Jetty WebSocket Server API

Write WebSocket Server Endpoints for Jetty.

Jetty WebSocket Client API

Connect to WebSocket servers with Jetty.

Java WebSocket Client API

The new standard Java WebSocket Client API (javax.websocket) [JSR-356]

Java WebSocket Server API

The new standard Java WebSocket Server API (javax.websocket.server) [JSR-356]

Enabling WebSocket

To enable Websocket, you need to enable the websocket module.

Once this module is enabled for your Jetty base, it will apply to all webapps deployed to that base. If you want to be more selective about which webapps use Websocket, then you can:

Disable Websocket for a particular webapp

You can disable jsr-356 for a particular webapp by setting the context attribute org.eclipse.jetty.websocket.javax to false. This will mean that websockets are not available to your webapp, however deployment time scanning for websocket-related classes such as endpoints will still occur. This can be a significant impost if your webapp contains a lot of classes and/or jar files. To completely disable websockets and avoid all setup costs associated with it for a particular webapp, use instead the context attribute org.eclipse.jetty.containerInitializerExclusionPattern, described next, which allows you to exclude the websocket ServletContainerInitializer that causes the scanning.

Completely disable Websocket for a particular webapp

Set the org.eclipse.jetty.containerInitializerExclusionPattern context attribute to include org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer. Here’s an example of doing this in code, although you can do the same in xml:

WebAppContext context = new WebAppContext();
context.setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern",
                     "org.eclipse.jetty.websocket.javax.server.JavaxWebSocketServletContainerInitializer|com.acme.*");

Jetty Websocket API

These pages are works in progress that have not been moved to their respective sections yet.

Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-events.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-session.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-send-message.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-annotations.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-listener.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-api-adapter.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-server-api.adoc[] Unresolved directive in old_docs/websockets/jetty/chapter.adoc - include::websocket-jetty-client-api.adoc[]

Ant and Jetty

This chapter explains how to use Jetty with Ant and the Jetty Ant tasks.

Ant Jetty Plugin

The Ant Jetty plugin is a part of Jetty 9 under the jetty-ant module. This plugin makes it possible to start a Jetty web server directly from the Ant build script, and to embed the Jetty web server inside your build process. Its purpose is to provide almost the same functionality as the Jetty plugin for Maven: dynamic application reloading, working directly on web application sources, and tightly integrating with the build system.

<dependency>
   <groupId>org.eclipse.jetty</groupId>
   <artifactId>jetty-ant</artifactId>
 </dependency>

Preparing Your Project

To set up your project for Ant to run Jetty, you need a Jetty distribution and the jetty-ant Jar:

  1. Download a Jetty distribution and unpack it in the local filesystem.

  2. Get the jetty-ant Jar.

  3. Make a directory in your project called jetty-lib/.

  4. Copy all of the Jars in your Jetty distribution’s lib directory, and all its subdirectories, into your new jetty-lib dir. When copying the Jars, don’t preserve the Jetty distribution’s lib dir hierarchy – all the jars should be directly inside your ` jetty-lib` dir.

  5. Also copy the jetty-ant Jar you downloaded earlier into the jetty-lib dir.

  6. Make a directory in your project called jetty-temp.

Now you’re ready to edit or create your Ant build.xml file.

Preparing the build.xml file

Begin with an empty build.xml:

<project name="Jetty-Ant integration test" basedir=".">
</project>

Add a <taskdef> that imports all available Jetty tasks:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
     <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

</project>

Now you are ready to add a new target for running Jetty:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run />
  </target>

</project>

This is the minimal configuration you need. You can now start Jetty on the default port of 8080.

Starting Jetty via Ant

At the command line enter:

> ant jetty.run

Configuring the Jetty Container

A number of configuration options can help you set up the Jetty environment so that your web application has all the resources it needs:

ports and connectors

To configure the port that Jetty starts on you need to define a connector. First you need to configure a <typedef> for the Connector class and then define the connector in the Jetty tags:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

  <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <typedef name="connector" classname="org.eclipse.jetty.ant.types.Connector"
           classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <connectors>
        <connector port="8090"/>
      </connectors>
    </jetty.run>
  </target>

</project>
You can set the port to 0, which starts the Jetty server connector on an arbitrary available port. You can then access these values from system properties jetty.ant.server.port and jetty.ant.server.host.
login services

If your web application requires authentication and authorization services, you can configure these on the Jetty container. Here’s an example of how to set up an org.eclipse.jetty.security.HashLoginService:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="hashLoginService" classname="org.eclipse.jetty.security.HashLoginService"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <loginServices>
        <hashLoginService name="Test Realm" config="${basedir}/realm.properties"/>
      </loginServices>
    </jetty.run>
  </target>

</project>
request log

The requestLog option allows you to specify a request logger for the Jetty instance. You can either use the org.eclipse.jetty.server.NCSARequestLog class, or supply the name of your custom class:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run requestLog="com.acme.MyFancyRequestLog">
    </jetty.run>
  </target>

</project>
temporary directory

You can configure a directory as a temporary file store for uses such as expanding files and compiling JSPs by supplying the tempDirectory option:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run tempDirectory="${basedir}/jetty-temp">
    </jetty.run>
  </target>

</project>
other context handlers

You may need to configure some other context handlers to run at the same time as your web application. You can specify these other context handlers using the <contextHandlers> element. You need to supply a <typedef> for it before you can use it:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath"
          resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="contextHandlers" classname="org.eclipse.jetty.ant.types.ContextHandlers"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
     <contextHandlers>
       <contextHandler resourceBase="${basedir}/stuff" contextPath="/stuff"/>
     </contextHandlers>
    </jetty.run>
  </target>

</project>
system properties

As a convenience, you can configure system properties by using the <systemProperties> element. Be aware that, depending on the purpose of the system property, setting it from within the Ant execution may mean that it is evaluated too late, as the JVM evaluates some system properties on entry.

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <systemProperties>
        <systemProperty name="foo" value="bar"/>
      </systemProperties>
    </jetty.run>
  </target>

</project>
jetty XML file

If you have a lot of configuration to apply to the Jetty container, it can be more convenient to put it into a standard Jetty XML configuration file and have the Ant plugin apply it before starting Jetty:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run jettyXml="${basedir}/jetty.xml">
    </jetty.run>
  </target>

</project>
scanning for changes

The most useful mode in which to run the Ant plugin is for it to continue to execute Jetty and automatically restart your web application if any part of it changes (for example, your IDE recompiles the classes of the web application). The scanIntervalSeconds option controls how frequently the <jetty.run> task scans your web application/WAR file for changes. The default value of 0 disables scanning. Here’s an example where Jetty checks for changes every five seconds:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run scanIntervalSeconds="5">
    </jetty.run>
  </target>

</project>
stopping

In normal mode (daemon="false"), the <jetty.run> task runs until you cntrl-c it. It may be useful to script both the stop AND the start of Jetty. For such a case, we provide the <jetty.stop> task. + To use it, you need to provide a port and an identifying string to both the ` <jetty.run>` and the <jetty.stop> tasks, where <jetty.run> listens on the given port for a stop message containing the given string, and cleanly stops Jetty when it is received. The <jetty.stop> task sends this stop message. You can also optionally provide a stopWait value (in seconds), which is the length of time the <jetty.stop> task waits for confirmation that the stop succeeded:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run stopPort="9999" stopKey="9999">
    </jetty.run>
  </target>

  <target name="jetty.stop">
   <jetty.stop stopPort="9999" stopKey="9999" stopWait="10"/>
  </target>

</project>

To stop jetty via Ant, enter:

> ant jetty.stop
execution without pausing ant

Usually, the <jetty.run> task runs until you cntrl-c it, pausing the execution of Ant as it does so. In some cases, it may be useful to let Ant continue executing. For example, to run your unit tests you may need other tasks to execute while Jetty is running. For this case, we provide the daemon option. This defaults to false. For true, Ant continues to execute after starting Jetty. If Ant exits, so does Jetty. Understand that this option does not fork a new process for Jetty.

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run daemon="true">
    </jetty.run>
  </target>

</project>

Deploying a Web Application

Add a <typedef> for the org.eclipse.jetty.ant.AntWebAppContext class with name webApp, then add a <webApp> element to <jetty.run> to describe your web application. The following example deploys a web application that is expanded in the local directory foo/ to context path ` / `:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo" contextPath="/"/>
    </jetty.run>
  </target>

</project>
deploying a WAR file

It is not necessary to expand the web application into a directory. It is fine to deploy it as a WAR file:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo.war" contextPath="/"/>
    </jetty.run>
  </target>

</project>
deploying more than one web application

You can also deploy more than one web application:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo.war" contextPath="/"/>
      <webApp war="${basedir}/other    contextPath="/other"/>
      <webApp war="${basedir}/bar.war" contextPath="/bar"/>
    </jetty.run>
  </target>

</project>
Configuring the Web Application

As the org.eclipse.jetty.ant.AntWebAppContext class is an extension of the org.eclipse.jetty.webapp.WebAppContext class, you can configure it by adding attributes of the same name (without the set or add prefix) as the setter methods.

Here’s an example that specifies the location of the web.xml file (equivalent to method AntWebAppContext.setDescriptor()) and the web application’s temporary directory (equivalent to method AntWebAppContext.setTempDirectory()):

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp descriptor="${basedir}/web.xml" tempDirectory="${basedir}/my-temp" war="${basedir}/foo" contextPath="/"/>
    </jetty.run>
  </target>

</project>

Other extra configuration options for the AntWebAppContext include:

extra classes and Jars

If your web application’s classes and Jars do not reside inside WEB-INF of the resource base directory, you can use the <classes> and <jar> elements to tell Ant where to find them. Here’s an example:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp descriptor="${basedir}/web.xml" tempDirectory="${basedir}/my-temp" war="${basedir}/foo" contextPath="/">
        <classes dir="${basedir}/classes">
          <include name="**/*.class"/>
          <include name="**/*.properties"/>
        </classes>
        <lib dir="${basedir}/jars">
          <include name="**/*.jar"/>
          <exclude name="**/*.dll"/>
        </lib>
      </webApp>
    </jetty.run>
  </target>

</project>
context attributes

Jetty allows you to set up ServletContext attributes on your web application. You configure them in a context XML file that is applied to your WebAppContext instance prior to starting it. For convenience, the Ant plugin permits you to configure these directly in the build file. Here’s an example:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo" contextPath="/">
        <attributes>
          <attribute name="my.param" value="123"/>
        </attributes>
      </webApp>
    </jetty.run>
  </target>

</project>
jetty-env.xml file

If you are using features such as JNDI with your web application, you may need to configure a WEB-INF/jetty-env.xml file to define resources. If the structure of your web application project is such that the source of jetty-env.xml file resides somewhere other than WEB-INF, you can use the jettyEnvXml attribute to tell Ant where to find it:

<project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo" contextPath="/" jettyEnvXml="${basedir}/jetty-env.xml">
        <attributes>
      </webApp>
    </jetty.run>
  </target>

</project>
context XML file

You may prefer or even require to do some advanced configuration of your web application outside of the Ant build file. In this case, you can use a standard context XML configuration file which the Ant plugin applies to your web application before it is deployed. Be aware that the settings from the context XML file override those of the attributes and nested elements you defined in the build file.

project name="Jetty-Ant integration test" basedir=".">

  <path id="jetty.plugin.classpath">
    <fileset dir="jetty-lib" includes="*.jar"/>
  </path>

 <taskdef classpathref="jetty.plugin.classpath" resource="tasks.properties" loaderref="jetty.loader" />

 <typedef name="webApp" classname="org.eclipse.jetty.ant.AntWebAppContext"
          classpathref="jetty.plugin.classpath" loaderref="jetty.loader" />

  <target name="jetty.run">
    <jetty.run>
      <webApp war="${basedir}/foo" contextPath="/" contextXml="${basedir}/jetty-env.xml">
        <attributes>
      </webApp>
    </jetty.run>
  </target>

</project>

Frameworks

Spring Setup

You can assemble and configure Jetty in code or with almost any IoC style framework including Spring. If all you want to do is setup a Jetty server in your stock Spring usage, simply look at the xml snippet below as an example. If you want to replace the jetty-xml being used to start the normal Jetty distribution with spring, you may do so however currently it will not leverage the rest of the module system.

Jetty-Spring Module

The skeleton of a jetty spring module can be enabled from the jetty-distribution via the module mechanism. For example:

$ java -jar start.jar --add-to-start=spring

This (or the alternative --add-to-start=spring command) creates a ${jetty.home}/lib/spring directory and populates it with the jetty-spring integration jar. It does NOT supply the spring jars and their dependencies. You will need to download these and place them into jetty’s classpath - you can use the ${jetty.home}/lib/spring directory created by spring.mod for this purpose.

Using Spring to Configure Jetty

Configuring Jetty via Spring is simply a matter of calling the API as Spring beans. The following is an example mimicking the default jetty startup configuration.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<!-- =============================================================== -->
<!-- Configure the Jetty Server with Spring                          -->
<!-- This file is the similar to jetty.xml, but written in spring    -->
<!-- XmlBeanFactory format.                                          -->
<!-- =============================================================== -->

<beans>
    <bean id="contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
    <bean id="server" name="Main" class="org.eclipse.jetty.server.Server" init-method="start" destroy-method="stop">
        <constructor-arg>
            <bean id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
                <property name="minThreads" value="10"/>
                <property name="maxThreads" value="50"/>
            </bean>
        </constructor-arg>
        <property name="connectors">
            <list>
                <bean id="connector" class="org.eclipse.jetty.server.ServerConnector">
                    <constructor-arg ref="server"/>
                    <property name="port" value="8080"/>
                </bean>
            </list>
        </property>
        <property name="handler">
            <bean id="handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
            <property name="handlers">
                    <list>
                        <ref bean="contexts"/>
                        <bean id="defaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
                    </list>
                </property>
            </bean>
        </property>
        <property name="beans">
            <list>
                <bean id="deploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
                    <property name="contexts" ref="contexts"/>
                    <property name="appProviders">
                        <list>
                            <bean id="webAppProvider" class="org.eclipse.jetty.deploy.providers.WebAppProvider">
                                <property name="monitoredDirName" value="webapps"/>
                                <property name="scanInterval" value="1"/>
                                <property name="extractWars" value="true"/>
                            </bean>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
</beans>

OSGI

Introduction

The Jetty OSGi infrastructure provides a Jetty container inside an OSGi container. Traditional JavaEE webapps can be deployed, in addition to Jetty ContextHandlers, along with OSGi web bundles. In addition, the infrastructure also supports the OSGi HttpService interface.

General Setup

All of the Jetty jars contain manifest entries appropriate to ensure that they can be deployed into an OSGi container as bundles. You will need to install some jetty jars into your OSGi container. You can always find the Jetty jars either in the Maven Central repository, or you can download a distribution of Jetty. Here’s the absolute minimal set of Jetty jars:

Table 2. Minimal Bundles
Jar Bundle Symbolic Name

jetty-util

org.eclipse.jetty.util

jetty-http

org.eclipse.jetty.http

jetty-io

org.eclipse.jetty.io

jetty-security

org.eclipse.jetty.security

jetty-server

org.eclipse.jetty.server

jetty-servlet

org.eclipse.jetty.servlet

jetty-webapp

org.eclipse.jetty.webapp

jetty-deploy

org.eclipse.jetty.deploy

jetty-xml

org.eclipse.jetty.xml

jetty-servlet-api

org.eclipse.jetty.servlet-api

You must also install the Apache Aries SPI Fly bundles as many parts of Jetty - for example ALPN, websocket, annotations - use the ServiceLoader mechanism, which requires an OSGi Service Loader Mediator like SPI Fly:

Table 3. SPI Fly Bundles
Jar Bundle Symbolic Name Location

org.apache.aries.spifly:org.apache.aries.spifly.dynamic.bundle-1.2.4.jar

org.apache.aries.spifly.dynamic.bundle

Maven central

We strongly recommend that you also deploy the annotation-related jars also, as the Servlet Specification increasingly relies on annotations for functionality.

You will also need the OSGi Event Management service and the OSGi Configuration Management service. If your OSGi container does not automatically make these available, you will need to add them in a way appropriate to your container.

The Jetty OSGi Container

The jetty-osgi-boot jar

Now that you have the basic set of Jetty jars installed, you can install the jetty-osgi-boot.jar bundle, downloadable from the maven central repo here.

This bundle will instantiate and make available the Jetty OSGi container when it is started. If this bundle is not auto-started upon installation into your OSGi container, you should start it manually using a command appropriate for your container.

Customizing the Jetty Container

Before going ahead with the install, you may want to customize the Jetty container. In general this is done by a combination of System properties and the usual Jetty xml configuration files. The way you define the System properties will depend on which OSGi container you are using, so ensure that you are familiar with how to set them for your environment. In the following examples, we will assume that the OSGi container allows us to set System properties as simple name=value pairs.

The available System properties are:

jetty.http.port

If not specified, this defaults to the usual jetty port of 8080.

jetty.home

Either this property or the jetty.home.bundle must be specified. This property should point to a file system location that has an etc/ directory containing xml files to configure the Jetty container on startup. For example:

jetty.home=/opt/custom/jetty

Where /opt/custom/jetty contains:

etc/jetty.xml
etc/jetty-selector.xml
etc/jetty-deployer.xml
etc/jetty-special.xml
jetty.home.bundle

Either this property or the jetty.home property must be specified. This property should specify the symbolic name of a bundle which contains a directory called jettyhome/. The jettyhome/ directory should have a subdirectory called etc/ that contains the xml files to be applied to Jetty on startup. The jetty-osgi-boot.jar contains a jettyhome/ directory with a default set of xml configuration files. Here’s how you would specify it:

jetty.home.bundle=org.eclipse.jetty.osgi.boot

Here’s a partial listing of that jar that shows you the names of the xml files contained within it:

META-INF/MANIFEST.MF
jettyhome/etc/jetty.xml
jettyhome/etc/jetty-deployer.xml
jettyhome/etc/jetty-http.xml
jetty.etc.config.urls

This specifies the paths of the xml files that are to be used. If not specified, they default to:

etc/jetty.xml,etc/jetty-http.xml,etc/jetty-deployer.xml

Note that the paths can either be relative or absolute, or a mixture. If the path is relative, it is resolved against either jetty.home or jetty.home.bundle, whichever was specified. You can use this ability to mix and match jetty configuration files to add functionality, such as adding in a https connector. Here’s an example of adding a HTTPS connector, using the relevant files from the jetty-distribution:

etc/jetty.xml, etc/jetty-http.xml, /opt/jetty/etc/jetty-ssl.xml, /opt/jetty/etc/jetty-https.xml, etc/jetty-deployer.xml

Note that regardless of whether you set the jetty.home or jetty.home.bundle property, when Jetty executes the configuration files, it will set an appropriate value for jetty.home so that references in xml files to <property name="jetty.home"> will work. Be careful, however, if you are mixing and matching relative and absolute configuration file paths: the value of jetty.home is determined from the resolved location of the relative files only.

The Jetty Container as an OSGi Service

You can now go ahead and deploy the jetty-osgi-boot.jar into your OSGi container. A Jetty server instance will be created, the xml config files applied to it, and then published as an OSGi service. Normally, you will not need to interact with this service instance, however you can retrieve a reference to it using the usual OSGi API:

org.osgi.framework.BundleContext bc;
org.osgi.framework.ServiceReference ref = bc.getServiceReference("org.eclipse.jetty.server.Server");

The Server service has a couple of properties associated with it that you can retrieve using the org.osgi.framework.ServiceReference.getProperty(String) method:

managedServerName

The Jetty Server instance created by the jetty-osgi-boot.jar will be called "defaultJettyServer"

jetty.etc.config.urls

The list of xml files resolved from either jetty.home or jetty.home.bundle/jettyhome

Adding More Jetty Servers

As we have seen in the previous section, the jetty-osgi-boot code will create an org.eclipse.jetty.server.Server instance, apply the xml configuration files specified by jetty.etc.config.urls System property to it, and then register it as an OSGi Service. The name associated with this default instance is defaultJettyServer.

You can create other Server instances, register them as OSGi Services, and the jetty-osgi-boot code will notice them, and configure them so that they can deploy ContextHandlers and webapp bundles. When you deploy webapps or ContextHandlers as bundles or Services (see sections below) you can target them to be deployed to a particular server instance via the Server’s name.

Here’s an example of how to create a new Server instance and register it with OSGi so that the jetty-osgi-boot code will find it and configure it so it can be a deployment target:

public class Activator implements BundleActivator
{

    public void start(BundleContext context) throws Exception
    {

        Server server = new Server();
        //do any setup on Server in here
        String serverName = "fooServer";
        Dictionary serverProps = new Hashtable();
        //define the unique name of the server instance
        serverProps.put("managedServerName", serverName);
        serverProps.put("jetty.http.port", "9999");
        //let Jetty apply some configuration files to the Server instance
        serverProps.put("jetty.etc.config.urls", "file:/opt/jetty/etc/jetty.xml,file:/opt/jetty/etc/jetty-selector.xml,file:/opt/jetty/etc/jetty-deployer.xml");
        //register as an OSGi Service for Jetty to find
        context.registerService(Server.class.getName(), server, serverProps);

    }
}

Now that we have created a new Server called "fooServer", we can deploy webapps and ContextHandlers as Bundles or Services to it (see below for more information on this). Here’s an example of deploying a webapp as a Service and targeting it to the "fooServer" Server we created above:

public class Activator implements BundleActivator
{

    public void start(BundleContext context) throws Exception
    {

        //Create a webapp context as a Service and target it at the "fooServer" Server instance
        WebAppContext webapp = new WebAppContext();
        Dictionary props = new Hashtable();
        props.put("war",".");
        props.put("contextPath","/acme");
        props.put("managedServerName", "fooServer");
        context.registerService(ContextHandler.class.getName(),webapp,props);
    }
}

Deploying Bundles as Webapps

The Jetty OSGi container listens for the installation of bundles, and will automatically attempt to deploy any that appear to be webapps.

Any of the following criteria are sufficient for Jetty to deploy the bundle as a webapp:

Bundle contains a WEB-INF/web.xml file

If the bundle contains a web descriptor, then it is automatically deployed. This is an easy way to deploy classic JavaEE webapps.

Bundle MANIFEST contains Jetty-WarFolderPath (for releases prior tojetty-9.3) or Jetty-WarResourcePath

This is the location within the bundle of the webapp resources. Typically this would be used if the bundle is not a pure webapp, but rather the webapp is a component of the bundle. Here’s an example of a bundle where the resources of the webapp are not located at the root of the bundle, but rather inside the subdirectory web/ :

MANIFEST:

Bundle-Name: Web
Jetty-WarResourcePath: web
Import-Package: javax.servlet;version="3.1",
 javax.servlet.resources;version="3.1"
Bundle-SymbolicName: com.acme.sample.web

Bundle contents:

META-INF/MANIFEST.MF
web/index.html
web/foo.html
web/WEB-INF/web.xml
com/acme/sample/web/MyStuff.class
com/acme/sample/web/MyOtherStuff.class
Bundle MANIFEST contains Web-ContextPath

This header can be used in conjunction with either of the two preceding headers to control the context path to which the webapp is deployed, or alone to identify that the bundle’s contents should be published as a webapp. This header is part of the RFC-66 specification for using webapps with OSGi. Here’s an example based on the previous one where we use the Web-ContextPath header to set its deployment context path to be "/sample" :

MANIFEST:

Bundle-Name: Web
Jetty-WarResourcePath: web
Web-ContextPath: /sample
Import-Package: javax.servlet;version="3.1",
 javax.servlet.resources;version="3.1"
Bundle-SymbolicName: com.acme.sample.web

You can also define extra headers in your bundle MANIFEST that help customize the web app to be deployed:

Jetty-defaultWebXmlFilePath

The location of a webdefault.xml file to apply to the webapp. The location can be either absolute (either absolute path or file: url), or relative (in which case it is interpreted as relative to the bundle root). Defaults to the webdefault.xml file built into the Jetty OSGi container.

Jetty-WebXmlFilePath

The location of the web.xml file. The location can be either absolute (either absolute path or file: url), or relative (in which case it is interpreted as relative to the bundle root). Defaults to WEB-INF/web.xml

Jetty-extraClassPath

A classpath of additional items to add to the webapp’s classloader.

Jetty-bundleInstall

The path to the base folder that overrides the computed bundle installation - mostly useful for those OSGi frameworks that unpack bundles by default.

Require-TldBundle

A comma separated list of bundle symbolic names of bundles containing TLDs that this webapp depends upon.

managedServerName

The name of the Server instance to which to deploy this webapp bundle. If not specified, defaults to the default Server instance called "defaultJettyServer".

Jetty-WarFragmentResourcePath

The path within a fragment hosted by the web-bundle that contains static resources for the webapp. The path is appended to the base resource for the webapp (see Jetty-WarResourcePath).

Jetty-WarPrependFragmentResourcePath

The path within a fragment hosted by the web-bundle that contains static resources for the webapp. The path is prepended to the base resource for the webapp (see Jetty-WarResourcePath).

Jetty-ContextFilePath

A comma separated list of paths within the webapp bundle to Jetty context files that will be applied to the webapp. Alternatively you may include a single Jetty context file called jetty-webapp-context.xml in the webapp bundle’s META-INF directory and it will be automatically applied to the webapp.

Determining the Context Path for a Webapp Bundle

As we have seen in the previous section, if the bundle MANIFEST contains the RFC-66 header Web-ContextPath, Jetty will use that as the context path. If the MANIFEST does not contain that header, then Jetty will concoct a context path based on the last element of the bundle’s location (by calling Bundle.getLocation()) after stripping off any file extensions.

For example, suppose we have a bundle whose location is:

file://some/where/over/the/rainbow/oz.war

The corresponding synthesized context path would be:

/oz
Extra Properties Available for Webapp Bundles

You can further customize your webapp by including a Jetty context xml file that is applied to the webapp. This xml file must be placed in META-INF of the bundle, and must be called jetty-webapp-context.xml.

Here’s an example of a webapp bundle listing containing such a file:

META-INF/MANIFEST.MF
META-INF/jetty-webapp-context.xml
web/index.html
web/foo.html
web/WEB-INF/web.xml
com/acme/sample/web/MyStuff.class
com/acme/sample/web/MyOtherStuff.class

Here’s an example of the contents of a META-INF/jetty-webapp-context.xml file:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="defaultsDescriptor"><Property name="bundle.root"/>META-INF/webdefault.xml</Set>
</Configure>

As you can see, it is a normal context xml file used to set up a webapp. There are, however, some additional useful properties that can be referenced

Server

This is a reference to the Jetty org.eclipse.jetty.server.Server instance to which the webapp being configured in the context xml file will be deployed.

bundle.root

This is a reference to the org.eclipse.jetty.util.resource.Resource that represents the location of the Bundle. Note that this could be either a directory in the file system if the OSGi container automatically unpacks bundles, or it may be a jar:file: url if the bundle remains packed.

Deploying Bundles as Jetty ContextHandlers

In addition to deploying webapps, the Jetty OSGi container listens for the installation of bundles that are not heavyweight webapps, but rather use the flexible Jetty-specific concept of ContextHandlers.

The following is the criteria used to decide if a bundle can be deployed as a ContextHandler:

Bundle MANIFEST contains Jetty-ContextFilePath

A comma separated list of names of context files - each one of which represents a ContextHandler that should be deployed by Jetty. The context files can be inside the bundle, external to the bundle somewhere on the file system, or external to the bundle in the jetty.home directory.

A context file that is inside the bundle:

Jetty-ContextFilePath: ./a/b/c/d/foo.xml

A context file that is on the file system:

Jetty-ContextFilePath: /opt/app/contexts/foo.xml

A context file that is relative to jetty.home:

Jetty-ContextFilePath: contexts/foo.xml

A number of different context files:

Jetty-ContextFilePath: ./a/b/c/d/foo.xml,/opt/app/contexts/foo.xml,contexts/foo.xml

Other MANIFEST properties that can be used to configure the deployment of the ContextHandler:

managedServerName

The name of the Server instance to which to deploy this webapp bundle. If not specified, defaults to the default Server instance called "defaultJettyServer".

Determining the Context Path for a ContextHandler Bundle

Usually, the context path for the ContextHandler will be set by the context xml file. However, you can override any path set in the context xml file by using the Web-ContextPath header in the MANIFEST.

Extra Properties Available for Context Xml Files

Before the Jetty OSGi container applies a context xml file found in a Jetty-ContextFilePath MANIFEST header, it sets a few useful propertiesthat can be referred to within the xml file:

Server

This is a reference to the Jetty org.eclipse.jetty.server.Server instance to which the ContextHandler being configured in the context xml file will be deployed.

bundle.root

This is a reference to the org.eclipse.jetty.util.resource.Resource that represents the location of the Bundle (obtained by calling Bundle.getLocation()). Note that this could be either a directory in the file system if the OSGi container automatically unpacks bundles, or it may be a jar:file: url if the bundle remains packed.

Here’s an example of a context xml file that makes use of these properties:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure class="org.eclipse.jetty.server.handler.ContextHandler">

  <!-- Get root for static content, could be on file system or this bundle -->
  <Call id="res" class="org.eclipse.jetty.util.resource.Resource" name="newResource">
    <Arg><Property name="bundle.root"/></Arg>
  </Call>

  <Ref refid="res">
    <Call id="base" name="addPath">
      <Arg>/static/</Arg>
    </Call>
  </Ref>

  <Set name="contextPath">/unset</Set>

  <!-- Set up the base resource for static files relative to inside bundle -->
  <Set name="baseResource">
     <Ref refid="base"/>
  </Set>

  <Set name="handler">
    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
      <Set name="welcomeFiles">
        <Array type="String">
          <Item>index.html</Item>
        </Array>
      </Set>
      <Set name="cacheControl">max-age=3600,public</Set>
    </New>
  </Set>

</Configure>

Deploying Services as Webapps

In addition to listening for bundles whose format or MANIFEST entries define a webapp or ContextHandler for to be deployed, the Jetty OSGi container also listens for the registration of OSGi services that are instances of org.eclipse.jetty.webapp.WebAppContext. So you may programmatically create a WebAppContext, register it as a service, and have Jetty pick it up and deploy it.

Here’s an example of doing that with a simple bundle that serves static content, and an org.osgi.framework.BundleActivator that instantiates the WebAppContext:

The bundle contents:

META-INF/MANIFEST.MF
index.html
com/acme/osgi/Activator.class

The MANIFEST.MF:

Bundle-Classpath: .
Bundle-Name: Jetty OSGi Test WebApp
DynamicImport-Package: org.eclipse.jetty.*;version="[9.0,10.0)"
Bundle-Activator: com.acme.osgi.Activator
Import-Package: org.eclipse.jetty.server.handler;version="[9.0,10)",
 org.eclipse.jetty.webapp;version="[9.0,10)",
 org.osgi.framework;version= "[1.5,2)",
 org.osgi.service.cm;version="1.2.0",
 org.osgi.service.packag eadmin;version="[1.2,2)",
 org.osgi.service.startlevel;version="1.0.0",
 org.osgi.service.url;version="1.0.0",
 org.osgi.util.tracker;version= "1.3.0",
 org.xml.sax,org.xml.sax.helpers
Bundle-SymbolicName: com.acme.testwebapp

The Activator code:

public void start(BundleContext context) throws Exception
{
    WebAppContext webapp = new WebAppContext();
    Dictionary props = new Hashtable();
    props.put("Jetty-WarResourcePath",".");
    props.put("contextPath","/acme");
    context.registerService(WebAppContext.class.getName(),webapp,props);
}

The above setup is sufficient for Jetty to recognize and deploy the WebAppContext at /acme.

As the example shows, you can use OSGi Service properties in order to communicate extra configuration information to Jetty:

Jetty-WarFolderPath (for releases prior to 9.3) or Jetty-WarResourcePath

The location within the bundle of the root of the static resources for the webapp

Web-ContextPath

The context path at which to deploy the webapp.

Jetty-defaultWebXmlFilePath

The location within the bundle of a webdefault.xml file to apply to the webapp. Defaults to that of the Jetty OSGi container.

Jetty-WebXmlFilePath

The location within the bundle of the web.xml file. Defaults to WEB-INF/web.xml

Jetty-extraClassPath

A classpath of additional items to add to the webapp’s classloader.

Jetty-bundleInstall

The path to the base folder that overrides the computed bundle installation - mostly useful for those OSGi frameworks that unpack bundles by default.

Require-TldBundle

A comma separated list of bundle symbolic names of bundles containing TLDs that this webapp depends upon.

managedServerName

The name of the Server instance to which to deploy this webapp. If not specified, defaults to the default Server instance called "defaultJettyServer".

Jetty-WarFragmentResourcePath

The path within a fragment hosted by the web-bundle that contains static resources for the webapp. The path is appended to the base resource for the webapp (see Jetty-WarResourcePath).

Jetty-WarPrependFragmentResourcePath

The path within a fragment hosted by the web-bundle that contains static resources for the webapp. The path is prepended to the base resource for the webapp (see Jetty-WarResourcePath).

Deploying Services as ContextHandlers

Similarly to WebApp`Contexts, the Jetty OSGi container can detect the registration of an OSGi Service that represents a ContextHandler and ensure that it is deployed. The ContextHandler can either be fully configured before it is registered as an OSGi service - in which case the Jetty OSGi container will merely deploy it - or the ContextHandler can be partially configured, with the Jetty OSGi container completing the configuration via a context xml file and properties associated with the Service.

Here’s an example of doing that with a simple bundle that serves static content with an org.osgi.framework.BundleActivator that instantiates a ContextHandler and registers it as an OSGi Service, passing in properties that define a context xml file and context path for Jetty to apply upon deployment:

The bundle contents:

META-INF/MANIFEST.MF
static/index.html
acme.xml
com/acme/osgi/Activator.class
com/acme/osgi/Activator$1.class

The MANIFEST:

Bundle-Classpath: .
Bundle-Name: Jetty OSGi Test Context
DynamicImport-Package: org.eclipse.jetty.*;version="[9.0,10.0)"
Bundle-Activator: com.acme.osgi.Activator
Import-Package: javax.servlet;version="2.6.0",
 javax.servlet.resources;version="2.6.0",
 org.eclipse.jetty.server.handler;version="[9.0,10)",
 org.osgi.framework;version="[1.5,2)",
 org.osgi.service.cm;version="1.2.0",
 org.osgi.service.packageadmin;version="[1.2,2)",
 org.osgi.service.startlevel;version="1.0.0.o",
 org.osgi.service.url;version="1.0.0",
 org.osgi.util.tracker;version="1.3.0",
 org.xml.sax,org.xml.sax.helpers
Bundle-SymbolicName: com.acme.testcontext

The Activator code:

public void start(final BundleContext context) throws Exception
{
    ContextHandler ch = new ContextHandler();
    ch.addEventListener(new ServletContextListener () {

            @Override
            public void contextInitialized(ServletContextEvent sce)
            {
               System.err.println("Context is initialized");
            }

            @Override
            public void contextDestroyed(ServletContextEvent sce)
            {
                System.err.println("Context is destroyed!");
            }

    });
    Dictionary props = new Hashtable();
    props.put("Web-ContextPath","/acme");
    props.put("Jetty-ContextFilePath", "acme.xml");
    context.registerService(ContextHandler.class.getName(),ch,props);
}

The contents of the acme.xml context file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure class="org.eclipse.jetty.server.handler.ContextHandler">

  <!-- Get root for static content, could be on file system or this bundle -->
  <Call id="res" class="org.eclipse.jetty.util.resource.Resource" name="newResource">
    <Arg><Property name="bundle.root"/></Arg>
  </Call>

  <Ref refid="res">
    <Call id="base" name="addPath">
      <Arg>/static/</Arg>
    </Call>
  </Ref>

  <Set name="contextPath">/unset</Set>

  <!-- Set up the base resource for static files relative to inside bundle -->
  <Set name="baseResource">
     <Ref refid="base"/>
  </Set>

  <Set name="handler">
    <New class="org.eclipse.jetty.server.handler.ResourceHandler">
      <Set name="welcomeFiles">
        <Array type="String">
          <Item>index.html</Item>
        </Array>
      </Set>
      <Set name="cacheControl">max-age=3600,public</Set>
    </New>
  </Set>

</Configure>

You may also use the following OSGi Service properties:

managedServerName

The name of the Server instance to which to deploy this webapp. If not specified, defaults to the default Server instance called "defaultJettyServer".

Extra Properties Available for Context Xml Files

Before the Jetty OSGi container applies a context xml file found in a Jetty-ContextFilePath property, it sets a few useful properties that can be referred to within the xml file:

Server

This is a reference to the Jetty org.eclipse.jetty.server.Server instance to which the ContextHandler being configured in the context xml file will be deployed.

bundle.root

This is a reference to the org.eclipse.jetty.util.resource.Resource that represents the location of the Bundle publishing the ContextHandler as a Service (obtained by calling Bundle.getLocation()). Note that this could be either a directory in the file system if the OSGi container automatically unpacks bundles, or it may be a jar:file: url if the bundle remains packed.

In the example above, you can see both of these properties being used in the context xml file.

Support for the OSGi Service Platform Enterprise Specification

The Jetty OSGi container implements several aspects of the Enterprise Specification v4.2 for the WebAppContexts and ContextHandlers that it deploys from either bundles or OSGi services as outlined in foregoing sections.

Context Attributes

For each WebAppContext or ContextHandler, the following context attribute is set, as required by section 128.6.1 Bundle Context page 427:

osgi-bundleContext

The value of this attribute is the BundleContext representing the Bundle associated with the WebAppContext or ContextHandler.

Service Attributes

As required by the specification section 128.3.4 Publishing the Servlet Context page 421, each WebAppContext and ContextHandler deployed by the Jetty OSGi container is also published as an OSGi service (unless it has been already - see sections 1.6 and 1.7). The following properties are associated with these services:

osgi.web.symbolicname

The symbolic name of the Bundle associated with the WebAppContext or ContextHandler

osgi.web.version

The Bundle-Version header from the Bundle associated with the WebAppContext or ContextHandler

osgi.web.contextpath

The context path of the WebAppContext or ContextHandler

OSGi Events

As required by the specification section 128.5 Events pg 426, the following OSGi Event Admin events will be posted:

org/osgi/service/web/DEPLOYING

The Jetty OSGi container is about to deploy a WebAppContext or ContextHandler

org/osgi/service/web/DEPLOYED

The Jetty OSGi container has finished deploying a WebAppContext or ContextHandler and it is in service

org/osgi/service/web/UNDEPLOYING

The Jetty OSGi container is about to undeploy a WebAppContext or ContextHandler

org/osgi/service/web/UNDEPLOYED

The Jetty OSGi container has finished undeploying a WebAppContext or ContextHandler and it is no longer in service

org/osgi/service/web/FAILED

The Jetty OSGi container failed to deploy a WebAppContext or ContextHandler

Using JSPs

Setup

In order to use JSPs with your webapps and bundles you will need to install the JSP and JSTL jars and their dependencies into your OSGi container. Some you will find in the Jetty distribution, whereas others you will need to download from Maven central. Here is the list of recommended jars (NOTE the version numbers may change in future):

Table 4. Jars Required for JSP
Jar Bundle Symbolic Name Location

The annotation jars

org.mortbay.jasper:apache-el

org.mortbay.jasper.apache-el

Distribution lib/apache-jsp

org.mortbay.jasper:apache-jsp

org.mortbay.jasper.apache-jsp

Distribution lib/apache-jsp

org.eclipse.jetty:apache-jsp

org.eclipse.jetty.apache-jsp

Distribution lib/apache-jsp

org.eclipse.jdt.core-3.8.2.v20130121.jar

org.eclipse.jdt.core.compiler.batch

Distribution lib/apache-jsp

org.eclipse.jetty.osgi:jetty-osgi-boot-jsp

org.eclipse.jetty.osgi.boot.jsp

Maven central

  1. As of Jetty 9.2.3 the jetty-osgi-boot-jsp bundle changed to using Apache Jasper as the JSP implementation. Prior to this the Glassfish Jasper implementation was used, which had a different set of dependencies - pay careful attention to the jars listed both at the top of this page and in this section, as deployment of other jars can cause incomplete or incorrect package resolution in the OSGi container.

  2. The order of deployment is important. Deploy these bundles in the order shown or you may experience strange failures in the compilation of jsps. This can be hard to diagnose but is almost always caused by the ServletContainerInitializer in the org.eclipse.jetty.apache-jsp bundle for the jsp container not being invoked due to incorrect startup of the annotation jars.

For the JSTL library, we recommend the use of the implementation from Glassfish, as it has fewer dependencies:

Table 5. Jars Required for Glassfish JSTL
Jar Bundle Symbolic Name The jsp jars

org.eclipse.jetty.orbit:javax.servlet.jsp.jstl-1.2.0.v201105211821.jar

javax.servlet.jsp.jstl

However, if you wish, you may use the JSTL implementation from Apache instead, although you will need to source some dependency jars with suitable OSGi manifests:

Table 6. Jars Required for Apache JSTL
Jar Bundle Symbolic Name Location

The jsp jars

org.apache.taglibs:taglibs-standard-spec:jar:1.2.1

org.apache.taglibs.taglibs-standard-spec

Distribution lib/apache-jstl

org.apache.taglibs:taglibs-standard-spec:jar:1.2.1

org.apache.taglibs.standard-impl

Distribution lib/apache-jstl

org.apache.xalan 2.7.1

Try Eclipse Orbit

org.apache.xml.serializer 2.7.1

Try Eclipse Orbit

The jetty-osgi-boot-jsp jar

To be able to use JSPs you will need to also install the jetty-osgi-boot-jsp.jar into your OSGi container. This jar can be obtained from maven central here.

This bundle acts as a fragment extension to the jetty-osgi-boot.jar and adds in support for using JSP.

Using TagLibs

The Jetty JSP OSGi container will make available the JSTL tag library to all webapps. If you only use this tag library, then your webapp will work without any further modification.

However, if you make use of other taglibs, you will need to ensure that they are installed into the OSGi container, and also define some System properties and/or MANIFEST headers in your webapp. This is necessary because the classloading regime used by the OSGi container is very different than that used by JSP containers, and the MANIFEST of a normal webapp does not contain enough information for the OSGi environment to allow a JSP container to find and resolve TLDs referenced in the webapp’s .jsp files.

Firstly, lets look at an example of a web bundle’s modified MANIFEST file so you get an idea of what is required. This example is a web bundle that uses the Spring servlet framework:

Bundle-SymbolicName: com.acme.sample
Bundle-Name: WebSample
Web-ContextPath: taglibs
Import-Bundle: org.springframework.web.servlet
Require-TldBundle: org.springframework.web.servlet
Bundle-Version: 1.0.0
Import-Package: org.eclipse.virgo.web.dm;version="[3.0.0,4.0.0)",org.s
 pringframework.context.config;version="[2.5.6,4.0.0)",org.springframe
 work.stereotype;version="[2.5.6,4.0.0)",org.springframework.web.bind.
 annotation;version="[2.5.6,4.0.0)",org.springframework.web.context;ve
 rsion="[2.5.6,4.0.0)",org.springframework.web.servlet;version="[2.5.6
 ,4.0.0)",org.springframework.web.servlet.view;version="[2.5.6,4.0.0)"

The Require-TldBundle header tells the Jetty OSGi container that this bundle contains TLDs that need to be passed over to the JSP container for processing. The Import-Bundle header ensures that the implementation classes for these TLDs will be available to the webapp on the OSGi classpath.

The format of the Require-TldBundle header is a comma separated list of one or more symbolic names of bundles containing TLDs.

Container Path Taglibs

Some TLD jars are required to be found on the Jetty OSGi container’s classpath, rather than considered part of the web bundle’s classpath. For example, this is true of JSTL and Java Server Faces. The Jetty OSGi container takes care of JSTL for you, but you can control which other jars are considered as part of the container’s classpath by using the System property org.eclipse.jetty.osgi.tldbundles:

org.eclipse.jetty.osgi.tldbundles

System property defined on the OSGi environment that is a comma separated list of symbolic names of bundles containing taglibs that will be treated as if they are on the container’s classpath for web bundles. For example:

org.eclipse.jetty.osgi.tldbundles=com.acme.special.tags,com.foo.web,org.bar.web.framework

You will still need to define the Import-Bundle header in the MANIFEST file for the web bundle to ensure that the TLD bundles are on the OSGi classpath.

Alternatively or additionally, you can define a pattern as a context attribute that will match symbolic bundle names in the OSGi environment containing TLDs that should be considered as discovered from the container’s classpath.

org.eclipse.jetty.server.webapp.containerIncludeBundlePattern

This pattern must be specified as a context attribute of the WebAppContext representing the web bundle. Unless you are deploying your own WebAppContext (see Deploying Services as Webapps), you won’t have a reference to the WebAppContext to do this. In that case, it can be specified on the org.eclipse.jetty.deploy.DeploymentManager, where it will be applied to every webapp deployed by the Jetty OSGi container. The jetty-osgi-boot.jar contains the default jettyhome/etc/jetty-deploy.xml file where the DeploymentManager is defined. To set the pattern, you will need to provide your own etc files - see the section on customizing the jetty container for how to do this. Here’s how the jetty-deploy.xml file would look if we defined a pattern that matched all bundle symbolic names ending in "tag" and "web":

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
    <Call name="addBean">
      <Arg>
        <New id="DeploymentManager" class="org.eclipse.jetty.deploy.DeploymentManager">
          <Set name="contexts">
            <Ref refid="Contexts" />
          </Set>
          <Call name="setContextAttribute">
            <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeBundlePattern</Arg>
            <Arg>.*\.tag$|.*\.web$</Arg>
          </Call>
        </New>
      </Arg>
    </Call>
</Configure>

Again, you will still need to define suitable Import-Bundle headers in your web bundle MANIFEST to ensure that bundles matching the pattern are available on the OSGi class path.

Using Annotations/ServletContainerInitializers

Annotations are very much part of the Servlet 3.0 and 3.1 specifications. In order to use them with Jetty in OSGi, you will need to deploy some extra jars into your OSGi container:

Table 7. Jars Required for Annotations
Jar Bundle Symbolic Name Location

The spifly jars

org.ow2.asm:asm-7.0.jar

org.objectweb.asm

Maven central

org.ow2.asm:asm-commons-7.0.jar

org.objectweb.asm.commons

Maven central

org.ow2.asm:asm-tree-7.0.jar

org.objectweb.asm.tree

Maven central

javax.annotation:javax.annotation-api-1.2.jar

javax.annotation-api

Maven central

jta api version 1.1.1 (eg org.apache.geronimo.specs:geronimo-jta_1.1_spec-1.1.1.jar)*

Maven central

javax mail api version 1.4.1 (eg org.eclipse.jetty.orbit:javax.mail.glassfish-1.4.1.v201005082020.jar)*

Maven central

jetty-jndi

org.eclipse.jetty.jndi

Distribution lib/

jetty-plus

org.eclipse.jetty.plus

Distribution lib/

jetty-annotations

org.eclipse.jetty.annotations

Distribution lib/

If you wish to use JSPs you will need to deploy these annotation-related jars.
You may be able to deploy later versions or other providers of these specifications, however these particular versions are known to have correct manifests and have been tested and known to work with OSGi.

Even if your webapp itself does not not use annotations, you may need to deploy these jars because your webapp depends on a Jetty module or a 3rd party library that uses a javax.servlet.ServletContainerInitializer. This interface requires annotation support. It is implemented by providers of code that extend the capabilities of the container. An example of this is the Jetty JSR356 Websocket implementation, although it is being used increasingly commonly in popular libraries like Spring, Jersey and JSP containers.

To find ServletContainerInitializers on the classpath, Jetty uses the Java ServiceLoader mechanism. For this to function in OSGi, you will need an OSGi R5 compatible container, and have support for the Service Loader Mediator. Jetty has been tested with the Aries SpiFly module, which is the reference implementation of the Service Loader Mediator, and is listed in the jars above.

OSGi Containers

Felix

The Jetty OSGi integration has been successfully tested against Felix 5.0.0.

You will require the following extra Felix services, available as separately downloadable jars:

Unfortunately, as of Felix 4.x there is a difficultly with the resolution of the javax.transaction package. A description of the problem and hint to solving it is described [here].

The simplest solution for this is to extract the default.properties file from the felix.jar, change the declaration of the javax.sql and javax.transaction packages and set the changed lines as the value of the org.osgi.framework.system.packages property in the conf/config.properties file.

The default.properties file defines the default org.osgi.framework.system.packages property like this:

# Default packages exported by system bundle.
org.osgi.framework.system.packages=org.osgi.framework; version=1.7.0, \
 org.osgi.framework.hooks.bundle; version=1.1.0, \
 org.osgi.framework.hooks.resolver; version=1.0.0, \
 org.osgi.framework.hooks.service; version=1.1.0, \
 org.osgi.framework.hooks.weaving; version=1.0.0, \
 org.osgi.framework.launch; version=1.1.0, \
 org.osgi.framework.namespace; version=1.0.0, \
 org.osgi.framework.startlevel; version=1.0.0, \
 org.osgi.framework.wiring; version=1.1.0, \
 org.osgi.resource; version=1.0.0, \
 org.osgi.service.packageadmin; version=1.2.0, \
 org.osgi.service.startlevel; version=1.1.0, \
 org.osgi.service.url; version=1.0.0, \
 org.osgi.util.tracker; version=1.5.1 \
 ${jre-${java.specification.version}}

The last line must be substituted for one of the definitions further down in the file that is suitable for the jvm you are using.

You will take these lines and copy them into the conf/config.properties file, after having replaced the line $\{jre-$\{java.specification.version}} with all of the lines relevant to your version of the jvm.

For example, for a 1.7 jvm, you will find this property definition:

jre-1.7=, \
 javax.accessibility;uses:="javax.swing.text";version="0.0.0.1_007_JavaSE", \
 javax.activation;version="0.0.0.1_007_JavaSE", \
 javax.activity;version="0.0.0.1_007_JavaSE", \
 javax.annotation.processing;uses:="javax.tools,javax.lang.model,javax.lang.model.element,javax.lang.model.util";version="0.0.0.1_007_JavaSE", \
 javax.annotation;version="0.0.0.1_007_JavaSE", \
 javax.crypto.interfaces;uses:="javax.crypto.spec,javax.crypto";version="0.0.0.1_007_JavaSE", \
 javax.crypto.spec;uses:="javax.crypto";version="0.0.0.1_007_JavaSE", \
 javax.crypto;uses:="javax.crypto.spec";version="0.0.0.1_007_JavaSE", \
 javax.imageio.event;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", \
 javax.imageio.metadata;uses:="org.w3c.dom,javax.imageio";version="0.0.0.1_007_JavaSE", \
 javax.imageio.plugins.bmp;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", \
 javax.imageio.plugins.jpeg;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", \
 javax.imageio.spi;uses:="javax.imageio.stream,javax.imageio,javax.imageio.metadata";version="0.0.0.1_007_JavaSE", \
 javax.imageio.stream;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", \
 javax.imageio;uses:="javax.imageio.metadata,javax.imageio.stream,javax.imageio.spi,javax.imageio.event";version="0.0.0.1_007_JavaSE", \
 javax.jws.soap;version="0.0.0.1_007_JavaSE", \
 javax.jws;version="0.0.0.1_007_JavaSE", \
 javax.lang.model.element;uses:="javax.lang.model.type,javax.lang.model";version="0.0.0.1_007_JavaSE", \
 javax.lang.model.type;uses:="javax.lang.model.element,javax.lang.model";version="0.0.0.1_007_JavaSE", \
 javax.lang.model.util;uses:="javax.lang.model,javax.lang.model.element,javax.annotation.processing,javax.lang.model.type";version="0.0.0.1_007_JavaSE", \
 javax.lang.model;version="0.0.0.1_007_JavaSE", \
 javax.management.loading;uses:="javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management.modelmbean;uses:="javax.management,javax.management.loading";version="0.0.0.1_007_JavaSE", \
 javax.management.monitor;uses:="javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management.openmbean;uses:="javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management.relation;uses:="javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management.remote.rmi;uses:="javax.management.remote,javax.security.auth,javax.management,javax.management.loading,javax.naming,javax.rmi.ssl,org.omg.CORBA,org.omg.CORBA_2_3.portable,org.omg.CORBA.portable,javax.rmi.CORBA,javax.rmi";version="0.0.0.1_007_JavaSE", \
 javax.management.remote;uses:="javax.security.auth,javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management.timer;uses:="javax.management";version="0.0.0.1_007_JavaSE", \
 javax.management;uses:="javax.management.loading,javax.management.openmbean";version="0.0.0.1_007_JavaSE", \
 javax.naming.directory;uses:="javax.naming";version="0.0.0.1_007_JavaSE", \
 javax.naming.event;uses:="javax.naming,javax.naming.directory";version="0.0.0.1_007_JavaSE", \
 javax.naming.ldap;uses:="javax.naming,javax.naming.directory,javax.net.ssl,javax.naming.event";version="0.0.0.1_007_JavaSE", \
 javax.naming.spi;uses:="javax.naming,javax.naming.directory";version="0.0.0.1_007_JavaSE", \
 javax.naming;uses:="javax.naming.spi";version="0.0.0.1_007_JavaSE", \
 javax.net.ssl;uses:="javax.security.cert,javax.security.auth.x500,javax.net";version="0.0.0.1_007_JavaSE", \
 javax.net;version="0.0.0.1_007_JavaSE", \
 javax.print.attribute.standard;uses:="javax.print.attribute";version="0.0.0.1_007_JavaSE", \
 javax.print.attribute;version="0.0.0.1_007_JavaSE", \
 javax.print.event;uses:="javax.print,javax.print.attribute";version="0.0.0.1_007_JavaSE", \
 javax.print;uses:="javax.print.attribute,javax.print.event,javax.print.attribute.standard";version="0.0.0.1_007_JavaSE", \
 javax.rmi.CORBA;uses:="org.omg.CORBA,org.omg.CORBA_2_3.portable,org.omg.CORBA.portable,org.omg.SendingContext";version="0.0.0.1_007_JavaSE", \
 javax.rmi.ssl;uses:="javax.net,javax.net.ssl";version="0.0.0.1_007_JavaSE", \
 javax.rmi;uses:="org.omg.CORBA,javax.rmi.CORBA";version="0.0.0.1_007_JavaSE", \
 javax.script;version="0.0.0.1_007_JavaSE", \
 javax.security.auth.callback;version="0.0.0.1_007_JavaSE", \
 javax.security.auth.kerberos;uses:="javax.security.auth,javax.crypto";version="0.0.0.1_007_JavaSE", \
 javax.security.auth.login;uses:="javax.security.auth,javax.security.auth.callback";version="0.0.0.1_007_JavaSE", \
 javax.security.auth.spi;uses:="javax.security.auth.callback,javax.security.auth.login,javax.security.auth";version="0.0.0.1_007_JavaSE", \
 javax.security.auth.x500;uses:="javax.security.auth";version="0.0.0.1_007_JavaSE", \
 javax.security.auth;version="0.0.0.1_007_JavaSE", \
 javax.security.cert;version="0.0.0.1_007_JavaSE", \
 javax.security.sasl;uses:="javax.security.auth.callback";version="0.0.0.1_007_JavaSE", \
 javax.sound.midi.spi;uses:="javax.sound.midi";version="0.0.0.1_007_JavaSE", \
 javax.sound.midi;uses:="javax.sound.midi.spi";version="0.0.0.1_007_JavaSE", \
 javax.sound.sampled.spi;uses:="javax.sound.sampled";version="0.0.0.1_007_JavaSE", \
 javax.sound.sampled;uses:="javax.sound.sampled.spi";version="0.0.0.1_007_JavaSE", \
 javax.sql.rowset.serial;uses:="javax.sql.rowset";version="0.0.0.1_007_JavaSE", \
 javax.sql.rowset.spi;uses:="javax.sql,javax.naming,javax.sql.rowset";version="0.0.0.1_007_JavaSE", \
 javax.sql.rowset;uses:="javax.sql,javax.sql.rowset.serial,javax.sql.rowset.spi";version="0.0.0.1_007_JavaSE", \
 javax.sql;uses:="javax.transaction.xa";version="0.0.0.1_007_JavaSE", \
 javax.swing.border;uses:="javax.swing";version="0.0.0.1_007_JavaSE", \
 javax.swing.colorchooser;uses:="javax.swing,javax.swing.border,javax.swing.event,javax.swing.text";version="0.0.0.1_007_JavaSE", \
 javax.swing.event;uses:="javax.swing,javax.swing.text,javax.swing.table,javax.swing.tree,javax.swing.undo";version="0.0.0.1_007_JavaSE", \
 javax.swing.filechooser;uses:="javax.swing";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf.basic;uses:="javax.swing.border,javax.swing,javax.swing.plaf,javax.swing.text,javax.swing.event,javax.swing.colorchooser,javax.accessibility,javax.swing.filechooser,javax.swing.text.html,javax.sound.sampled,javax.swing.table,javax.swing.plaf.synth,javax.swing.tree";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf.metal;uses:="javax.swing.plaf,javax.swing,javax.swing.border,javax.swing.text,javax.swing.plaf.basic,javax.swing.filechooser,javax.swing.event,javax.swing.tree";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf.multi;uses:="javax.accessibility,javax.swing,javax.swing.plaf,javax.swing.filechooser,javax.swing.text,javax.swing.tree";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf.nimbus;uses:="javax.swing,javax.swing.plaf,javax.swing.border,javax.swing.plaf.synth";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf.synth;uses:="javax.swing,javax.swing.plaf,javax.swing.text,javax.swing.border,javax.swing.plaf.basic,javax.swing.colorchooser,javax.swing.event,javax.xml.parsers,org.xml.sax,org.xml.sax.helpers,javax.swing.table,javax.swing.tree";version="0.0.0.1_007_JavaSE", \
 javax.swing.plaf;uses:="javax.swing,javax.swing.border,javax.accessibility,javax.swing.filechooser,javax.swing.text,javax.swing.tree";version="0.0.0.1_007_JavaSE", \
 javax.swing.table;uses:="javax.swing.event,javax.swing.plaf,javax.swing.border,javax.swing,javax.accessibility";version="0.0.0.1_007_JavaSE", \
 javax.swing.text.html.parser;uses:="javax.swing.text,javax.swing.text.html";version="0.0.0.1_007_JavaSE", \
 javax.swing.text.html;uses:="javax.swing.event,javax.swing.text,javax.accessibility,javax.swing,javax.swing.plaf,javax.swing.border,javax.swing.undo";version="0.0.0.1_007_JavaSE", \
 javax.swing.text.rtf;uses:="javax.swing.text";version="0.0.0.1_007_JavaSE", \
 javax.swing.text;uses:="javax.swing.event,javax.swing.tree,javax.swing.undo,javax.swing,javax.swing.plaf,javax.swing.plaf.basic,javax.print,javax.print.attribute,javax.accessibility,javax.swing.text.html";version="0.0.0.1_007_JavaSE", \
 javax.swing.tree;uses:="javax.swing.event,javax.swing,javax.swing.border,javax.swing.plaf,javax.swing.plaf.basic";version="0.0.0.1_007_JavaSE", \
 javax.swing.undo;uses:="javax.swing,javax.swing.event";version="0.0.0.1_007_JavaSE", \
 javax.swing;uses:="javax.swing.event,javax.accessibility,javax.swing.text,javax.swing.plaf,javax.swing.border,javax.swing.tree,javax.swing.table,javax.swing.colorchooser,javax.swing.plaf.basic,javax.swing.text.html,javax.swing.filechooser,javax.print,javax.print.attribute,javax.swing.plaf.metal";version="0.0.0.1_007_JavaSE", \
 javax.tools;uses:="javax.lang.model.element,javax.annotation.processing,javax.lang.model";version="0.0.0.1_007_JavaSE", \
 javax.transaction.xa;version="0.0.0.1_007_JavaSE", \
 javax.transaction;version="0.0.0.1_007_JavaSE", \
 javax.xml.bind.annotation.adapters;uses:="javax.xml.bind";version="0.0.0.1_007_JavaSE", \
 javax.xml.bind.annotation;uses:="javax.xml.transform,javax.xml.bind,javax.xml.parsers,javax.xml.transform.dom,org.w3c.dom";version="0.0.0.1_007_JavaSE", \
 javax.xml.bind.attachment;uses:="javax.activation";version="0.0.0.1_007_JavaSE", \
 javax.xml.bind.helpers;uses:="javax.xml.bind.annotation.adapters,javax.xml.transform.dom,org.w3c.dom,org.xml.sax,javax.xml.bind.attachment,javax.xml.stream,javax.xml.transform,javax.xml.transform.stream,javax.xml.validation,javax.xml.transform.sax,javax.xml.bind,javax.xml.parsers";version="0.0.0.1_007_JavaSE", \
 javax.xml.bind.util;uses:="javax.xml.transform.sax,javax.xml.bind,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", \
 javax.xml.bind;uses:="javax.xml.validation,javax.xml.namespace,javax.xml.datatype,javax.xml.transform,javax.xml.bind.annotation,javax.xml.transform.stream,org.w3c.dom,javax.xml.bind.attachment,javax.xml.stream,javax.xml.bind.annotation.adapters,org.xml.sax";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto.dom;uses:="javax.xml.crypto,org.w3c.dom";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto.dsig.dom;uses:="javax.xml.crypto.dsig,javax.xml.crypto,org.w3c.dom,javax.xml.crypto.dom";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto.dsig.keyinfo;uses:="javax.xml.crypto";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto.dsig.spec;uses:="javax.xml.crypto";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto.dsig;uses:="javax.xml.crypto,javax.xml.crypto.dsig.spec,javax.xml.crypto.dsig.keyinfo";version="0.0.0.1_007_JavaSE", \
 javax.xml.crypto;uses:="javax.xml.crypto.dsig.keyinfo";version="0.0.0.1_007_JavaSE", \
 javax.xml.datatype;uses:="javax.xml.namespace";version="0.0.0.1_007_JavaSE", \
 javax.xml.namespace;version="0.0.0.1_007_JavaSE", \
 javax.xml.parsers;uses:="javax.xml.validation,org.w3c.dom,org.xml.sax,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", \
 javax.xml.soap;uses:="javax.activation,javax.xml.namespace,org.w3c.dom,javax.xml.transform.dom,javax.xml.transform";version="0.0.0.1_007_JavaSE", \
 javax.xml.stream.events;uses:="javax.xml.namespace,javax.xml.stream";version="0.0.0.1_007_JavaSE", \
 javax.xml.stream.util;uses:="javax.xml.stream,javax.xml.stream.events,javax.xml.namespace";version="0.0.0.1_007_JavaSE", \
 javax.xml.stream;uses:="javax.xml.stream.events,javax.xml.namespace,javax.xml.stream.util,javax.xml.transform";version="0.0.0.1_007_JavaSE", \
 javax.xml.transform.dom;uses:="javax.xml.transform,org.w3c.dom";version="0.0.0.1_007_JavaSE", \
 javax.xml.transform.sax;uses:="org.xml.sax.ext,javax.xml.transform,org.xml.sax,javax.xml.transform.stream";version="0.0.0.1_007_JavaSE", \
 javax.xml.transform.stax;uses:="javax.xml.stream,javax.xml.transform,javax.xml.stream.events";version="0.0.0.1_007_JavaSE", \
 javax.xml.transform.stream;uses:="javax.xml.transform";version="0.0.0.1_007_JavaSE", \
 javax.xml.transform;version="0.0.0.1_007_JavaSE", \
 javax.xml.validation;uses:="org.w3c.dom.ls,javax.xml.transform,javax.xml.transform.stream,org.xml.sax,org.w3c.dom";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.handler.soap;uses:="javax.xml.ws.handler,javax.xml.namespace,javax.xml.soap,javax.xml.bind";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.handler;uses:="javax.xml.ws,javax.xml.namespace";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.http;uses:="javax.xml.ws";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.soap;uses:="javax.xml.ws.spi,javax.xml.ws,javax.xml.soap";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.spi.http;version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.spi;uses:="javax.xml.ws,javax.xml.ws.wsaddressing,javax.xml.transform,org.w3c.dom,javax.xml.namespace,javax.xml.ws.handler,javax.xml.bind";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws.wsaddressing;uses:="javax.xml.bind.annotation,javax.xml.namespace,org.w3c.dom,javax.xml.transform,javax.xml.bind,javax.xml.ws,javax.xml.ws.spi";version="0.0.0.1_007_JavaSE", \
 javax.xml.ws;uses:="javax.xml.ws.handler,javax.xml.ws.spi,javax.xml.ws.spi.http,javax.xml.transform,org.w3c.dom,javax.xml.bind.annotation,javax.xml.transform.stream,javax.xml.bind,javax.xml.namespace";version="0.0.0.1_007_JavaSE", \
 javax.xml.xpath;uses:="org.xml.sax,javax.xml.namespace";version="0.0.0.1_007_JavaSE", \
 javax.xml;version="0.0.0.1_007_JavaSE", \
 org.ietf.jgss;version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA.DynAnyPackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA.ORBPackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA.TypeCodePackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA.portable;uses:="org.omg.CORBA,org.omg.CORBA_2_3.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA;uses:="org.omg.CORBA.portable,org.omg.CORBA.DynAnyPackage,org.omg.CORBA.ORBPackage,org.omg.CORBA_2_3.portable,org.omg.CORBA.TypeCodePackage";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA_2_3.portable;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.CORBA_2_3;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.CosNaming.NamingContextExtPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.CosNaming.NamingContextPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.CosNaming";version="0.0.0.1_007_JavaSE", \
 org.omg.CosNaming;uses:="org.omg.CORBA.portable,org.omg.CORBA,org.omg.PortableServer,org.omg.CosNaming.NamingContextPackage,org.omg.CosNaming.NamingContextExtPackage";version="0.0.0.1_007_JavaSE", \
 org.omg.Dynamic;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.DynamicAny.DynAnyFactoryPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.DynamicAny.DynAnyPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.DynamicAny;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.DynamicAny.DynAnyFactoryPackage,org.omg.DynamicAny.DynAnyPackage";version="0.0.0.1_007_JavaSE", \
 org.omg.IOP.CodecFactoryPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.IOP.CodecPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.IOP;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.IOP.CodecFactoryPackage,org.omg.IOP.CodecPackage";version="0.0.0.1_007_JavaSE", \
 org.omg.Messaging;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableInterceptor.ORBInitInfoPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableInterceptor;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.IOP,org.omg.PortableInterceptor.ORBInitInfoPackage,org.omg.CORBA_2_3.portable,org.omg.Dynamic";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer.CurrentPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer.POAManagerPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer.POAPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer.ServantLocatorPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer.portable;uses:="org.omg.CORBA,org.omg.PortableServer";version="0.0.0.1_007_JavaSE", \
 org.omg.PortableServer;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.PortableServer.CurrentPackage,org.omg.PortableServer.POAManagerPackage,org.omg.PortableServer.POAPackage,org.omg.PortableServer.portable,org.omg.CORBA_2_3,org.omg.PortableServer.ServantLocatorPackage";version="0.0.0.1_007_JavaSE", \
 org.omg.SendingContext;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", \
 org.omg.stub.java.rmi;uses:="javax.rmi.CORBA";version="0.0.0.1_007_JavaSE", \
 org.w3c.dom.bootstrap;uses:="org.w3c.dom";version="0.0.0.1_007_JavaSE", \
 org.w3c.dom.events;uses:="org.w3c.dom,org.w3c.dom.views";version="0.0.0.1_007_JavaSE", \
 org.w3c.dom.ls;uses:="org.w3c.dom,org.w3c.dom.events,org.w3c.dom.traversal";version="0.0.0.1_007_JavaSE", \
 org.w3c.dom;version="0.0.0.1_007_JavaSE", \
 org.xml.sax.ext;uses:="org.xml.sax,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", \
 org.xml.sax.helpers;uses:="org.xml.sax";version="0.0.0.1_007_JavaSE", \
 org.xml.sax;version="0.0.0.1_007_JavaSE"

Remove the definition for the javax.transaction packages, and remove the uses:= clause for the javax.sql packages (but leaving the version clause). Concatenate all the lines together. You’ll wind up with something like this in your conf/config.properties file:

org.osgi.framework.system.packages=org.osgi.framework;version=1.7.0, org.osgi.framework.hooks.bundle;version=1.1.0, org.osgi.framework.hooks.resolver;version=1.0.0, org.osgi.framework.hooks.service;version=1.1.0, org.osgi.framework.hooks.weaving;version=1.0.0, org.osgi.framework.launch;version=1.1.0, org.osgi.framework.namespace;version=1.0.0, org.osgi.framework.startlevel;version=1.0.0, org.osgi.framework.wiring;version=1.1.0, org.osgi.resource;version=1.0.0, org.osgi.service.packageadmin; version=1.2.0, org.osgi.service.startlevel; version=1.1.0, org.osgi.service.url;version=1.0.0, org.osgi.util.tracker;version=1.5.1 javax.accessibility;uses:="javax.swing.text";version="0.0.0.1_007_JavaSE", javax.activation;version="0.0.0.1_007_JavaSE", javax.activity;version="0.0.0.1_007_JavaSE", javax.annotation.processing;uses:="javax.tools,javax.lang.model,javax.lang.model.element,javax.lang.model.util";version="0.0.0.1_007_JavaSE", javax.annotation;version="0.0.0.1_007_JavaSE", javax.crypto.interfaces;uses:="javax.crypto.spec,javax.crypto";version="0.0.0.1_007_JavaSE", javax.crypto.spec;uses:="javax.crypto";version="0.0.0.1_007_JavaSE", javax.crypto;uses:="javax.crypto.spec";version="0.0.0.1_007_JavaSE", javax.imageio.event;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", javax.imageio.metadata;uses:="org.w3c.dom,javax.imageio";version="0.0.0.1_007_JavaSE", javax.imageio.plugins.bmp;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", javax.imageio.plugins.jpeg;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", javax.imageio.spi;uses:="javax.imageio.stream,javax.imageio,javax.imageio.metadata";version="0.0.0.1_007_JavaSE", javax.imageio.stream;uses:="javax.imageio";version="0.0.0.1_007_JavaSE", javax.imageio;uses:="javax.imageio.metadata,javax.imageio.stream,javax.imageio.spi,javax.imageio.event";version="0.0.0.1_007_JavaSE", javax.jws.soap;version="0.0.0.1_007_JavaSE", javax.jws;version="0.0.0.1_007_JavaSE", javax.lang.model.element;uses:="javax.lang.model.type,javax.lang.model";version="0.0.0.1_007_JavaSE", javax.lang.model.type;uses:="javax.lang.model.element,javax.lang.model";version="0.0.0.1_007_JavaSE", javax.lang.model.util;uses:="javax.lang.model,javax.lang.model.element,javax.annotation.processing,javax.lang.model.type";version="0.0.0.1_007_JavaSE", javax.lang.model;version="0.0.0.1_007_JavaSE", javax.management.loading;uses:="javax.management";version="0.0.0.1_007_JavaSE", javax.management.modelmbean;uses:="javax.management,javax.management.loading";version="0.0.0.1_007_JavaSE", javax.management.monitor;uses:="javax.management";version="0.0.0.1_007_JavaSE", javax.management.openmbean;uses:="javax.management";version="0.0.0.1_007_JavaSE", javax.management.relation;uses:="javax.management";version="0.0.0.1_007_JavaSE", javax.management.remote.rmi;uses:="javax.management.remote,javax.security.auth,javax.management,javax.management.loading,javax.naming,javax.rmi.ssl,org.omg.CORBA,org.omg.CORBA_2_3.portable,org.omg.CORBA.portable,javax.rmi.CORBA,javax.rmi";version="0.0.0.1_007_JavaSE", javax.management.remote;uses:="javax.security.auth,javax.management";version="0.0.0.1_007_JavaSE", javax.management.timer;uses:="javax.management";version="0.0.0.1_007_JavaSE", javax.management;uses:="javax.management.loading,javax.management.openmbean";version="0.0.0.1_007_JavaSE", javax.naming.directory;uses:="javax.naming";version="0.0.0.1_007_JavaSE", javax.naming.event;uses:="javax.naming,javax.naming.directory";version="0.0.0.1_007_JavaSE", javax.naming.ldap;uses:="javax.naming,javax.naming.directory,javax.net.ssl,javax.naming.event";version="0.0.0.1_007_JavaSE", javax.naming.spi;uses:="javax.naming,javax.naming.directory";version="0.0.0.1_007_JavaSE", javax.naming;uses:="javax.naming.spi";version="0.0.0.1_007_JavaSE", javax.net.ssl;uses:="javax.security.cert,javax.security.auth.x500,javax.net";version="0.0.0.1_007_JavaSE", javax.net;version="0.0.0.1_007_JavaSE", javax.print.attribute.standard;uses:="javax.print.attribute";version="0.0.0.1_007_JavaSE", javax.print.attribute;version="0.0.0.1_007_JavaSE", javax.print.event;uses:="javax.print,javax.print.attribute";version="0.0.0.1_007_JavaSE", javax.print;uses:="javax.print.attribute,javax.print.event,javax.print.attribute.standard";version="0.0.0.1_007_JavaSE", javax.rmi.CORBA;uses:="org.omg.CORBA,org.omg.CORBA_2_3.portable,org.omg.CORBA.portable,org.omg.SendingContext";version="0.0.0.1_007_JavaSE", javax.rmi.ssl;uses:="javax.net,javax.net.ssl";version="0.0.0.1_007_JavaSE", javax.rmi;uses:="org.omg.CORBA,javax.rmi.CORBA";version="0.0.0.1_007_JavaSE", javax.script;version="0.0.0.1_007_JavaSE", javax.security.auth.callback;version="0.0.0.1_007_JavaSE", javax.security.auth.kerberos;uses:="javax.security.auth,javax.crypto";version="0.0.0.1_007_JavaSE", javax.security.auth.login;uses:="javax.security.auth,javax.security.auth.callback";version="0.0.0.1_007_JavaSE", javax.security.auth.spi;uses:="javax.security.auth.callback,javax.security.auth.login,javax.security.auth";version="0.0.0.1_007_JavaSE", javax.security.auth.x500;uses:="javax.security.auth";version="0.0.0.1_007_JavaSE", javax.security.auth;version="0.0.0.1_007_JavaSE", javax.security.cert;version="0.0.0.1_007_JavaSE", javax.security.sasl;uses:="javax.security.auth.callback";version="0.0.0.1_007_JavaSE", javax.sound.midi.spi;uses:="javax.sound.midi";version="0.0.0.1_007_JavaSE", javax.sound.midi;uses:="javax.sound.midi.spi";version="0.0.0.1_007_JavaSE", javax.sound.sampled.spi;uses:="javax.sound.sampled";version="0.0.0.1_007_JavaSE", javax.sound.sampled;uses:="javax.sound.sampled.spi";version="0.0.0.1_007_JavaSE", javax.sql.rowset.serial;version="0.0.0.1_007_JavaSE", javax.sql.rowset.spi;version="0.0.0.1_007_JavaSE", javax.sql.rowset;version="0.0.0.1_007_JavaSE", javax.sql;version="0.0.0.1_007_JavaSE", javax.swing.border;uses:="javax.swing";version="0.0.0.1_007_JavaSE", javax.swing.colorchooser;uses:="javax.swing,javax.swing.border,javax.swing.event,javax.swing.text";version="0.0.0.1_007_JavaSE", javax.swing.event;uses:="javax.swing,javax.swing.text,javax.swing.table,javax.swing.tree,javax.swing.undo";version="0.0.0.1_007_JavaSE", javax.swing.filechooser;uses:="javax.swing";version="0.0.0.1_007_JavaSE", javax.swing.plaf.basic;uses:="javax.swing.border,javax.swing,javax.swing.plaf,javax.swing.text,javax.swing.event,javax.swing.colorchooser,javax.accessibility,javax.swing.filechooser,javax.swing.text.html,javax.sound.sampled,javax.swing.table,javax.swing.plaf.synth,javax.swing.tree";version="0.0.0.1_007_JavaSE", javax.swing.plaf.metal;uses:="javax.swing.plaf,javax.swing,javax.swing.border,javax.swing.text,javax.swing.plaf.basic,javax.swing.filechooser,javax.swing.event,javax.swing.tree";version="0.0.0.1_007_JavaSE", javax.swing.plaf.multi;uses:="javax.accessibility,javax.swing,javax.swing.plaf,javax.swing.filechooser,javax.swing.text,javax.swing.tree";version="0.0.0.1_007_JavaSE", javax.swing.plaf.nimbus;uses:="javax.swing,javax.swing.plaf,javax.swing.border,javax.swing.plaf.synth";version="0.0.0.1_007_JavaSE", javax.swing.plaf.synth;uses:="javax.swing,javax.swing.plaf,javax.swing.text,javax.swing.border,javax.swing.plaf.basic,javax.swing.colorchooser,javax.swing.event,javax.xml.parsers,org.xml.sax,org.xml.sax.helpers,javax.swing.table,javax.swing.tree";version="0.0.0.1_007_JavaSE", javax.swing.plaf;uses:="javax.swing,javax.swing.border,javax.accessibility,javax.swing.filechooser,javax.swing.text,javax.swing.tree";version="0.0.0.1_007_JavaSE", javax.swing.table;uses:="javax.swing.event,javax.swing.plaf,javax.swing.border,javax.swing,javax.accessibility";version="0.0.0.1_007_JavaSE", javax.swing.text.html.parser;uses:="javax.swing.text,javax.swing.text.html";version="0.0.0.1_007_JavaSE", javax.swing.text.html;uses:="javax.swing.event,javax.swing.text,javax.accessibility,javax.swing,javax.swing.plaf,javax.swing.border,javax.swing.undo";version="0.0.0.1_007_JavaSE", javax.swing.text.rtf;uses:="javax.swing.text";version="0.0.0.1_007_JavaSE", javax.swing.text;uses:="javax.swing.event,javax.swing.tree,javax.swing.undo,javax.swing,javax.swing.plaf,javax.swing.plaf.basic,javax.print,javax.print.attribute,javax.accessibility,javax.swing.text.html";version="0.0.0.1_007_JavaSE", javax.swing.tree;uses:="javax.swing.event,javax.swing,javax.swing.border,javax.swing.plaf,javax.swing.plaf.basic";version="0.0.0.1_007_JavaSE", javax.swing.undo;uses:="javax.swing,javax.swing.event";version="0.0.0.1_007_JavaSE", javax.swing;uses:="javax.swing.event,javax.accessibility,javax.swing.text,javax.swing.plaf,javax.swing.border,javax.swing.tree,javax.swing.table,javax.swing.colorchooser,javax.swing.plaf.basic,javax.swing.text.html,javax.swing.filechooser,javax.print,javax.print.attribute,javax.swing.plaf.metal";version="0.0.0.1_007_JavaSE", javax.tools;uses:="javax.lang.model.element,javax.annotation.processing,javax.lang.model";version="0.0.0.1_007_JavaSE", javax.xml.bind.annotation.adapters;uses:="javax.xml.bind";version="0.0.0.1_007_JavaSE", javax.xml.bind.annotation;uses:="javax.xml.transform,javax.xml.bind,javax.xml.parsers,javax.xml.transform.dom,org.w3c.dom";version="0.0.0.1_007_JavaSE", javax.xml.bind.attachment;uses:="javax.activation";version="0.0.0.1_007_JavaSE", javax.xml.bind.helpers;uses:="javax.xml.bind.annotation.adapters,javax.xml.transform.dom,org.w3c.dom,org.xml.sax,javax.xml.bind.attachment,javax.xml.stream,javax.xml.transform,javax.xml.transform.stream,javax.xml.validation,javax.xml.transform.sax,javax.xml.bind,javax.xml.parsers";version="0.0.0.1_007_JavaSE", javax.xml.bind.util;uses:="javax.xml.transform.sax,javax.xml.bind,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", javax.xml.bind;uses:="javax.xml.validation,javax.xml.namespace,javax.xml.datatype,javax.xml.transform,javax.xml.bind.annotation,javax.xml.transform.stream,org.w3c.dom,javax.xml.bind.attachment,javax.xml.stream,javax.xml.bind.annotation.adapters,org.xml.sax";version="0.0.0.1_007_JavaSE", javax.xml.crypto.dom;uses:="javax.xml.crypto,org.w3c.dom";version="0.0.0.1_007_JavaSE", javax.xml.crypto.dsig.dom;uses:="javax.xml.crypto.dsig,javax.xml.crypto,org.w3c.dom,javax.xml.crypto.dom";version="0.0.0.1_007_JavaSE", javax.xml.crypto.dsig.keyinfo;uses:="javax.xml.crypto";version="0.0.0.1_007_JavaSE", javax.xml.crypto.dsig.spec;uses:="javax.xml.crypto";version="0.0.0.1_007_JavaSE", javax.xml.crypto.dsig;uses:="javax.xml.crypto,javax.xml.crypto.dsig.spec,javax.xml.crypto.dsig.keyinfo";version="0.0.0.1_007_JavaSE", javax.xml.crypto;uses:="javax.xml.crypto.dsig.keyinfo";version="0.0.0.1_007_JavaSE", javax.xml.datatype;uses:="javax.xml.namespace";version="0.0.0.1_007_JavaSE", javax.xml.namespace;version="0.0.0.1_007_JavaSE", javax.xml.parsers;uses:="javax.xml.validation,org.w3c.dom,org.xml.sax,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", javax.xml.soap;uses:="javax.activation,javax.xml.namespace,org.w3c.dom,javax.xml.transform.dom,javax.xml.transform";version="0.0.0.1_007_JavaSE", javax.xml.stream.events;uses:="javax.xml.namespace,javax.xml.stream";version="0.0.0.1_007_JavaSE", javax.xml.stream.util;uses:="javax.xml.stream,javax.xml.stream.events,javax.xml.namespace";version="0.0.0.1_007_JavaSE", javax.xml.stream;uses:="javax.xml.stream.events,javax.xml.namespace,javax.xml.stream.util,javax.xml.transform";version="0.0.0.1_007_JavaSE", javax.xml.transform.dom;uses:="javax.xml.transform,org.w3c.dom";version="0.0.0.1_007_JavaSE", javax.xml.transform.sax;uses:="org.xml.sax.ext,javax.xml.transform,org.xml.sax,javax.xml.transform.stream";version="0.0.0.1_007_JavaSE", javax.xml.transform.stax;uses:="javax.xml.stream,javax.xml.transform,javax.xml.stream.events";version="0.0.0.1_007_JavaSE", javax.xml.transform.stream;uses:="javax.xml.transform";version="0.0.0.1_007_JavaSE", javax.xml.transform;version="0.0.0.1_007_JavaSE", javax.xml.validation;uses:="org.w3c.dom.ls,javax.xml.transform,javax.xml.transform.stream,org.xml.sax,org.w3c.dom";version="0.0.0.1_007_JavaSE", javax.xml.ws.handler.soap;uses:="javax.xml.ws.handler,javax.xml.namespace,javax.xml.soap,javax.xml.bind";version="0.0.0.1_007_JavaSE", javax.xml.ws.handler;uses:="javax.xml.ws,javax.xml.namespace";version="0.0.0.1_007_JavaSE", javax.xml.ws.http;uses:="javax.xml.ws";version="0.0.0.1_007_JavaSE", javax.xml.ws.soap;uses:="javax.xml.ws.spi,javax.xml.ws,javax.xml.soap";version="0.0.0.1_007_JavaSE", javax.xml.ws.spi.http;version="0.0.0.1_007_JavaSE", javax.xml.ws.spi;uses:="javax.xml.ws,javax.xml.ws.wsaddressing,javax.xml.transform,org.w3c.dom,javax.xml.namespace,javax.xml.ws.handler,javax.xml.bind";version="0.0.0.1_007_JavaSE", javax.xml.ws.wsaddressing;uses:="javax.xml.bind.annotation,javax.xml.namespace,org.w3c.dom,javax.xml.transform,javax.xml.bind,javax.xml.ws,javax.xml.ws.spi";version="0.0.0.1_007_JavaSE", javax.xml.ws;uses:="javax.xml.ws.handler,javax.xml.ws.spi,javax.xml.ws.spi.http,javax.xml.transform,org.w3c.dom,javax.xml.bind.annotation,javax.xml.transform.stream,javax.xml.bind,javax.xml.namespace";version="0.0.0.1_007_JavaSE", javax.xml.xpath;uses:="org.xml.sax,javax.xml.namespace";version="0.0.0.1_007_JavaSE", javax.xml;version="0.0.0.1_007_JavaSE", org.ietf.jgss;version="0.0.0.1_007_JavaSE", org.omg.CORBA.DynAnyPackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", org.omg.CORBA.ORBPackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", org.omg.CORBA.TypeCodePackage;uses:="org.omg.CORBA";version="0.0.0.1_007_JavaSE", org.omg.CORBA.portable;uses:="org.omg.CORBA,org.omg.CORBA_2_3.portable";version="0.0.0.1_007_JavaSE", org.omg.CORBA;uses:="org.omg.CORBA.portable,org.omg.CORBA.DynAnyPackage,org.omg.CORBA.ORBPackage,org.omg.CORBA_2_3.portable,org.omg.CORBA.TypeCodePackage";version="0.0.0.1_007_JavaSE", org.omg.CORBA_2_3.portable;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.CORBA_2_3;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.CosNaming.NamingContextExtPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.CosNaming.NamingContextPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.CosNaming";version="0.0.0.1_007_JavaSE", org.omg.CosNaming;uses:="org.omg.CORBA.portable,org.omg.CORBA,org.omg.PortableServer,org.omg.CosNaming.NamingContextPackage,org.omg.CosNaming.NamingContextExtPackage";version="0.0.0.1_007_JavaSE", org.omg.Dynamic;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.DynamicAny.DynAnyFactoryPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.DynamicAny.DynAnyPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.DynamicAny;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.DynamicAny.DynAnyFactoryPackage,org.omg.DynamicAny.DynAnyPackage";version="0.0.0.1_007_JavaSE", org.omg.IOP.CodecFactoryPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.IOP.CodecPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.IOP;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.IOP.CodecFactoryPackage,org.omg.IOP.CodecPackage";version="0.0.0.1_007_JavaSE", org.omg.Messaging;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableInterceptor.ORBInitInfoPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableInterceptor;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.IOP,org.omg.PortableInterceptor.ORBInitInfoPackage,org.omg.CORBA_2_3.portable,org.omg.Dynamic";version="0.0.0.1_007_JavaSE", org.omg.PortableServer.CurrentPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableServer.POAManagerPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableServer.POAPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableServer.ServantLocatorPackage;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.PortableServer.portable;uses:="org.omg.CORBA,org.omg.PortableServer";version="0.0.0.1_007_JavaSE", org.omg.PortableServer;uses:="org.omg.CORBA,org.omg.CORBA.portable,org.omg.PortableServer.CurrentPackage,org.omg.PortableServer.POAManagerPackage,org.omg.PortableServer.POAPackage,org.omg.PortableServer.portable,org.omg.CORBA_2_3,org.omg.PortableServer.ServantLocatorPackage";version="0.0.0.1_007_JavaSE", org.omg.SendingContext;uses:="org.omg.CORBA,org.omg.CORBA.portable";version="0.0.0.1_007_JavaSE", org.omg.stub.java.rmi;uses:="javax.rmi.CORBA";version="0.0.0.1_007_JavaSE", org.w3c.dom.bootstrap;uses:="org.w3c.dom";version="0.0.0.1_007_JavaSE", org.w3c.dom.events;uses:="org.w3c.dom,org.w3c.dom.views";version="0.0.0.1_007_JavaSE", org.w3c.dom.ls;uses:="org.w3c.dom,org.w3c.dom.events,org.w3c.dom.traversal";version="0.0.0.1_007_JavaSE", org.w3c.dom;version="0.0.0.1_007_JavaSE", org.xml.sax.ext;uses:="org.xml.sax,org.xml.sax.helpers";version="0.0.0.1_007_JavaSE", org.xml.sax.helpers;uses:="org.xml.sax";version="0.0.0.1_007_JavaSE", org.xml.sax;version="0.0.0.1_007_JavaSE"

You should now be able to start Felix, and deploy all the jars listed on this page. You should see output similar to this on the console, using the felix:lb command:

    ID|State      |Level|Name
    0|Active     |    0|System Bundle (4.4.1)
    1|Active     |    1|ASM (7.0)
    2|Active     |    1|ASM commons classes (7.0)
    3|Active     |    1|ASM Tree class visitor (7.0)
    4|Active     |    1|geronimo-jta_1.1_spec (1.1.1)
    5|Active     |    1|javax.annotation API (1.2.0)
    6|Active     |    1|javax.mail bundle from Glassfish (1.4.1.v201005082020)
    7|Active     |    1|Java Server Pages Standard Tag Library API Bundle (1.2.0.v201105211821)
    8|Active     |    1|JavaServer Pages (TM) TagLib Implementation (1.2.2)
    9|Active     |    1|Jetty :: Servlet Annotations ({VERSION})
   10|Active     |    1|Jetty :: Deployers ({VERSION})
   11|Active     |    1|Jetty :: Http Utility ({VERSION})
   12|Active     |    1|Jetty :: IO Utility ({VERSION})
   13|Active     |    1|Jetty :: JNDI Naming ({VERSION})
   14|Active     |    1|Jetty :: OSGi :: Boot ({VERSION})
   15|Resolved   |    1|Jetty-OSGi-Jasper Integration ({VERSION})
   16|Active     |    1|Jetty Servlet API and Schemas for OSGi (3.1.0)
   17|Active     |    1|Jetty :: Plus ({VERSION})
   18|Active     |    1|Jetty :: Security ({VERSION})
   19|Active     |    1|Jetty :: Server Core ({VERSION})
   20|Active     |    1|Jetty :: Servlet Handling ({VERSION})
   21|Active     |    1|Jetty :: Utility Servlets and Filters ({VERSION})
   22|Active     |    1|Jetty :: Utilities ({VERSION})
   23|Active     |    1|Jetty :: Webapp Application Support ({VERSION})
   24|Active     |    1|Jetty :: XML utilities ({VERSION})
   25|Active     |    1|Apache Aries SPI Fly Dynamic Weaving Bundle (1.2)
   27|Active     |    1|Apache Felix Bundle Repository (2.0.2)
   28|Active     |    1|Apache Felix Configuration Admin Service (1.8.0)
   29|Active     |    1|Apache Felix EventAdmin (1.3.2)
   30|Active     |    1|Apache Felix Gogo Command (0.14.0)
   31|Active     |    1|Apache Felix Gogo Runtime (0.12.1)
   32|Active     |    1|Apache Felix Gogo Shell (0.10.0)
   33|Active     |    1|Apache Felix Log Service (1.0.1)
   34|Active     |    1|Jetty :: Apache JSP ({VERSION})
   35|Active     |    1|Eclipse Compiler for Java(TM) (3.8.2.v20130121-145325)
   36|Active     |    1|Mortbay EL API and Implementation (8.5.33.1)
   37|Active     |    1|Mortbay Jasper (8.5.33.1)
Eclipse

The Jetty OSGi integration has been successfully tested against Equinox Mars RC1.

Ensure that these services are present:

Eclipse Update Site

There is a list of Eclipse P2 sites for the jetty releases maintained at http://download.eclipse.org/jetty/updates/jetty-bundles-9.x/

Each P2 repo has one big feature group that defines most of the Jetty jars. Beware: No 3rd party dependency jars are included, so you will need to have installed the dependencies listed previously in this document.

In addition, as the feature group includes websocket, you will need to download and have installed the javax.websocket-api jar:

Table 8. Extra Jars Required for Websocket
Jar Bundle Symbolic Name Location

javax.websocket-api

javax.websocket-api

Maven central

Weld

Weld can be used to add support for CDI (Contexts and Dependency Injection) to Servlets, Listeners and Filters. It is easily configured with Jetty 9.

Weld Setup

The easiest way to configure weld is within the Jetty distribution itself. This can be accomplished either by enabling one of the startup modules for Weld, or by creating/editing a jetty-web.xml descriptor (see also Jetty XML Reference).

Jetty Weld Modules
Jetty cdi-decorate Module

Since Jetty 9.4.20 and Weld 3.1.2.Final, the Weld/Jetty integration uses the jetty cdi-decorate module. To activate this module in Jetty the cdi-decorate module needs activated on the command line, which can be done as follows:

cd $JETTY_BASE
java -jar $JETTY_HOME/start.jar --add-to-start=cdi-decorate
Jetty cdi2 Module

For versions prior to Jetty 9.4.20 and Weld 3.1.2, the Weld/Jetty integration required some internal Jetty APIs to be made visible to the web application. This can be done using the deprecated cdi2 module either by activating the cdi2 module:

cd $JETTY_BASE
java -jar $JETTY_HOME/start.jar --add-to-start=cdi2
Jetty-Web XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
   <Call name="prependServerClass">
      <Arg>-org.eclipse.jetty.util.Decorator</Arg>
   </Call>
   <Call name="prependServerClass">
      <Arg>-org.eclipse.jetty.util.DecoratedObjectFactory</Arg>
   </Call>
   <Call name="prependServerClass">
      <Arg>-org.eclipse.jetty.server.handler.ContextHandler.</Arg>
   </Call>
   <Call name="prependServerClass">
      <Arg>-org.eclipse.jetty.server.handler.ContextHandler</Arg>
   </Call>
   <Call name="prependServerClass">
      <Arg>-org.eclipse.jetty.servlet.ServletContextHandler</Arg>
   </Call>
</Configure>
Directly modifying the web application classpath via jetty-web.xml will not work for Jetty 10.0.0 and later.
Jetty cdi-spi Module

Since Jetty 9.4.20 the Jetty cdi-spi module has been available that integrates any compliant CDI implementation by directly calling the CDI SPI. Since Weld support specific Jetty integration, it is not recommended to use this module with Weld.

When you start up your Jetty distribution with the webapp you should see output similar to the following (providing your logging is the default configuration):

2015-06-18 12:13:54.924:INFO::main: Logging initialized @485ms
2015-06-18 12:13:55.231:INFO:oejs.Server:main: jetty-10.0.0.beta1
2015-06-18 12:13:55.264:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:///tmp/cdi-demo/webapps/] at interval 1
2015-06-18 12:13:55.607:WARN:oeja.AnnotationConfiguration:main: ServletContainerInitializers: detected. Class hierarchy: empty
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.EnhancedListener onStartup
INFO: WELD-ENV-001008: Initialize Weld using ServletContainerInitializer
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup <clinit>
INFO: WELD-000900: 2.2.9 (Final)
Jun 18, 2015 12:13:55 PM org.jboss.weld.environment.servlet.deployment.WebAppBeanArchiveScanner scan
WARN: WELD-ENV-001004: Found both WEB-INF/beans.xml and WEB-INF/classes/META-INF/beans.xml. It's not portable to use both locations at the same time. Weld is going to use file:/tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/WEB-INF/beans.xml.
Jun 18, 2015 12:13:55 PM org.jboss.weld.bootstrap.WeldStartup startContainer
INFO: WELD-000101: Transactional services not available. Injection of @Inject UserTransaction not available. Transactional observers will be invoked synchronously.
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PostActivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:55 PM org.jboss.weld.interceptor.util.InterceptionTypeRegistry <clinit>
WARN: WELD-001700: Interceptor annotation class javax.ejb.PrePassivate not found, interception based on it is not enabled
Jun 18, 2015 12:13:56 PM org.jboss.weld.bootstrap.MissingDependenciesRegistry handleResourceLoadingException
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.WeldServletLifecycle findContainer
INFO: WELD-ENV-001002: Container detection skipped - custom container class loaded: org.jboss.weld.environment.jetty.JettyContainer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.jetty.JettyContainer initialize
INFO: WELD-ENV-001200: Jetty 7.2+ detected, CDI injection will be available in Servlets and Filters. Injection into Listeners should work on Jetty 9.1.1 and newer.
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.Listener contextInitialized
INFO: WELD-ENV-001006: org.jboss.weld.environment.servlet.EnhancedListener used for ServletContext notifications
Jun 18, 2015 12:13:56 PM org.jboss.weld.environment.servlet.EnhancedListener contextInitialized
INFO: WELD-ENV-001009: org.jboss.weld.environment.servlet.Listener used for ServletRequest and HttpSession notifications
2015-06-18 12:13:56.535:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@6574b225{/cdi-webapp,file:///tmp/jetty-0.0.0.0-8080-cdi-webapp.war-_cdi-webapp-any-8161614308407422636.dir/webapp/,AVAILABLE}{/cdi-webapp.war}
2015-06-18 12:13:56.554:INFO:oejs.ServerConnector:main: Started ServerConnector@7112f81c{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2015-06-18 12:13:56.587:INFO:oejus.SslContextFactory:main: x509={jetty.eclipse.org=jetty} wild={} alias=null for SslContextFactory@3214ee6(file:///tmp/cdi-demo/etc/keystore,file:///tmp/cdi-demo/etc/keystore)
2015-06-18 12:13:56.821:INFO:oejs.ServerConnector:main: Started ServerConnector@69176a9b{SSL,[ssl, http/1.1]}{0.0.0.0:8443}
2015-06-18 12:13:56.822:INFO:oejs.Server:main: Started @2383ms

For use with the jetty-maven-plugin, the best idea is to make the org.jboss.weld.servlet:weld-servlet artifact a plugin dependency (not a webapp dependency).

Embedded Jetty

When starting embedded Jetty programmatically from the main method it is necessary to register Weld’s listener:

public class Main {

    public static void main(String[] args) throws Exception {
        Server jetty = new Server(8080);
        WebAppContext context = new WebAppContext();
        context.setContextPath("/");
        context.setResourceBase("src/main/resources");
        jetty.setHandler(context);
        context.addServlet(HelloWorldServlet.class, "/*");

        context.addEventListener(new DecoratingListener()); (1)
        context.addEventListener(new Listener()); (2)

        jetty.start();
        jetty.join();
    }

    public static class HelloWorldServlet extends HttpServlet {

        @Inject BeanManager manager;

        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.setContentType("text/plain");
            resp.getWriter().append("Hello from " + manager);
        }
    }
}

<1> Jetty's `org.eclipse.jetty.webapp.DecoratingListener` registered programmatically (since Jetty-9.4.20)
<2> Weld's `org.jboss.weld.environment.servlet.Listener` registered programmatically

Metro

Metro is the reference implementation for web services. You can easily use Metro with Jetty to integrate web services with your web applications.

Metro Setup

  1. Download the Metro distribution and unpack it to your disk. We’ll refer to the unpacked location as $metro.home.

  2. Create the directory $jetty.home/lib/metro

  3. Copy the jars from $metro.home/lib to $jetty.home/lib/metro

  4. Edit the start.ini file and add an OPTION line for metro near the end.

    OPTIONS=metro

That’s all the setup you need to do to integrate Jetty and Metro.

Now read the Metro documentation on how to create web services. The Metro distribution you downloaded should also contain several example web applications in the $metro.home/samples directory that you can build and deploy to Jetty (simply by copying the war file produced by the build).

Here’s an example of the log output from Jetty when one of the sample Metro wars (from $metro.home/samples/async) is deployed to Jetty:

[2093] java -jar start.jar

2013-07-26 15:47:53.480:INFO:oejs.Server:main: jetty-9.0.4.v20130625
2013-07-26 15:47:53.549:INFO:oejdp.ScanningAppProvider:main: Deployment monitor [file:/home/user/jetty-distribution-10.0.0.beta1/webapps/] at interval 1
Jul 26, 2013 3:47:53 PM com.sun.xml.ws.transport.http.servlet.WSServletContextListener contextInitialized
INFO: WSSERVLET12: JAX-WS context listener initializing
Jul 26, 2013 3:47:56 PM com.sun.xml.ws.server.MonitorBase createRoot
INFO: Metro monitoring rootname successfully set to: com.sun.metro:pp=/,type=WSEndpoint,name=/metro-async-AddNumbersService-AddNumbersImplPort
Jul 26, 2013 3:47:56 PM com.sun.xml.ws.transport.http.servlet.WSServletDelegate <init>
INFO: WSSERVLET14: JAX-WS servlet initializing
2013-07-26 15:47:56.800:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@75707c77{/metro-async,file:/tmp/jetty-0.0.0.0-8080-metro-async.war-_metro-async-any-/webapp/,AVAILABLE}{/metro-async.war}
2013-07-26 15:47:56.853:INFO:oejs.ServerConnector:main: Started ServerConnector@47dce809{HTTP/1.1}{0.0.0.0:8080}

Architecture

General items related to the architecture of jetty and how it deals with certain design decisions.

Jetty Architecture

View from 20,000 feet

The Jetty Server is the plumbing between a collection of `Connector`s that accept connections and a collection of `Handler`s that service requests from the connections and produce responses, with threads from a thread pool doing the work.

image

While the Jetty request/responses are derived from the Servlet API, the full features of the Servlet API are only available if you configure the appropriate handlers. For example, the session API on the request is inactive unless the request has been passed to a SessionHandler. The concept of a Servlet itself is implemented by a ServletHandler. If Servlets are not required, there is very little overhead in the use of the servlet request/response APIs. Thus you can build a Jetty server using only connectors and handlers, without using Servlets.

The job of configuring Jetty is building a tree of connectors and handlers and providing their individual configurations. As Jetty components are simply Plain Old Java Objects (POJOs), you can accomplish this assembly and configuration of components by a variety of techniques:

  • In code, see the examples in the Jetty Source XRef.

  • Using Jetty XML, a dependency injection style in XML format.

  • With your dependency injection framework of choice, Spring or XBean.

  • Using Jetty WebApp and Context Deployers.

Patterns

The implementation of Jetty follows some fairly standard patterns. Most abstract concepts such as Connector`s and `Handler`s are captured by interfaces. Generic handling for those interfaces is then provided in an abstract implementation such as `AbstractConnector and AbstractHandler.

image

The JSR77 inspired life cycle of most Jetty components is represented by the LifeCycle interface and the AbstractLifeCycle implementation used as the base of many Jetty components.

Connectors

A Connector is the component that accepts TCP connections. For each accepted TCP connection, the Connector asks a ConnectionFactory to create a Connection object that handles the network traffic on that TCP connection, parsing and generating bytes for a specific protocol.

A ServerConnector can therefore be configured with one or more ConnectionFactory.

The simplest case is a single ConnectionFactory such as HttpConnectionFactory, that creates HttpConnection objects that parse and generate bytes for the HTTP/1.1 protocol.

A more complex case can be a ServerConnector configured with three factories: ProxyConnectionFactory, SslConnectionFactory and HttpConnectionFactory. Such connector will be able to handle PROXY protocol bytes coming from a load balancer such as HAProxy (with the ProxyConnectionFactory), then handle TLS bytes (with SslConnectionFactory) and therefore decrypting/encrypting the bytes from/to a remote client, and finally handling HTTP/1.1 bytes (with HttpConnectionFactory). Each ConnectionFactory is asked to create a Connection object for each 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, SslConnection will handle the encryption/decryption of the bytes, and HttpConnection will handle the HTTP/1.1 bytes producing a request and response object that will be processed by applications.

Advanced usages of Jetty will allow users to write their own ConnectionFactory to handle custom protocols that are not implemented directly by the Jetty project, therefore using Jetty as a generic network server.

Handlers

A Handler is the component that deals with HTTP requests and responses. The core API of a handler is the handle method:

image

public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException

Parameters:

  • target – the target of the request, either a URI or a name.

  • baseRequest – the original unwrapped request object.

  • request – the request object, either as the baseRequest object or a wrapper of baseRequest. You can use the HttpConnection.getCurrentConnection() method to access the Request object if required.

  • response – the response object, either unwrapped as Response or a wrapper of that response. You can use the HttpConnection.getCurrentConnection() method to access the Response object if required.

An implementation of this method can handle the request, pass the request onto another handler (or servlet) or it might modify and/or wrap the request and then pass it on. This gives three styles of Handler:

  • Coordinating handlers – handlers that route requests to other handlers (HandlerCollection, ContextHandlerCollection)

  • Filtering handlers – handlers that augment a request and pass it on to other handlers (HandlerWrapper, ContextHandler, SessionHandler)

  • Generating handlers – handlers that produce content (ResourceHandler and ServletHandler)

Nested Handlers and Handlers Called Sequentially

You can combine handlers to handle different aspects of a request by nesting them, calling them in sequence, or by combining the two models.

image

Handlers called in sequence perform actions that do not depend on the next invocation, nor on the handler order. They handle a request and generate the response without interacting with other handlers. The main class for this model is HandlerCollection.

Nested handlers are called according to a before/invokeNext/after pattern. The main class for nested handlers is HandlerWrapper. Nested handlers are much more common than those called in sequence.

Servlet Handler

The ServletHandler is a Handler that generates content by passing the request to any configured Servlet Filters and then to a Servlet mapped by a URI pattern.

image

A ServletHandler is normally deployed within the scope of a ServletContext, which is a ContextHandler that provides convenience methods for mapping URIs to servlets.

Filters and Servlets can also use a RequestDispatcher to reroute a request to another context or another Servlet in the current context.

Contexts

Contexts are handlers that group other handlers below a particular URI context path or a virtual host. Typically a context can have:

  • A context path that defines which requests are handled by the context (e.g. /myapp)

  • A resource base for static content (a document root)

  • A class loader to obtain classes specific to the context (typically from /WEB-INF/classes and /WEB-INF/lib)

  • Virtual host names

Contexts implementations include:

  • ContextHandler

  • ServletContextHandler

  • WebAppContext

A web application context combines handlers for security, session and servlets in a single unit that you can configure with a web.xml descriptor.

Web Application

A WebAppContext is a derivation of ServletContextHandler that supports the standardized layout of a web application and configuration of session, security, listeners, filter, servlets, and JSP via a web.xml descriptor normally found in the /WEB-INF directory of a web application.

image

Essentially WebAppContext is a convenience class that assists the construction and configuration of other handlers to achieve a standard web application configuration. Configuration is actually done by pluggable implementations of the Configuration class and the prime among these is WebXmlConfiguration.

Jetty Classloading

Class loading in a web container is slightly more complex than a normal Java application. The normal configuration is that each web context (web application or WAR file) has its own classloader, which has the system classloader as its parent. Such a classloader hierarchy is normal in Java, however the servlet specification complicates the hierarchy because it requires the following:

  • Classes contained within WEB-INF/lib or WEB-INF/classes have priority over classes on the parent classloader. This is the opposite of the normal behavior of a Java 2 classloader.

  • System classes such as java.lang.String are excluded from the webapp priority, and you may not replace them with classes in WEB-INF/lib or WEB-INF/ classes. Unfortunately the specification does not clearly state what classes are System classes, and it is unclear if all javax classes should be treated as System classes.

  • Server implementation classes like Server should be hidden from the web application and should not be available in any classloader. Unfortunately the specification does not state what classes are Server classes, and it is unclear if common libraries like the Xerces parser should be treated as Implementation classes.

Configuring Webapp Classloading

Jetty provides configuration options to control the three webapp class loading issues identified above.

You can configure webapp classloading by several methods on the WebAppContext. You can call these methods directly if you are working with the Jetty API, or you can inject methods from a context XML file if you are using the Context Provider ([using-context-provider]). You CANNOT set these methods from a jetty-web.xml file, as it executes after the classloader configuration is set. As a note, jetty-web.xml uses the webapp classpath and not the classpath of the server.

Controlling Webapp Classloader Priority

The method org.eclipse.jett .webapp.WebAppContext.setParentLoaderPriority(boolean) allows control over the priority given to webapp classes over system classes. If you set it to false (the default), Jetty uses standard webapp classloading priority. However, if in this mode some classes that are dependencies of other classes are loaded from the parent classloader (due to settings of system classes below), ambiguities might arise as both the webapp and system classloader versions can end up being loaded.

If set to true, Jetty uses normal JavaSE classloading priority, and gives priority to the parent/system classloader. This avoids the issues of multiple versions of a class within a webapp, but the version the parent/system loader provides must be the right version for all webapps you configure in this way.

Configuring Webapp Classloader Caching

Introduced in Jetty 9.3.6, the CachingWebAppClassLoader can be used to cache getResource(String) results. For webapps that search for classes and resources regularly, this can increase speed and performance. This is an optional feature and it should be noted that it can conflict with several other libraries such as JSP, JSTL, JSF and CDI. As such, this feature must be manually enabled for each webapp you want to use it in.

Below is an example of implementing this feature using Jetty IoC XML format:

<Configure id="mywebapp" class="org.eclipse.jetty.webapp.WebAppContext">

...

  <Set name="classLoader">
    <New class="org.eclipse.jetty.webapp.CachingWebAppClassLoader">
      <Arg><Ref refid="mywebapp"/></Arg>
    </New>
  </Set>

...
</Configure>
Setting System Classes

You can call the methods WebAppContext.setSystemClasses(String[]) or WebAppContext.getSystemClasspathPattern().add(String) to allow fine control over which classes are considered system classes.

  • A web application can see a System class.

  • A WEB-INF class cannot replace a System class.

The default system classes are:

Table 9. Default System Classes
System Classes Note

java.

Java SE classes (per servlet spec v2.5 / SRV.9.7.2).

javax.

Java SE classes (per servlet spec v2.5 / SRV.9.7.2).

org.xml.

Needed by javax.xml.

org.w3c.

Needed by javax.xml.

org.eclipse.jetty.continuation.

Webapp can see and not change continuation classes.

org.eclipse.jetty.jndi.

Webapp can see and not change naming classes.

org.eclipse.jetty.jaas.

Webapp can see and not change JAAS classes.

org.eclipse.jetty.websocket.

WebSocket is a Jetty extension.

org.eclipse.jetty.servlet.DefaultServlet

Webapp can see and not change default servlet.

Absolute classname can be passed, names ending with . are treated as packages names, and names starting with - are treated as negative matches and must be listed before any enclosing packages.

Setting Server Classes

You can call the methods org.eclipse.jetty.webapp.WebAppContext.setServerClasses(String Array) or org.eclipse.jetty.webapp.WebAppContext.addServerClass(String) to allow fine control over which classes are considered Server classes.

  • A web application cannot see a Server class.

  • A WEB-INF class can replace a Server class.

The default server classes are:

Table 10. Default Server Classes
Server Classes -org.eclipse.jetty.continuation.

Don’t hide continuation classes.

-org.eclipse.jetty.jndi.

Don’t hide naming classes.

-org.eclipse.jetty.jaas.

Don’t hide jaas classes.

-org.eclipse.jetty.servlets.

Don’t hide utility servlet classes if provided.

-org.eclipse.jetty.servlet.DefaultServlet

Don’t hide default servlet.

-org.eclipse.jetty.servlet.listener.

Don’t hide utility listeners

-org.eclipse.jetty.websocket.

Don’t hide websocket extension.

org.eclipse.jetty.

Adding Extra Classpaths to Jetty

You can add extra classpaths to Jetty in several ways.

Using start.jar

If you are using start.jar via the Jetty distribution, at startup the Jetty runtime automatically loads option Jars from the top level $jetty.home/lib directory. The default settings include:

  • Adding Jars under $jetty.home/lib/ext to the system classpath. You can place additional Jars here.

  • Adding the directory $jetty.home/resources to the classpath (may contain classes or other resources).

  • Adding a single path defined by the command line parameter path.

Using the extraClasspath() method

You can add an additional classpath to a context classloader by calling org.eclipse.jetty.webapp.WebAppContext.setExtraClasspath(String) with a comma-separated list of paths. You can do so directly to the API via a context XML file such as the following:

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
 ...
 <Set name="extraClasspath">../my/classes,../my/jars/special.jar,../my/jars/other.jar</Set>
 ...

Using a Custom WebAppClassLoader

If none of the alternatives already described meet your needs, you can always provide a custom classloader for your webapp. We recommend, but do not require, that your custom loader subclasses WebAppClassLoader.

If you do not subclass WebAppClassLoader, we recommend that you implement the ClassVisibilityChecker interface. Without this interface, session persistence will be slower.

You configure the classloader for the webapp like so:

MyCleverClassLoader myCleverClassLoader = new MyCleverClassLoader();
 ...
   WebAppContext webapp = new WebAppContext();
 ...
   webapp.setClassLoader(myCleverClassLoader);

You can also accomplish this in a context xml file.

Starting Jetty with a Custom ClassLoader

If you start a Jetty server using a custom class loader–consider the Jetty classes not being available to the system class loader, only your custom class loader–you may run into class loading issues when the WebAppClassLoader kicks in. By default the WebAppClassLoader uses the system class loader as its parent, hence the problem. This is easy to fix, like so:

context.setClassLoader(new WebAppClassLoader(this.getClass().getClassLoader(), context));

or

context.setClassLoader(new WebAppClassLoader(new MyCustomClassLoader(), context));

Creating a Custom Protocol

You can create custom protocols with Jetty. This page provides an example of how to do so, with Telnet as the protocol.

To create a custom Telnet protocol, complete the following tasks:

  • Implement a TelnetServerConnectionFactory.

  • Implement a TelnetServerConnection by extending o.e.j.io.AbstractConnection.

  • Create a parser/interpreter for the bytes you receive (this is totally independent from Jetty).

  • If needed, design an API for the application to use to process the bytes received (also independent from Jetty). The API likely has a respond back primitive that uses a Jetty provided EndPoint and EndPoint.write(Callback, Buffer…​) to write the response bytes.

Implementing a TelnetServerConnectionFactory

Begin with an org.eclipse.jetty.server.ServerConnector, which you can use as is. ServerConnector takes a o.e.j.server.ConnectionFactory, which creates o.e.j.io.Connection objects that interpret the bytes the connector receives. You must implement ConnectionFactory with a TelnetServerConnectionFactory, where you return a Connection implementation (for example, TelnetServerConnection).

Implementing the TelnetServerConnection

For the Connection implementation you need to extend from o.e.j.io.AbstractConnection because it provides many facilities that you would otherwise need to re-implement from scratch.

For each Connection instance there is associated an o.e.j.io.EndPoint instance. Think of EndPoint as a specialized version of JDK’s SocketChannel. You use the EndPoint to read, write, and close. You don’t need to implement EndPoint, because Jetty provides concrete classes for you to use.

The Connection is the passive side (that is, Jetty calls it when there is data to read), while the EndPoint is the active part (that is, applications call it to write data to the other end). When there is data to read, Jetty calls AbstractConnection.onFillable(), which you must implement in your TelnetServerConnection.

A typical implementation reads bytes from the EndPoint by calling EndPoint.fill(ByteBuffer). For examples, look at both the simpler SPDYConnection (in the SPDY client package, but server also uses it), and the slightly more complex HttpConnection.

Parsing the Bytes Received

After you read the bytes, you need to parse them. For the Telnet protocol there is not much to parse, but perhaps you have your own commands that you want to interpret and execute. Therefore typically every connection has an associated parser instance. In turn, a parser usually emits parse events that a parser listener interprets, as the following examples illustrate:

  • In HTTP, the Jetty HTTP parser parses the request line (and emits a parser event), then parses the headers (and emits a parser event for each) until it recognizes the end of the headers (and emits another parser event). At that point, the interpreter or parser listener (which for HTTP is o.e.j.server.HttpChannel) has all the information necessary to build a HttpServletRequest object and can call the user code (the web application, that is, servlets/filters).

  • In SPDY, the Jetty SPDY parser parses a SPDY frame (and emits a parser event), and the parser listener (an instance of o.e.j.spdy.StandardSession) interprets the parser events and calls user code (application-provided listeners).

With ConnectionFactory, Connection, parser, and parser listeners in place, you have configured the read side.

Designing an API to Process Bytes

At this point, server applications typically write data back to the client.

The Servlet API (for HTTP) or application-provided listeners (for SPDY) expose an interface to web applications so that they can write data back to the client. The implementation of those interfaces must link back to the EndPoint instance associated with the Connection instance so that it can write data via EndPoint.write(Callback, ByteBuffer…​). This is an asynchronous call, and it notifies the callback when all the buffers have been fully written.

For example, in the Servlet API, applications use a ServletOutputStream to write the response content. ServletOutputStream is an abstract class that Jetty implements, enabling Jetty to handle the writes from the web application; the writes eventually end up in an EndPoint.write(…​) call.

Tips for Designing an API

If you want to write a completely asynchronous implementation, your API to write data to the client must have a callback/promise concept: “Call me back when you are done, and (possibly) give me the result of the computation."

SPDY’s Stream class is a typical example. Notice how the methods there exist in two versions, a synchronous (blocking) one, and an asynchronous one that takes as last parameter a Callback (if no result is needed), or a Promise (if a result is needed). It is trivial to write the synchronous version in terms of the asynchronous version.

You can use EndPoint.write(Callback, ByteBuffer…​) in a blocking way as follows:

FutureCallback callback = new FutureCallback();
endPoint.write(callback, buffers);
callback.get();

With the snippet above your API can be synchronous or asynchronous (your choice), but implemented synchronously.

Platforms, Stacks and Alternative Distributions

Many many options…​

In addition to using Jetty in its distribution form and its multiple embedded forms, there are a number of alternative ways to use Jetty. Many products and open source projects out there distribute Jetty themselves, in both distribution and embedded forms, not to mention different operating systems bundling Jetty in other installable forms.

If your platform supports Jetty from a distribution or deployment perspective and want to be included on this list just fork the documentation and submit a pull request, or contact us. Check out our list of Powered By page for software that makes use of Jetty, often in novel and exciting ways.

Jelastic

Jelastic is a wonderful place to host your applications with solid support for Jetty. As a cloud hosting platform they take the majority of configuration and installation details out of the picture and focus on letting you focus on your web application.

CloudFoundry

This is an increasingly aged integration, things like likely changed enough this is not directly useful but may serve as a useful starting point should someone want to look into it.

Overview

CloudFoundry is an open platform intended as a place to deploy end user applications in a manner which is both simple and eminently scalable to fit the needs of the application. With the release of their V2 framework the Jetty project has created a buildpack which allows you to deploy your java based web application onto Jetty and still make use of the remainder of the CloudFoundry platform.

This buildpack itself is quite simple to use. A collection of ruby scripting and the buildpack conventions will allow Jetty to be downloaded, configured and customized to your needs and then have your web application deployed onto it. While the default buildpack we have created is useful to deploy a stock configuration of jetty, it is quite likely that you will want to fork the buildpack and tweak it to fit your immediate needs. This process is made trivial since buildpacks install from a github repository. For example, to change the jetty version simply fork it in GitHub and tweak the JETTY_VERSION string in the jetty_web.rb file.

If you have additional modifications to make to the Jetty server, like perhaps configuring additional static contexts, setting up a proxy servlet, adding jar files to the jetty home/lib/ext directory, etc you can either adapt the ruby scripting directly or place them under the appropriate location in the /resources directory of this buildpack and they will be copied into the correct location.

For the time being I’ll leave this buildpack under my personal github account and should there be interest expressed I am more then happy to push it over to https://github.com/jetty-project down the road for proper contributions, etc.

Usage

To show how incredibly easy it is to use the Jetty buildpack with cloudfoundry, this is all the more you need to do to deploy your application. Refer to the CloudFoundry documentation to get started, get the cf utilities installed and an environment configured.

$ cf push snifftest --buildpack=git://github.com/jmcc0nn3ll/jetty-buildpack.git
In this example the web application is uploaded from the current directory so make sure you have changed directory into the root of your web application. The snifftest on the commandline refers to what you are calling the application, not the directory to deploy. Also note that the webapplication is installed into the ROOT context of Jetty as is available at the root context of the server. Any additional web applications will have to be configured within the buildpack as mentioned above.

You will be prompted to answer a series of questions describing the execution environment and any additional services you need enabled (databases, etc).

Instances> 1

Custom startup command> none

1: 64M
2: 128M
3: 256M
4: 512M
5: 1G
Memory Limit> 256M

Creating snifftest... OK

1: snifftest
2: none
Subdomain> snifftest

1: a1-app.cf-app.com
2: none
Domain> a1-app.cf-app.com

Binding snifftest.a1-app.cf-app.com to snifftest... OK

Create services for application?> n

Save configuration?> n

Once answered you will see the installation process of your application.

Uploading snifftest... OK
Starting snifftest... OK
-> Downloaded app package (4.0K)
Initialized empty Git repository in /tmp/buildpacks/jetty-buildpack.git/.git/
Installing jetty-buildpack.git.
Downloading JDK...
Copying openjdk-1.7.0_21.tar.gz from the buildpack cache ...
Unpacking JDK to .jdk
Downloading Jetty: jetty-distribution-10.0.0.beta1.tar.gz
Downloading jetty-distribution-10.0.0.beta1.tar.gz from http://repo2.maven.org/maven2/org/eclipse/jetty/jetty-distribution/9.0.3.v20130506/ ...
Unpacking Jetty to .jetty
-> Uploading staged droplet (36M)
-> Uploaded droplet
Checking snifftest...
Staging in progress...
Staging in progress...
Staging in progress...
Staging in progress...
Staging in progress...
Staging in progress...
  0/1 instances: 1 starting
  0/1 instances: 1 starting
  0/1 instances: 1 starting
  0/1 instances: 1 starting
  1/1 instances: 1 running
OK

The application is now available at the configured location! Under the url http://snifftest.a1-app.cf-app.com/ in this particular example.

Acknowledgements

The Jetty buildpack was forked from the CloudFoundry Java buildpack. The Virgo Buildpack that Glyn worked on was used as a sanity check.

CloudFoundry buildpacks were modelled on Heroku buildpacks.

Amazon Elastic Beanstalk

This is an increasingly aged integration, things like likely changed enough this is not directly useful but may serve as a useful starting point should someone want to look into it.

Elastic Beanstalk is a component with the Amazon Web Services offering that allows you to configure an entire virtual machine based on one of several available baseline configurations and then customize it through a powerful configuration system. While the default offerings currently available are based on Tomcat for for the java community, we worked out the basics using that configuration system to enable the usage of Jetty instead.

Overview

Elastic beanstalk has a very powerful configuration mechanism so this integration taps into that to effectively rework the tomcat configuration and replace it with the bits required to make jetty run in its place. Below is a walk through of what the various configuration files are doing and how the general flow of configuration on beanstalk happens.

There is an .ebextensions directory in your beanstalk application which contains all of the files requires to configure and customize your beanstalk and application combo. Files that end in .config in this directory are processed in alphabetical order.

00-java7.config

installs java 7 onto the beanstalk environment and makes it the default

10-tweak.config

not required, but changes the /opt/elasticbeanstalk directory to be readable making debugging easier

11-jetty.config

installs jetty9 into /opt/jetty-9 and removes unneeded distribution files

12-beanstalk.config

handles replacing tomcat with jetty in many configuration files, configures logging and wires up system startup processes. Some files in your .ebextensions directory are moved to replace files under /opt/elasticbeanstalk.

If you look in the .ebextensions directory of your application you should also see other jetty specific xml and ini files. The final config file handles these as they are largely customization for your application.

20-testapp.config

layers application specific configuration files into the jetty installation

The files in our example test webapp here enable various OPTIONS for libraries that need to be loaded, customize the root application being deployed and even deploy additional contexts like we do in our jetty distribution demo. This is also the mechanism that you would use to wire up application specific things, for example if you needed additional software installed, customized directories made, etc.

Maven Bits

Support for this feature leverages Maven to make things easy and is composed of three different modules.

jetty-beanstalk-overlay

This is the collection of scripts that are required to wedge jetty into the normal beanstalk setup. This module is intended to extract into an webapp to enable it for beanstalk usage with jetty.

jetty-beanstalk-resources

This generates an artifact of files that are downloaded by the configuration process and contains replacements for certain beanstalk files as well as various system level jetty configuration files like an updated jetty.sh script for the /etc/init.d setup.

jetty-beanstalk-testapp

An example webapp that shows both how to combine the war file from another maven module with the jetty-beanstalk-overlay to produce a beanstalk enabled application bundle. Also included is examples of how to alter the jetty configuration for things like a customized start.ini file.

The test webapps needs access to a snapshot version of the test-jetty-webapp so it really serves as more of an example of how to layer your webapp with the bits required to customize your app for beanstalk and jetty.

To actually make use of these artifacts you currently must clone this git repository and build it locally. Once you have the artifacts you simply need to copy the approach in the jetty-beanstalk-testapp to apply the configuration to your webapp.

Bluepill is used to manage the start and stop process of the app server. This seems to be a problematic bit of software with a colored history and the version in use at the time of this writing is old. When starting and stopping (or restarting) the appserver you may see error messages show up that the Server timed out getting a response or things like that. These are red herrings and my experience is that jetty has started and stopped just fine, the pid file required shows up in a very timely fashion (under /var/run/jetty.pid) so do check that the app server has started, but please be aware there is a strangeness here that hasn’t been sorted out yet.

Fedora

As of Fedora 19, Jetty 9 is the version of Jetty available. This distribution of Jetty is not created or maintained by the Jetty project though we have had a fair amount of communication with the folks behind it and we are very pleased with how this Linux distribution has stayed current. Releases are kept largely in sync with our releases as there is a wonderful automatic notification mechanism in place for Fedora that detects our releases and immediately opens an issue for them to update.

Ubuntu

Currently there are no actual .deb files available for installing on Debian based Linux machines but there is a handy blog that as been largely been kept up to date on the steps involved through the comments.

Jetty XML Reference

Jetty XML Syntax

The Jetty XML syntax is a straightforward mapping of XML elements to a Java API so that POJOs can be instantiated and getters, setters, and methods called. It is very similar to Inversion Of Control (IOC) or Dependency Injection (DI) frameworks like Spring or Plexus (but it predates all of them). Typically Jetty XML is used by jetty.xml to configure a Jetty server or by a context.xml file to configure a ContextHandler or subclass, but you can also use the mechanism to configure arbitrary POJOs.

This page describes the basic syntax of Jetty XML configuration. See Jetty XML Usage for information on how you can use and combine Jetty XML. See configuration files for specific examples.

Basic XML Configuration File Example

The following XML configuration file creates some Java objects and sets some attributes:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="foo" class="com.acme.Foo">
  <Set name="name">demo</Set>
  <Set name="nested">
    <New id="bar" class="com.acme.Bar">
      <Arg>true</Arg>
      <Set name="wibble">10</Set>
      <Set name="wobble">xyz</Set>
      <Set name="parent"><Ref refid="foo"/></Set>
      <Call name="init">
         <Arg>false</Arg>
      </Call>
    </New>
  </Set>

  <Ref refid="bar">
    <Set name="wibble">20</Set>
    <Get name="parent">
      <Set name="name">demo2</Set>
    </Get>
  </Ref>
</Configure>

The XML above is equivalent to the following Java code:

com.acme.Foo foo = new com.acme.Foo();
foo.setName("demo");

com.acme.Bar bar = new com.acme.Bar(true);
bar.setWibble(10);
bar.setWobble("xyz");
bar.setParent(foo);
bar.init(false);

foo.setNested(bar);

bar.setWibble(20);
bar.getParent().setName("demo2");

Overview

Understanding DTD and Parsing

The document type descriptor (configure.dtd) describes all valid elements in a Jetty XML configuration file using the Jetty IoC format. The first two lines of an XML must reference the DTD to be used to validate the XML like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">
...

Typcically a good XML editor will fetch the DTD from the URL and use it to give syntax highlighting and validation while a configuration file is being edited. Some editors also allows DTD files to be locally cached. The URL may point to configure.dtd if you want the latest current version, or to a specific version like configure_9_3.dtd if you want a particular validation feature set.

Files that conform to the configure.dtd format are processed in Jetty by the XmlConfiguration class which may also validate the XML (using a version of the DTD from the classes jar file), but is by default run in a forgiving mode that tries to work around validation failures.

To ensure your web.xml, web-fragment.xml and webdefault.xml files are validated, you will also need to set the validateXml attribute to true:

<Call name="setAttribute">
     <Arg>org.eclipse.jetty.webapp.validateXml</Arg>
     <Arg type="Boolean">true</Arg>
</Call>
Jetty XML Configuration Scope

The configuration of object instances with Jetty IoC XML is done on a scoped basis, so that for any given XML element there is a corresponding Object in scope and the nested XML elements apply to that. The outer most scope is given by a Configure element and elements like Call, New and Get establish new scopes. The following example uses the name fields to explain the scope.

<Configure class="com.example.Foo">
  <Set name="fieldOnFoo">value</Set>
  <Set name="fieldOnFoo">
    <New class="com.example.Bar">
      <Set name=fieldOnBar>value</Set>
      <Call name="methodOnBarWithNoArgs"/>
    </New>
  </Set>

  <Call name="methodOnFoo">
    <Arg>value for first arg of methodOnFoo</Arg>
    <Arg><New class="com.example.Bar"/></Arg>
    <Set name="fieldOnObjectReturnedByMethodOnFoo">value</Set>
    <Call name="methodOnObjectReturnedByMethodOnFooWithNoArgs"/>
  </Call>
</Configure>
Coercing Arguments to a Type

When trying to match XML elements to java elements, Jetty XmlConfiguration may need to coerces values to match method arguments. By default it does so on a best effort basis, but you can also specify explicit types with the type attribute. Supported values for type are: String, Character, Short, Byte, Integer, Long, Boolean, Float, Double, char, short, byte, int, long, boolean, float, double, URL, InetAddress, InetAddrPort, and void.

Referring to a Class

If you do not specify the classname, Jetty assumes you are calling the method on the object that is current in scope (eg the object of the surrounding Configure, New or Get clause). If the class attribute is specified to a fully-qualified class name, then it is either used to create a new instance (Configure and New elements) or is used to access a static (Call, Set or Get elements).

Referring to an Object

You can use the id attribute to store a reference to the current object when first creating or referring to this object. You can then use the Ref element to reference the object later. The ID must be unique for each object you create.

Attribute vs Element Style

For XML elements that contain only other XML Elements, there is a choice of using attributes or elements style. The following is an example of attribute style:

  <Call id="result" class="org.example.SomeClass" name="someMethod" arg="value0,value1"/>

Attribute style has the benefit of brevity, but is limited by: values can only be Strings; multivalued items can not contain ','; values may not be subject to property expansion or other elements that return values. Thus, the more verbose element style is available and the following is semantically equivalent to the attribute style above:

  <Call>
    <Id>result</Id>
    <Class>org.example.SomeClass</Class>
    <Name>someMethod</Name>
    <Arg>value0</Arg>
    <Arg>value1</Arg>
  </Call>

Note that multivalued elements like Arg must be repeated and may not be comma-separated like they are when provided as attributes. It is possible to use a mix of styles and the following example shows a moretypical example that uses property expansion as the reason for element style:

  <Call id="result" name="someMethod">
    <Class><Property name="which.class">
      <Default><Property name="default.class" default="org.example.SomeClass"/>
    </Property></Class>
    <Arg>value0</Arg>
    <Arg>value1</Arg>
  </Call>

Attributes may not be expressed as elements when their parent element is one that contains data. Thus Arg, Item, Set, Put and Get elements may not have their attributes expressed as elements.

<Configure>

This is the root element that specifies the class of object that is to be configured. It is usually either the Server, in jetty.xml, or a WebAppContext in jetty-web.xml.

Attribute Required Description

id

no

A reference to the object that was created. If you define multiple Configure elements with the same id, they will be treated as one object, even if they’re in different files. You can use this to break up configuration of an object (such as the Server) across multiple files.

class

no

The fully qualified classname of the object to be configured. Could be org.eclipse.jetty.server.Server, org.eclipse.jetty.webapp.WebAppContext, a handler, etc.

Examples
Basic Example
<Configure class="org.eclipse.jetty.server.Server">
  <Set name="port">8080</Set>
</Configure>

This is equivalent to:

org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server();
server.setPort(8080);
Using id to break up configuration of one object across multiple files

In etc/jetty.xml:

<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <!-- basic configuration here -->
</Configure>

In etc/jetty-logging.xml:

<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <!-- assumes that you have the basic server configuration set up; this file only contains additional configuration for logging -->
</Configure>

Then run the combined configuration using:

java -jar start.jar etc/jetty.xml jetty-logging.xml

<Set>

A Set element maps to a call to a setter method or field on the current object. It can contain text and/or elements such as Call, New, SystemProperty, etc., as values. The name and optional type attributes are used to select the setter method. If you do not specify a value type, white space is trimmed out of the value. If it contains multiple elements as values, they are added as strings before being converted to any specified type.

Attribute Required Description

name

yes

the name of the setter method to call, or the field to set. If the name given is xxx, then a setXxx method is used. If the setXxx method cannot be found, then the xxx field is used.

type

no

the declared type of the argument. See also discussion of type for Arg for how to define null and empty string values.

class

no

if present, then this Set is treated as a static set method invocation

Examples
Basic Example
<Configure id="server" class="org.eclipse.jetty.server.Server">
  <Set name="port">8080</Set>
</Configure>
Set via a System Property
<Configure id="server" class="org.eclipse.jetty.server.Server">
  <Set name="port"><SystemProperty name="jetty.http.port" /></Set>
</Configure>
Creating a NewObject and Setting It on the Server
<Configure id="server" class="org.eclipse.jetty.server.Server">
  <Set name="threadPool">
    <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
      <Set name="minThreads">10</Set>
      <Set name="maxThreads">1000</Set>
    </New>
  </Set>
</Configure>

This is equivalent to:

org.eclipse.jetty.server.Server server = new org.eclipse.jetty.server.Server();

org.eclipse.jetty.util.thread.QueuedThreadPool threadPool = new org.eclipse.jetty.util.thread.QueuedThreadPool();
threadPool.setMinThreads(10);
threadPool.setMaxThreads(1000);

server.setThreadPool(threadPool);
Invoking a Static Setter
<Configure id="server" class="org.eclipse.jetty.server.Server">
  <Set class="org.eclipse.jetty.util.log.Log" name="logToParent">loggerName</Set>
</Configure>

<Get>

A Get element maps to a call to a getter method or field on the current object. It can contain nested elements such as Set, Put, Call, etc.; these act on the object returned by the Get call.

Attribute Required Description

name

yes

the name of the getter method to call, or the field to get. If the name given is xxx, then a getXxx method is used. If the getXxx method cannot be found, then the xxx field is used.

class

no

f present, then this Get is treated as a static getter or field.

id

no

if present, then you can use this id to refer to the returned object later.

Examples
Basic Example

This simple example doesn’t do much on its own. You would normally use this in conjunction with a <Ref id="Logger" />.

<Configure id="server" class="org.eclipse.jetty.server.Server">
  <Get id="Logger" class="org.eclipse.jetty.util.log.Log" name="log"/>
</Configure>
Invoking a Static Getter and Call Methods on the Returned Object
<Configure id="server" class="org.eclipse.jetty.server.Server">
    <Get class="java.lang.System" name="out">
      <Call name="println">
        <Arg>Server version is: <Get class="org.eclipse.jetty.server.Server" name="version"/></Arg>
      </Call>
    </Get>
</Configure>

<Put>

A Put element maps to a call to a put method on the current object, which must implement the Map interface. It can contain text and/or elements such as Call, New, SystemProperty, etc. as values. If you do not specify a no value type, white space is trimmed out of the value. If it contains multiple elements as values, they are added as strings before being converted to any specified type.

Attribute Required Description

name

yes

used as the put key

type

no

forces the type of the value. See also discussion of type for Arg for how to define null and empty string values.

Example
<Get name="someKindOfMap">
   <Put name="keyName">objectValue</Put>
</Get>

<Call>

A Call element maps to an arbitrary call to a method on the current object. It can contain a sequence of Arg elements followed by a sequence of configuration elements, such as Set, Put, Call. The <Arg>s are passed as arguments to the method; the sequence of configuration elements act on the object returned by the original call.

Attribute Required Description

name

yes

the name of the arbitrary method to call. The method called will use the exact name you provide it.

class

no

if present, then this Call is treated as a static method.

id

no

if present, you can use this id to refer to any object returned by the call, for later use.

arg

no

comma separated list of arguments may be used for simple string values rather than Arg elements

Examples
Basic example
<Call name="doFoo">
  <Arg>bar</Arg>
  <Set name="test">1, 2, 3</Set>
</Call>

This is equivalent to:

Object o2 = o1.doFoo("bar");
o2.setTest("1, 2, 3");
Invoking a static method
<Call class="com.acme.Foo" name="setString">
  <Arg>somestring</Arg>
</Call>

Which is equivalent to:

com.acme.Foo.setString("somestring");
Invoking the Actual MethodInstead of Relying on Getter/Setter Magic
<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <Call name="getPort" id="port" />
  <Call class="com.acme.Environment" name="setPort">
    <Arg>
      <Ref refid="port"/>
    </Arg>
  </Call>
</Configure>

Which is equivalent to:

org.mortbay.jetty.Server server = new org.mortbay.jetty.Server();
com.acme.Environment.setPort( server.getPort() );

<Arg>

An Arg element can be an argument of either a method or a constructor. Use it within [jetty-syntax-call] and [jetty-syntax-new].

It can contain text and/or elements, such as Call, New, SystemProperty, etc., as values. The optional type attribute can force the type of the value. If you don’t specify a type, white space is trimmed out of the value. If it contains multiple elements as values, they are added as strings before being converted to any specified type. Simple String arguments can also be specified as a string separated arg attribute on the parent element.

Attribute Required Description

type

no

force the type of the argument. If you do not provide a value for the element, if you use type of "String", the value will be the empty string (""), otherwise it is null.

Examples
Basic examples
<Arg>foo</Arg> <!-- String -->
<Arg>true</Arg> <!-- Boolean -->
<Arg>1</Arg> <!-- int, long, short, float, double -->
<Arg><Ref refid="foo" /></Arg>  <!-- any object; reference a previously created object with id "foo", and pass it as a parameter -->
<Arg></Arg> <!-- null value -->
<Arg type="String"></Arg> <!-- empty string "" -->
Coercing Type

This explicitly coerces the type to a boolean:

<Arg type="boolean">False</Arg>
As a Parameter

Here are a couple of examples of Arg element being used as a parameter to methods and to constructors:

<Call class="com.acme.Environment" name="setFoo">
  <Arg>
    <New class="com.acme.Foo">
      <Arg>bar</Arg>
    </New>
  </Arg>
</Call>

This is equivalent to:

com.acme.Environment.setFoo(new com.acme.Foo("bar"));
<New class="com.acme.Baz">
  <Arg>
    <Call id="bar" class="com.acme.MyStaticObjectFactory" name="createObject">
      <Arg>2</Arg>
    </Call>
  </Arg>
</New>

This is equivalent to:

new com.acme.Baz(com.acme.MyStaticObjectFactory.createObject(2));

<New>

Instantiates an object. Equivalent to new in Java, and allows the creation of a new object. A New element can contain a sequence of Arg element's, followed by a sequence of configuration elements (Set, Put, etc). Arg element's are used to select a constructor for the object to be created. The sequence of configuration elements then acts on the newly-created object.

Attribute Required Description

class

yes

fully qualified classname, which determines the type of the new object that is instantiated.

id

no

gives a unique name to the object which can be referenced later by Ref elements.

arg

no

comma separated list of arguments may be used for simple string values rather than Arg elements

Examples
Basic example
<New class="com.acme.Foo">
  <Arg>bar</Arg>
</New>

Which is equivalent to:

com.acme.Foo foo = new com.acme.Foo("bar");
Instantiate with the Default Constructor
<New class="com.acme.Foo" />

Which is equivalent to:

com.acme.Foo foo = new com.acme.Foo();
Instantiate with Multiple Arguments, Then Configuring Further
<New id="foo" class="com.acme.Foo">
   <Arg>bar</Arg>
   <Arg>baz</Arg>
   <Set name="test">1, 2, 3</Set>
 </New>

Which is equivalent to:

Object foo = new com.acme.Foo("bar", "baz");
foo.setTest("1, 2, 3");

<Ref>

A Ref element allows a previously created object to be referenced by a unique id. It can contain a sequence of elements, such as Set or Put which then act on the referenced object. You can also use a Ref element as a value for other elements such as Set and Arg.

The Ref element provides convenience and eases readability. You can usually achieve the effect of the Ref by nesting elements (method calls), but this can get complicated very easily. The Ref element makes it possible to refer to the same object if you’re using it multiple times, or passing it into multiple methods. It also makes it possible to split up configuration across multiple files.

Attribute Required Description

refid

yes

the unique identifier used to name a previously created object.

Examples
Basic example

Use the referenced object as an argument to a method call or constructor:

<Get id="foo" name="xFoo" />
<Set name="test"><Ref refid="foo"/></Set>

This is equivalent to:

foo = getXFoo();
setSomeMethod(foo);
Manipulating the Object Returned by Ref
<Get id="foo" name="xFoo" />
<Ref refid="foo">
  <Set name="test">1, 2, 3</Set>
</Ref>

This is equivalent to:

foo = getXFoo();
foo.setTest("1, 2, 3");
Ref vs. Nested Elements

Here is an example of the difference in syntax between using the Ref element, and nesting method calls. They are exactly equivalent:

<!-- using Ref in conjunction with Get -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <Get id="Logger" class="org.eclipse.jetty.util.log.Log" name="log"/>
  <Ref refid="Logger">
    <Set name="debugEnabled">true</Set>
  </Ref>
</Configure>
<!-- calling the setter directly on the object returned by Get -->
<Configure id="Server" class="org.eclipse.jetty.server.Server">
  <Get class="org.eclipse.jetty.util.log.Log" name="log">
    <Set name="debugEnabled">true</Set>
  </Get>
</Configure>

Here is a more practical example, taken from the handler configuration section in etc/jetty.xml:

<Set name="handler">
  <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection">
    <Set name="handlers">
      <Array type="org.eclipse.jetty.server.Handler">
        <Item>
          <!-- create a new instance of a ContextHandlerCollection named "Contexts" -->
          <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
        </Item>
        <Item>
          <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
        </Item>
        <Item>
          <!-- create a new instance of a RequestLogHandler named "RequestLog" -->
          <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>
        </Item>
      </Array>
    </Set>
  </New>
</Set>

<Call name="addBean">
  <Arg>
    <New class="org.eclipse.jetty.deploy.ContextDeployer">
      <!-- pass in the ContextHandlerCollection object ("Contexts") that was created earlier, as an argument -->
      <Set name="contexts"><Ref refid="Contexts"/></Set>
    </New>
  </Arg>
</Call>

<!-- configure the RequestLogHandler object ("RequestLog") that we created earlier -->
<Ref refid="RequestLog">
  ....
</Ref>

<Array>

An Array element allows the creation of a new array.

Attribute Required Description

type

no

specify what types of items the array can contain.

id

no

unique identifier you can use to refer to the array later.

Can Contain
Example
<Array type="java.lang.String">
   <Item>value0</Item>
   <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
</Array>

This is equivalent to:

String[] a = new String[] { "value0", new String("value1") };

<Item>

An Item element defines an entry for Array and Map elements.

Attribute Required Description

type

no

force the types of value.

id

no

unique identifier that you can use to refer to the array later.

<Map>

A Map element allows the creation of a new HashMap and to populate it with (key, value) pairs.

Attribute Required Description

id

no

unique identifier you can use to refer to the map later.

Can Contain
Example
<Map>
  <Entry>
    <Item>keyName</Item>
    <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
  </Entry>
</Map>

This is equivalent to:

Map m = new HashMap();
m.put("keyName", new String("value1"));

<Entry>

An Entry element contains a key-value Item element pair for a Map.

Can Contain

<SystemProperty>

A SystemProperty element gets the value of a JVM system property. It can be used within elements that accept values, such as Set, Put, Arg.

Attribute Required Description

name

yes

property name

default

no

a default value as a fallback

id

no

unique identifier which you can use to refer to the array later.

Can Contain

Only attributes as Elements (Id, Name, Default).

Example
<SystemProperty name="jetty.http.port" default="8080"/>

That is equivalent to:

System.getProperty("jetty.http.port", "8080");

Both try to retrieve the value of jetty.http.port. If jetty.http.port is not set, then 8080 is used.

<Property>

A Property element allows arbitrary properties to be retrieved by name. It can contain a sequence of elements, such as Set, Put, Call that act on the retrieved object.

Attribute Required Description

name

yes

property name

default

no

a default value as a fallback

id

no

unique identifier which you can use to refer to the array later.

The Name attribute may be a comma separated list of property names, with the first property name being the "official" name, and the others names being old, deprecated property names that are kept for backward compatibility. A warning log is issued when deprecated property names are used. The Default attribute contains the value to use in case none of the property names is found.

Can Contain

The attributes may be expressed as contained Elements (Id, Name, Default).

Example
<Property name="Server">
  <Call id="jdbcIdMgr" name="getAttribute">
    <Arg>jdbcIdMgr</Arg>
  </Call>
</Property>

Jetty XML Usage

Jetty provides an XML-based configuration. It is grounded in Java’s Reflection API. Classes in the java.lang.reflect represent Java methods and classes, such that you can instantiate objects and invoke their methods based on their names and argument types. Behind the scenes, Jetty’s XML config parser translates the XML elements and attributes into Reflection calls.

Using jetty.xml

To use jetty.xml, specify it as a configuration file when running Jetty.

 java -jar start.jar etc/jetty.xml
If you start Jetty without specifying a configuration file, Jetty automatically locates and uses the default installation jetty.xml file. Therefore java -jar start.jar is equivalent to java -jar start.jar etc/jetty.xml .

Using Multiple Configuration Files

You are not limited to one configuration file; you can use multiple configuration files when running Jetty, and Jetty will configure the appropriate server instance. The ID of the server in the <Configure> tag specifies the instance you want to configure. Each server ID in a configuration file creates a new server instance within the same JVM. If you use the same ID across multiple configuration files, those configurations are all applied to the same server.

Setting Parameters in Configuration Files

You can set parameters in configuration files either with system properties (using <SystemProperty>) or properties files (using <Property>) passed via the command line. For example, this code in jetty.xml allows the port to be defined on the command line, falling back onto `8080`if the port is not specified:

  <Set name="port"><SystemProperty name="jetty.http.port" default="8080"/></Set>

Then you modify the port while running Jetty by using this command:

 java -Djetty.http.port=8888 -jar start.jar etc/jetty.xml

An example of defining both system properties and properties files from the command line:

 java -Djetty.http.port=8888 -jar start.jar myjetty.properties etc/jetty.xml etc/other.xml

jetty.xml

jetty.xml is the default configuration file for Jetty, typically located at $JETTY_HOME/etc/jetty.xml. Usually the jetty.xml configures:

  • The Server class (or subclass if extended) and global options.

  • A ThreadPool (min and max thread).

  • Connectors (ports, timeouts, buffer sizes, protocol).

  • The handler structure (default handlers and/or a contextHandlerCollections).

  • The deployment manager that scans for and deploys webapps and contexts.

  • Login services that provide authentication checking.

  • A request log.

Not all Jetty features are configured in jetty.xml. There are several optional configuration files that share the same format as jetty.xml and, if specified, concatenate to it. These configuration files are also stored in $JETTY_HOME/etc/, and examples of them are in Github Repository. The selection of which configuration files to use is controlled by start.jar and the process of merging configuration is described in Jetty XML Usage.

Root Element

jetty.xml configures an instance of the Jetty org.eclipse.jetty.server.Server.

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure id="Server" class="org.eclipse.jetty.server.Server">
 ...
</Configure>

Examples

$JETTY_HOME/etc contains the default jetty.xml, as well as other sample configuration files (jetty-*.xml) which can be passed to the server via the command line.

Additional Resources

  • Jetty XML Syntax –in-depth reference for Jetty-specific configuration XML syntax.

  • jetty.xml –configuration file for configuring the entire server

jetty-web.xml

jetty-web.xml is a Jetty configuration file that you can bundle with a specific web application. The format of jetty-web.xml is the same as jetty.xml – it is an XML mapping of the Jetty API.

This document offers an overview for using the jetty-web.xml configuration file. For a more in-depth look at the syntax, see Jetty XML Syntax.

Root Element

jetty-web.xml applies on a per-webapp basis, and configures an instance of org.eclipse.jetty.webapp.WebAppContext.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
 ..
</Configure>
Make sure you are applying the configuration to an instance of the proper class. jetty-web.xml configures an instance of WebAppContext; jetty.xml configures an instance of Server.

Using jetty-web.xml

Place the jetty-web.xml into your web application’s WEB-INF folder. When Jetty deploys a web application, it looks for a file called WEB-INF/jetty-web.xml or WEB-INF/web-jetty.xml within the web application (or WAR) and applies the configuration found there. Be aware that jetty-web.xml is called after all other configuration has been applied to the web application.

It is important to note that jetty-web.xml files utilize the webapp classpath, not the classpath of the server.

jetty-web.xml Examples

The distribution contains an example of jetty-web.xml inside the WEB-INF folder of the test webapp WAR ($JETTY_HOME/demo-base/webapps/test.war/WEB-INF/jetty-web.xml).

Additional jetty-web.xml Resources

  • Jetty XML Syntax –in-depth reference for Jetty-specific configuration XML syntax.

  • jetty.xml –configuration file for configuring the entire server

jetty-env.xml

jetty-env.xml is an optional Jetty file that configures JNDI resources for an individual webapp. The format of jetty-env.xml is the same as jetty.xml –it is an XML mapping of the Jetty API.

When Jetty deploys a web application, it automatically looks for a file called ` WEB-INF/jetty-env.xml` within the web application (or WAR), and sets up the webapp naming environment so that naming references in the WEB-INF/web.xml file can be resolved from the information provided in the WEB-INF/jetty-env.xml and jetty.xml files. You define global naming resources on the server via jetty.xml.

jetty-env.xml Root Element

Jetty applies jetty-env.xml on a per-webapp basis, and configures an instance of org.eclipse.jetty.webapp.WebAppContext.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
 ..
</Configure>
Make sure you are applying the configuration to an instance of the proper class. jetty-env.xml configures an instance of WebAppContext, and not an instance of Server.

Using jetty-env.xml

Place the jetty-env.xml file in your web application’s WEB-INF folder.

 <?xml version="1.0"?>
 <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_9_3.dtd">

 <Configure class="org.eclipse.jetty.webapp.WebAppContext">

   <!-- Add an EnvEntry only valid for this webapp               -->
   <New id="gargle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
     <Arg>gargle</Arg>
     <Arg type="java.lang.Double">100</Arg>
     <Arg type="boolean">true</Arg>
   </New>

  <!-- Add an override for a global EnvEntry                           -->
   <New id="wiggle"  class="org.eclipse.jetty.plus.jndi.EnvEntry">
     <Arg>wiggle</Arg>
     <Arg type="java.lang.Double">55.0</Arg>
     <Arg type="boolean">true</Arg>
   </New>

   <!-- an XADataSource                                                -->
   <New id="mydatasource99" class="org.eclipse.jetty.plus.jndi.Resource">
     <Arg>jdbc/mydatasource99</Arg>
     <Arg>
       <New class="com.atomikos.jdbc.SimpleDataSourceBean">
         <Set name="xaDataSourceClassName">org.apache.derby.jdbc.EmbeddedXADataSource</Set>
         <Set name="xaDataSourceProperties">databaseName=testdb99;createDatabase=create</Set>
         <Set name="UniqueResourceName">mydatasource99</Set>
       </New>
     </Arg>
   </New>

 </Configure>

Additional jetty-env.xml Resources

  • Jetty XML Syntax –In-depth reference for Jetty-specific configuration XML syntax.

  • jetty.xml –Configuration file for configuring the entire server.

webdefault.xml

The webdefault.xml file saves web applications from having to define a lot of house-keeping and container-specific elements in their own web.xml files. For example, you can use it to set up MIME-type mappings and JSP servlet-mappings. Jetty applies webdefault.xml to a web application before the application’s own WEB-INF/web.xml, which means that it cannot override values inside the webapp’s web.xml. It uses the jetty.xml syntax. Generally, it is convenient for all webapps in a Jetty instance to share the same webdefault.xml file. However, it is certainly possible to provide differentiated ` webdefault.xml` files for individual web applications.

The webdefault.xml file is located in $(jetty.home)/etc/webdefault.xml.

Using webdefault.xml

You can specify a custom configuration file to use for specific webapps, or for all webapps. If you do not specify an alternate defaults descriptor, the $JETTY-HOME/etc/jetty-deploy.xml file will configure jetty to automatically use $JETTY_HOME/etc/webdefault.xml.

To ensure your webdefault.xml files are validated, you will need to set the validateXml attribute to true as described here.

The webdefault.xml included with the Jetty Distribution contains several configuration options, such as init params and servlet mappings, and is separated into sections for easy navigation. Some of the more common options include, but are not limited to:

dirAllowed

If true, directory listings are returned if no welcome file is found. Otherwise 403 Forbidden displays.

precompressed

If set to a comma separated list of file extensions, these indicate compressed formats that are known to map to a MIME-type that may be listed in a requests Accept-Encoding header. If set to a boolean True, then a default set of compressed formats will be used, otherwise no pre-compressed formats.

maxCacheSize

Maximum total size of the cache or 0 for no cache.

maxCachedFileSize

Maximum size of a file to cache.

maxCachedFiles

Maximum number of files to cache.

Creating a Custom webdefault.xml for One WebApp

You can specify a custom webdefault.xml for an individual web application in that webapp’s jetty.xml as follows:

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  ...
  <!-- Set up the absolute path to the custom webdefault.xml -->
  <Set name="defaultsDescriptor">/my/path/to/webdefault.xml</Set>
  ...
</Configure>

The equivalent in code is:

import org.eclipse.jetty.webapp.WebAppContext;

    ...

    WebAppContext wac = new WebAppContext();
    ...
    //Set up the absolute path to the custom webdefault.xml.
    wac.setDefaultsDescriptor("/my/path/to/webdefault.xml");
    ...

Alternatively, you can use a Jetty Classloading to find the resource representing your custom webdefault.xml.

Creating a Custom webdefault.xml for Multiple WebApps

If you want to apply the same custom webdefault.xml to a number of webapps, provide the path to the file in jetty.xml in the $JETTY_HOME/etc/jetty-deploy.xml file:

   <Set name="defaultsDescriptor"><Property name="jetty.home" default="." />/other/path/to/another/webdefault.xml</Set>
Using the Jetty Maven Plugin

Similarly, when using the Jetty Maven Plugin you provide a customized webdefault.xml file for your webapp as follows:

<project>
    ...
    <plugins>
        <plugin>
            ...
            <artifactId>jetty-maven-plugin</artifactId>
            <configuration>
                <webApp>
                  ...
                  <defaultsDescriptor>/my/path/to/webdefault.xml</defaultsDescriptor>
                </webApp>
            </configuration>
        </plugin>
        ...
    </plugins>
    ...
</project>
Additional Resources

Jetty override-web.xml

To deploy a web application or WAR into different environments, most likely you will need to customize the webapp for compatibility with each environment. The challenge is to do so without changing the webapp itself. You can use a jetty.xml file for some of this work since it is not part of the webapp. But there are some changes that jetty.xml cannot accomplish, for example, modifications to servlet init-params and context init-params. Using webdefault.xml is not an option because Jetty applies webdefault.xml to a web application before the application’s own WEB-INF/web.xml, which means that it cannot override values inside the webapp’s ` web.xml`.

The solution is override-web.xml. It is a web.xml file that Jetty applies to a web application after the application’s own WEB-INF/web.xml, which means that it can override values or add new elements. This is defined on a per-webapp basis, using the Jetty XML Syntax.

Using override-web.xml

You can specify the override-web.xml to use for an individual web application in a deployable xml file located in Jetty webapps folder . For example, if you had a webapp named MyApp, you would place a deployable xml file named myapp.xml in ${jetty.base}/webapps which includes an overrideDescriptor entry for the override-web.xml file.

<Configure class="org.eclipse.jetty.webapp.WebAppContext">
  ...
  <!-- Set up the path to the custom override descriptor,
  relative to your $(jetty.home) directory or to the current directory -->
  <Set name="overrideDescriptor"><SystemProperty name="jetty.home" default="."/>/my/path/to/override-web.xml</Set>
  ...
</Configure>

The equivalent in code is:

import org.eclipse.jetty.webapp.WebAppContext;

    ...

    WebAppContext wac = new WebAppContext();
    ...
    //Set the path to the override descriptor, based on your $(jetty.home) directory
    wac.setOverrideDescriptor(System.getProperty("jetty.home")+"/my/path/to/override-web.xml");
    ...

Alternatively, you can use the classloader (Jetty Classloading) to get the path to the override descriptor as a resource.

Using the Jetty Maven Plugin

Use the <overrideDescriptor> tag as follows:

<project>
    ...
    <plugins>
        <plugin>
            ...
            <artifactId>jetty-maven-plugin</artifactId>
            <configuration>
                <webAppConfig>
                  ...
                  <overrideDescriptor>src/main/resources/override-web.xml</overrideDescriptor>
                </webAppConfig>
            </configuration>
        </plugin>
        ...
    </plugins>
    ...
</project>

Additional Resources

  • webdefault.xml –Information for this web.xml -formatted file, applied before the webapp’s web.xml webapp.

  • jetty.xml –Reference for jetty.xml files

Troubleshooting

This is a collection of helpful tricks and tips that we have come across to address odd issues that might arise.

Troubleshooting Zip Exceptions

A Zip exception occurs when Jetty rereads a Jar or WAR file.

The JVM maintains a cache of zip file indexes, and does not support hot replacement of zip files. Thus if you redeploy a web application using the same WAR or Jar files, exceptions occur when Jetty rereads the jars. See Oracle Bug 4774421 for more information.

Remedy

The remedy is to avoid hot replacing Jar or WAR files, which can be difficult if you are using the Webapp Provider. You can use the following techniques to reduce exposure to this issue:

  • Deploy unpacked classes in the WEB-INF/classes directory rather than as a Jar file under WEB-INF/lib.

  • Deploy all WAR and Jar files with a version number in their filename or path. If the code changes, a new version number applies, avoiding the cache problem.

  • Deploy a packed WAR file with the setExtractWAR option set to true. This causes the WAR to be extracted to a temporary directory and thus to a new location. This might not be sufficient if you want to hot-replace and re-extract the WAR, so you might also need to use WebAppContext.setCopyWebInf(true), which (re)copies just the WEB-INF directory to a different location.

  • Deploy an unpacked WAR file with the setCopyWebDir option set to true. This causes the directory to be extracted to a new location.

If you have problems with Windows file-locking preventing static file editing (such as JSP or HTML), use the WebAppContext .setCopyWebDir(true) option.

Troubleshooting Locked Files on Windows

Jetty buffers static content for webapps such as HTML files, CSS files, images, etc. If you are using NIO connectors, Jetty uses memory-mapped files to do this. The problem is that on Windows, memory mapping a file causes the file to lock, so that you cannot update or replace the file. Effectively this means that you have to stop Jetty to update a file.

Remedy

Jetty provides a configuration switch for the DefaultServlet that enables or disables the use of memory-mapped files. If you are running on Windows and are having file-locking problems, you should set this switch to disable memory-mapped file buffers. Use one of the following options to configure the switch.

Using override-web.xml

An override-web.xml file can be placed in your webapp’s WEB-INF directory to change the default setting of the DefaultServlet for memory-mapped file buffers. Create an override-web.xml file with appropriate headers for your version of the servlet specification, and place the following inside the <web-app> element:

 <servlet>
   <servlet-name>default</servlet-name>
   <init-param>
     <param-name>useFileMappedBuffer</param-name>
     <param-value>false</param-value>
   </init-param>
 </servlet>
Using a Context XML File

You can create or update a context xml file that configures your webapp to apply the setting to disable memory-mapped file buffers. Add the following to your context xml file:

  <Call name="setInitParameter">
      <Arg>org.eclipse.jetty.servlet.Default.useFileMappedBuffer</Arg>
      <Arg>false</Arg>
  </Call>
Using the Jetty Maven Plugin

If you don’t want to use either of the other two solutions, you can configure the plugin directly to disable memory-mapped file buffers. Add the following to the plugin’s configuration under the <webApp> element:

  <_initParams>
          <org.eclipse.jetty.servlet.Default.useFileMappedBuffer>false</org.eclipse.jetty.servlet.Default.useFileMappedBuffer>
  </_initParams>

Alternate Remedy

You can force a WebAppContext to always copy a web app directory on deployment. The base directory of your web app (i.e. the root directory where your static content exists) will be copied to the temp directory. Configure this in an xml file like so:

<New id="myWebAppContext"  class="org.eclipse.jetty.webapp.WebAppContext">
  <Set name="contextPath">/</Set>
  <Set name="war">./webapps/fredapp</Set>
  <Set name="copyWebDir">true</Set>
  .
  .
</New>
Be careful with this option when using an explicitly settemp directory name - as the name of the temp directory will not unique across redeployments, copying the static content into the same directory name each time may not avoid the locking problem.

Preventing Memory Leaks

If you have memory leaks, and you have thoroughly investigated tools like jconsole, yourkit, jprofiler, jvisualvm or any of the other profiling and analysis tools, and you can eliminate your code as the source of the problem, read the following sections about how to prevent memory leaks in your application.

Preventing WebApp Classloader Pinning

This feature is available for Jetty 7.6.6 and later.

Code that keeps references to a webapp classloader can cause memory leaks. These leaks fall generally into two categories: static fields and daemon threads.

  • A static field is initialized with the value of the classloader, which happens to be a webapp classloader; as Jetty undeploys and redeploys the webapp, the static reference lives on, meaning garbage collecting cannot occur for the webapp classloader.

  • When Jetty starts as a daemon thread and is outside the lifecycle of the webapp, threads have references to the context classloader that created them, leading to a memory leak if that classloader belongs to a webapp. For a good discussion of the issue see Anatomy of a PermGen Memory Leak.

We provide a number of workaround classes that preemptively invoke the problematic code with the Jetty classloader, thereby ensuring the webapp classloader is not pinned. Be aware that since some of the problematic code creates threads, you should be selective about which preventers you enable, and use only those that are specific to your application.

Preventers

Jetty includes the following preventers.

Preventer Name Problem Addressed

AppContextLeakPreventer

The call to AppContext.getAppContext() keeps a static reference to the context classloader. The JRE can invoke AppContext in many different places.

AWTLeakPreventer

The java.awt.Toolkit class has a static field that is the default toolkit. Creating the default toolkit causes the creation of an EventQueue, which has a classloader field initialized with the thread context class loader. See JBoss bug AS7-3733.

DOMLeakPreventer

DOM parsing can cause the webapp classloader to be pinned, due to the static field ` RuntimeException` of com.sun.org.apache.xerces.internal.parsers.AbstractDOMParser. Oracle bug 6916498 specifically mentions that a heap dump might not identify the GCRoot as the uncollected loader, making it difficult to identify the cause of the leak.

DriverManagerLeakPreventer

The number of threads dedicated to accepting incoming connections.

GCThreadLeakPreventer

Calls to sun.misc.GC.requestLatency create a daemon thread that keeps a reference to the context classloader. A known caller of this method is the RMI impl. See Stackoverflow: Does java garbage collection log entry 'Full GC system' mean some class called System.gc()?

Java2DLeakPreventer

sun.java2d.Disposer keeps a reference to the classloader. See ASF bug 51687.

LDAPLeakPreventer

If com.sun.jndi.LdapPoolManager class is loaded and the system property ` com.sun.jndi.ldap.connect.pool.timeout` is set to a nonzero value, a daemon thread starts and keeps a reference to the context classloader.

LoginConfigurationLeakPreventer

The javax.security.auth.login.Configuration class keeps a static reference to the thread context classloader.

SecurityProviderLeakPreventer

Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread that traps the thread context classloader.

Configuring Preventers

You can individually enable each preventer by adding an instance to a Server with the ` addBean(Object)` call. Here’s an example of how to do it in code with the org.eclipse.jetty.util.preventers.AppContextLeakPreventer:

Server server = new Server();
server.addBean(new AppContextLeakPreventer());

You can add the equivalent in code to the $JETTY_HOME/etc/jetty.xml file or any jetty xml file that is configuring a Server instance. Be aware that if you have more than one Server instance in your JVM, you should configure these preventers on just one of them. Here’s the example from code put into xml:

<Configure id="Server" class="org.eclipse.jetty.server.Server">

   <Call name="addBean">
    <Arg>
      <New class="org.eclipse.jetty.util.preventers.AppContextLeakPreventer"/>
    </Arg>
   </Call>

</Configure>

JSP Bugs: Permgen Problems

The JSP engine in Jetty is Jasper. This was originally developed under the Apache Tomcat project, but over time many different project have forked it. All Jetty versions up to 6 used Apache-based Jasper exclusively, with Jetty 6 using Apache Jasper only for JSP 2.0. With the advent of JSP 2.1, Jetty 6 switched to using Jasper from Sun’s Glassfish project, which is now the reference implementation.

All forks of Jasper suffer from a problem whereby using JSP tag files puts the permgen space under pressure. This is because of the classloading architecture of the JSP implementation. Each JSP file is effectively compiled and its class loaded in its own classloader to allow for hot replacement. Each JSP that contains references to a tag file compiles the tag if necessary and then loads it using its own classloader. If you have many JSPs that refer to the same tag file, the tag’s class is loaded over and over again into permgen space, once for each JSP. See Glassfish bug 3963 and Apache bug 43878. The Apache Tomcat project has already closed this bug with status WON’T FIX, however the Glassfish folks still have the bug open and have scheduled it to be fixed. When the fix becomes available, the Jetty project will pick it up and incorporate into our release program.

JVM Bugs

This section describes garbage collection and direct ByteBuffer problems.

Garbage Collection Problems

One symptom of a cluster of JVM related memory issues is the OOM exception accompanied by a message such as java.lang.OutOfMemoryError: requested xxxx bytes for xxx. Out of swap space?

Oracle bug 4697804 describes how this can happen in the scenario when the garbage collector needs to allocate a bit more space during its run and tries to resize the heap, but fails because the machine is out of swap space. One suggested work around is to ensure that the JVM never tries to resize the heap, by setting min heap size to max heap size:

java -Xmx1024m -Xms1024m

Another workaround is to ensure you have configured sufficient swap space on your device to accommodate all programs you are running concurrently.

Direct ByteBuffers

Exhausting native memory is another issue related to JVM bugs. The symptoms to look out for are the process size growing, but heap use remaining relatively constant. Both the JIT compiler and nio ByteBuffers can consume native memory. Oracle bug 6210541 discusses a still-unsolved problem whereby the JVM itself allocates a direct ByteBuffer in some circumstances while the system never garbage collects, effectively eating native memory. Guy Korland’s blog discusses this problem here and here. As the JIT compiler consumes native memory, the lack of available memory may manifest itself in the JIT as OutOfMemory exceptions such as Exception in thread "CompilerThread0" java.lang.OutOfMemoryError: requested xxx bytes for ChunkPool::allocate. Out of swap space?

By default, Jetty allocates and manages its own pool of direct ByteBuffers for io if you configure the nio SelectChannelConnector. It also allocates MappedByteBuffers to memory-map static files via the DefaultServlet settings. However, you could be vulnerable to this JVM ByteBuffer allocation problem if you have disabled either of these options. For example, if you’re on Windows, you may have disabled the use of memory-mapped buffers for the static file cache on the DefaultServlet to avoid the file-locking problem.

Troubleshooting Slow Deployment

After upgrading to a version of Jetty that supports Servlet Spec 3.0 or above, enabling some new modules, or introducing some new jars to your webapp, you notice that your deployment time is increased. This could be due to scanning for classes caused by a ServletContainerInitializer.

As documented in the section on Using Annotations, even if your webapp has set metadata-complete=true in web.xml, all jars within your webapp may still be scanned due to one or more ServletContainerInitializers that have a @HandlesTypes annotation listing the names of classes in which it is interested.

There are 3 ways to speed up deployment time:

  • limit which ServletContainerInitializers to include

  • limit which jars to scan

  • limit the scan to the first deployment only

Remedies

Limit Which ServletContainerInitializers to Execute

As documented in the section Excluding ServletContainerInitializers, you can provide a context attribute that defines a pattern of ServletContainerInitializer (SCI) class names to ignore. These SCIs will not be examined for @HandlesTypes and will not be executed. This is useful if you have included a 3rd party jar that has a SCI on which your code does not rely.

Limit Which Jars to Scan

As documented in the section Jars Scanned for Annotations, you can explicitly define which jars to include in the scanning process. This is helpful if you have a lot of jars in your webapp, and you know that they do not contain any classes referenced by an @HandlesTypes annotation on a ServletContainerInitializer that will be executed.

Limit Scanning to First Deployment Only (Quickstart)

The quickstart mechanism will do a normal deployment - obeying any limits on SCIs and jars to scan as documented here - the first time the webapp is deployed only. Subsequent deployments will re-use the information discovered during the first deployment. This is useful if you cannot limit the scan significantly by using any of the mechanisms described here, but you don’t want to incur the cost of scanning on every redeployment. The quickstart mechanism and how to use it is described here.

Jetty Security Reports

The following sections provide information about Jetty security issues.

If you would like to report a security issue please follow these instructions.

Table 11. Resolved Issues
yyyy/mm/dd ID Exploitable Severity Affects Fixed Version Comment

2019/08/13

CVE-2019-9518

Med

Med

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to a flood of empty frames, potentially leading to a denial of service.

2019/08/13

CVE-2019-9516

Med

Med

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to a header leak, potentially leading to a denial of service.

2019/08/13

CVE-2019-9515

Med

Med

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to a settings flood, potentially leading to a denial of service when an attacker sent a stream of SETTINGS frames to the peer.

2019/08/13

CVE-2019-9514

Med

Med

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to a reset flood, potentially leading to a denial of service.

2019/08/13

CVE-2019-9512

Low

Low

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to ping floods which could lead to a denial of service.

2019/08/13

CVE-2019-9511

Low

Low

< = 9.4.20

9.4.21

Some HTTP/2 implementations are vulnerable to window size manipulation and stream prioritization manipulation which could lead to a denial of service.

2019/04/11

CVE-2019-10247

Med

Med

< = 9.4.16

9.2.28, 9.3.27, 9.4.17

If no webapp was mounted to the root namespace and a 404 was encountered, an HTML page would be generated displaying the fully qualified base resource location for each context.

2019/04/11

CVE-2019-10246

High

High

< = 9.4.16

9.2.28, 9.3.27, 9.4.17

Use of DefaultServlet or ResourceHandler with indexing was vulnerable to XSS behaviors to expose the directory listing on Windows operating systems.

2019/04/11

CVE-2019-10241

High

High

< = 9.4.15

9.2.27, 9.3.26, 9.4.16

Use of DefaultServlet or ResourceHandler with indexing was vulnerable to XSS behaviors to expose the directory listing.

2018/06/25

CVE-2018-12538

High

High

>= 9.4.0, < = 9.4.8

9.4.9

HttpSessions present specifically in the FileSystem’s storage could be hijacked/accessed by an unauthorized user.

2018/06/25

CVE-2018-12536

High

See CWE-202

< = 9.4.10

9.2.25, 9.3.24, 9.4.11

InvalidPathException Message reveals webapp system path.

2018/06/25

CVE-2017-7658

See CWE-444

See CWE-444

< = 9.4.10

9.2.25, 9.3.24, 9.4.11

Too Tolerant Parser, Double Content-Length + Transfer-Encoding + Whitespace.

2018/06/25

CVE-2017-7657

See CWE-444

See CWE-444

< = 9.4.10

9.2.25, 9.3.24, 9.4.11

HTTP/1.1 Request smuggling with carefully crafted body content (Does not apply to HTTP/1.0 or HTTP/2).

2018/06/25

CVE-2017-7656

See CWE-444

See CWE-444

< = 9.4.10

9.2.25, 9.3.24, 9.4.11

HTTP Request Smuggling when used with invalid request headers (for HTTP/0.9).

2016/05/31

CVE-2016-4800

high

high

>= 9.3.0, < = 9.3.8

9.3.9

Alias vulnerability allowing access to protected resources within a webapp on Windows.

2015/02/24

CVE-2015-2080

high

high

>=9.2.3 <9.2.9

9.2.9

JetLeak exposure of past buffers during HttpParser error

2013/11/27

PT-2013-65

medium

high

>=9.0.0 <9.0.5

9.0.6 418014

Alias checking disabled by NTFS errors on Windows.

2013/07/24

413684

low

medium

>=7.6.9 <9.0.5

7.6.13,8.1.13,9.0.5 413684

Constraints bypassed if Unix symlink alias checker used on Windows.

2011/12/29

CERT2011-003 CVE-2011-4461

high

medium

All versions

7.6.0.RCO Jetty-367638

Added ContextHandler.setMaxFormKeys (intkeys) to limit the number of parameters (default 1000).

2009/11/05

CERT2011-003 CERT2011-003

medium

high

JVM<1.6u19

jetty-7.01.v20091125, jetty-6.1.22

Work around by turning off SSL renegotiation in Jetty. If using JVM > 1.6u19 setAllowRenegotiate(true) may be called on connectors.

2009/06/18

Jetty-1042

low

high

< = 6.1.18, < = 7.0.0.M4

6.1.19, 7.0.0.Rc0

Cookie leak between requests sharing a connection.

2009/04/30

CERT402580

medium

high

< = 6.1.16, < = 7.0.0.M2

5.1.15, 6.1.18, 7.0.0.M2

Jetty-1004

View arbitrary disk content in some specific configurations.

2007/12/22

CERT553235 CVE-2007-6672

high

medium

6.1.rrc0-6.1.6

6.1.7

CERT553235

Static content visible in WEB-INF and past security constraints.

2007/11/05

CERT438616 CVE-2007-5614

low

low

<6.1.6

6.1.6rc1 (patch in CVS for jetty5)

Single quote in cookie name.

2007/11/05

CERT237888> CVE-2007-5613

low

low

<6.1.6

6.1.6rc0 (patch in CVS for jetty5)

XSS in demo dup servlet.

2007/11/03

CERT212984 > CVE-2007-5615

medium

medium

<6.1.6

6.1.6rc0 (patch in CVS for jetty5)

CRLF Response splitting.

2006/11/22

CVE-2006-6969

low

high

<6.1.0, <6.0.2, <5.1.12, <4.2.27

6.1.0pre3, 6.0.2, 5.1.12, 4.2.27

Session ID predictability.

2006/06/01

CVE-2006-2759

medium

medium

<6.0.*, <6.0.0Beta17

6.0.0Beta17

JSP source visibility.

2006/01/05

medium

medium

<5.1.10

5.1.10

Fixed //security constraint bypass on Windows.

2005/11/18

CVE-2006-2758

medium

medium

<5.1.6

5.1.6, 6.0.0Beta4

JSP source visibility.

2004/02/04

JSSE 1.0.3_01

medium

medium

<4.2.7

4.2.7

Upgraded JSSE to obtain downstream security fix.

2002/09/22

high

high

<4.1.0

4.1.0

Fixed CGI servlet remove exploit.

2002/03/12

medium

<3.1.7

4.0.RC2, 3.1.7

Fixed // security constraint bypass.

2001/10/21

medium

high

<3.1.3

3.1.3

Fixed trailing null security constraint bypass.

Java WatchService

The JVM WatchService is in place to monitor objects like a directory for changes, and then update it’s contents and notify the application of those changes. This service is useful for features like Hot Deployment. When a change is detected, the WatchService will enter a "quiet time" where it is waiting for the change (or changes) to be made and completed before notifying the application of the change.

Example: A new war file is copied into /webapps. The WatchService can (depending on implementation) see that the file was created (which is registered as an event!, and that its growing in size (another event). With the quiet time, each of the events are gated behind that timeout before the aggregated events are sent to the application.

While some operating systems such as Windows have a native value for this quiet time, not all do, notably OSX. At the core this is a limitation of the JVM’s FileSystem-specific implementation, but one that has been raised to the attention of the project.

Remedy

To help offset the delay in systems like OSX, Jetty defaults the value for non-native implementations to a time of 5000ms. Using values lower than 5000ms is not recommended and has shown to frequently fail.

Debugging

Options

Given how flexible Jetty is in how it can be configured and deployed into development and production, there exists a wealth of different options for debugging your application in you favorite environment. In this section we will gather up some of these different options available and explain how you can use them. If you would like to contribute to this section simply fork the repository and contribute the information, or open a github issue with the information and we’ll bring it over.

Enable remote debugging

Remote Debugging

If you have a web application deployed into Jetty you can interact with it remotely from a debugging perspective easily. The basics are that you must start up the remote JVM with additional parameters and then start up a remote debugging session in Eclipse for the webapp in question. This is easily accomplished.

This example assumes you are deploying your web application into the jetty-distribution.
Starting Jetty

Assuming you have your webapp deployed into jetty, there are two different ways to approach this:

Via command line

Add the required parameters on the commandline like so.

$ java -Xdebug -agentlib:jdwp=transport=dt_socket,address=9999,server=y,suspend=n -jar start.jar
Via start.ini

This approach is best used if you want to debug a particular jetty-distribution and not have to remember the commandline incantations.

  1. Edit the start.ini and uncomment the --exec line, this is required if you are adding jvm options to the start.ini file as jetty-start must generate the classpath required and fork a new jvm.

  2. Add the parameters mentioned above in the Command Line option so your start.ini looks like this:

    #===========================================================
    # Configure JVM arguments.
    # If JVM args are include in an ini file then --exec is needed
    # to start a new JVM from start.jar with the extra args.
    # If you wish to avoid an extra JVM running, place JVM args
    # on the normal command line and do not use --exec
    #-----------------------------------------------------------
    --exec
    -Xdebug
    -agentlib:jdwp=transport=dt_socket,address=9999,server=y,suspend=n
    # -Xmx2000m
    # -Xmn512m
    # -XX:+UseConcMarkSweepGC
    # -XX:ParallelCMSThreads=2
    # -XX:+CMSClassUnloadingEnabled
    # -XX:+UseCMSCompactAtFullCollection
    # -XX:CMSInitiatingOccupancyFraction=80
    # -verbose:gc
    # -XX:+PrintGCDateStamps
    # -XX:+PrintGCTimeStamps
    # -XX:+PrintGCDetails
    # -XX:+PrintTenuringDistribution
    # -XX:+PrintCommandLineFlags
    # -XX:+DisableExplicitGC

    Uncomment any other jvm environmental options you so desire for your debugging session.

  3. Regardless of the option chosen, you should see the following lines at the top of your jetty-distribution startup.

    Listening for transport dt_socket at address: 9999
Linking with your IDE

Refer to the documentation for your ide:

Debugging With IntelliJ

There are a number of options available to debug your application in IntelliJ.

If not done already prepare your application for remote debugging as described here: Enable remote debugging

Linking with IntelliJ

Next we need to link the IntelliJ project with the deployed webapp.

  1. Within IntelliJ, open the project containing the webapp deployed into jetty that you want to debug. SelectRun → Edit Configurations. Add a new configuration by clicking the "+" icon. Choose Remote. Make sure the port you choose is the same as the one you added in Enable remote debugging.

    image

  2. Next in your webapp you can set a breakpoint within a servlet which when it is tripped will halt the remote jvm’s processing thread to await for debugging commands from your IntelliJ instance. To set a breakpoint, simply open the servlet or any other class you want to debug and click left to the line you want to set the breakpoint at (where the red dot is on the next screenshot). The red dot and red background on the line mark the breakpoint.

    image

  3. Accessing that servlet within your browser, pointed at your remote debug configured jetty-distribution, should transition your IntelliJ instance to the standard debugger view.

    image

Within IntelliJ

Since Jetty can be incredibly simple to embed, many people choose to create a small main method which they can launch directly within IntelliJ in order to more easily debug their entire application. The best place to get started on this approach is to look through Embedding Jetty and the Embedded Examples sections.

Once you have a main method defined in order to launch your application, open the source file and right-click the main method. Select Debug or simply hit CTRL+SHIFT+D. In your Console tab within IntelliJ you should see your application startup and once it has completed startup you should be able to configure breakpoints and hit the Jetty instance as normal via your web browser. The same thing works for unit tests. Instead of the main method run debug on the test method you want to debug.

image

Debugging in IntelliJ is extremely powerful. For example it’s possible to have conditional breakpoints that only trigger a break if the configured conditions are met. Have a look at the various tutorials in the internet or the IntelliJ documentation for further details.

You can easily configure logging through a jetty-logging.properties file. If this file is on your classpath then Jetty will use it for configuring logging, we use this approach extensively throughout Jetty development and it makes life ever so much easier. You can see this in action in the [configuring-jetty-stderrlog] section.

Debugging With Eclipse

There are a number of options available to debug your application in Eclipse.

If not done already prepare your application for remote debugging as described here: Enable remote debugging

Linking with Eclipse

Next we need to link the Eclipse project with the deployed webapp.

  1. Within Eclipse, right-click on the project containing the webapp deployed into jetty and select Debug → Debug Configurations and create a new configuration of Remote Java Application. Make sure the port you choose is the same as the one you added in Enable remote debugging.

    image

  2. Next in your webapp you can set a breakpoint within a servlet which when it is tripped will halt the remote jvm’s processing thread to await for debugging commands from your Eclipse instance.

    image

  3. Accessing that servlet within your browser, pointed at your remote debug configurated jetty-distribution, should transition your Eclipse instance to the standard Debug view.

    image

Within Eclipse

Since Jetty can be incredibly simple to embed, many people choose to create a small main method which they can launch directly within Eclipse in order to more easily debug their entire application. The best place to get started on this approach is to look through Embedding Jetty and the Embedded Examples sections.

Once you have a main method defined in order to launch your application, right-click on the source file and selectDebug As → Java Application. In your Console tab within Eclipse you should see your application startup and once it has completed startup you should be able to configure breakpoints and hit the Jetty instance as normal via your web browser.

You can easily configure logging through a jetty-logging.properties file. If this file is on your classpath then Jetty will use it for configuring logging, we use this approach extensively throughout Jetty development and it makes life ever so much easier. You can see this in action in the [configuring-jetty-stderrlog] section.

Unresolved directive in old_docs/server.adoc - include::upgrading/chapter.adoc[]

Appendix A: Jetty Architecture

Jetty Component Architecture

Applications that use the Jetty libraries (both client and server) create objects from Jetty classes and compose them together to obtain the desired functionalities.

A client application creates a ClientConnector instance, a HttpClientTransport instance and an HttpClient instance and compose them to have a working HTTP client that uses to call third party services.

A server application creates a ThreadPool instance, a Server instance, a ServerConnector instance, a Handler instance and compose them together to expose an HTTP service.

Internally, the Jetty libraries create even more instances of other components that also are composed together with the main ones created by applications.

The end result is that an application based on the Jetty libraries is a tree of components. In server application the root of the component tree is a Server instance, while in client applications the root of the component tree is an HttpClient instance.

Having all the Jetty components in a tree is beneficial in a number of use cases. It makes possible to register the components in the tree as JMX MBeans so that a JMX console can look at the internal state of the components. It also makes possible to dump the component tree (and therefore each component’s internal state) to a log file or to the console for troubleshooting purposes.

Jetty Component Lifecycle

Jetty components typically have a life cycle: they can be started and stopped. The Jetty components that have a life cycle implement the org.eclipse.jetty.util.component.LifeCycle interface.

Jetty components that contain other components implement the org.eclipse.jetty.util.component.Container interface and typically extend the org.eclipse.jetty.util.component.ContainerLifeCycle class. ContainerLifeCycle can contain these type of components, also called beans:

  • managed beans, LifeCycle instances whose life cycle is tied to the life cycle of their container

  • unmanaged beans, LifeCycle instances whose life cycle is not tied to the life cycle of their container

  • POJO (Plain Old Java Object) beans, instances that do not implement LifeCycle

ContainerLifeCycle uses the following logic to determine if a bean should be managed, unmanaged or POJO:

  • the bean implements LifeCycle

    • the bean is not started, add it as managed

    • the bean is started, add it as unmanaged

  • the bean does not implement LifeCycle, add it as POJO

When a ContainerLifeCycle is started, it also starts recursively all its managed beans (if they implement LifeCycle); unmanaged beans are not started during the ContainerLifeCycle start cycle. Likewise, stopping a ContainerLifeCycle stops recursively and in reverse order all its managed beans; unmanaged beans are not stopped during the ContainerLifeCycle stop cycle.

Components can also be started and stopped individually, therefore activating or deactivating the functionalities that they offer.

Applications should first compose components in the desired structure, and then start the root component:

class Monitor extends AbstractLifeCycle
{
}

class Root extends ContainerLifeCycle
{
    // Monitor is an internal component.
    private final Monitor monitor = new Monitor();

    public Root()
    {
        // The Monitor life cycle is managed by Root.
        addManaged(monitor);
    }
}

class Service extends ContainerLifeCycle
{
    // An instance of the Java scheduler service.
    private ScheduledExecutorService scheduler;

    @Override
    protected void doStart() throws Exception
    {
        // Java's schedulers cannot be restarted, so they must
        // be created anew every time their container is started.
        scheduler = Executors.newSingleThreadScheduledExecutor();
        // Even if Java scheduler does not implement
        // LifeCycle, make it part of the component tree.
        addBean(scheduler);
        // Start all the children beans.
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        // Perform the opposite operations that were
        // performed in doStart(), in reverse order.
        super.doStop();
        removeBean(scheduler);
        scheduler.shutdown();
    }
}

// Create a Root instance.
Root root = new Root();

// Create a Service instance.
Service service = new Service();

// Link the components.
root.addBean(service);

// Start the root component to
// start the whole component tree.
root.start();

The component tree is the following:

Root
├── Monitor (MANAGED)
└── Service (MANAGED)
    └── ScheduledExecutorService (POJO)

When the Root instance is created, also the Monitor instance is created and added as bean, so Monitor is the first bean of Root. Monitor is a managed bean, because it has been explicitly added to Root via ContainerLifeCycle.addManaged(…​).

Then, the application creates a Service instance and adds it to Root via ContainerLifeCycle.addBean(…​), so Service is the second bean of Root. Service is a managed bean too, because it is a LifeCycle and at the time it was added to Root is was not started.

The ScheduledExecutorService within Service does not implement LifeCycle so it is added as a POJO to Service.

It is possible to stop and re-start any component in a tree, for example:

class Root extends ContainerLifeCycle
{
}

class Service extends ContainerLifeCycle
{
    // An instance of the Java scheduler service.
    private ScheduledExecutorService scheduler;

    @Override
    protected void doStart() throws Exception
    {
        // Java's schedulers cannot be restarted, so they must
        // be created anew every time their container is started.
        scheduler = Executors.newSingleThreadScheduledExecutor();
        // Even if Java scheduler does not implement
        // LifeCycle, make it part of the component tree.
        addBean(scheduler);
        // Start all the children beans.
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        // Perform the opposite operations that were
        // performed in doStart(), in reverse order.
        super.doStop();
        removeBean(scheduler);
        scheduler.shutdown();
    }
}

Root root = new Root();
Service service = new Service();
root.addBean(service);

// Start the Root component.
root.start();

// Stop temporarily Service without stopping the Root.
service.stop();

// Restart Service.
service.start();

Service can be stopped independently of Root, and re-started. Starting and stopping a non-root component does not alter the structure of the component tree, just the state of the subtree starting from the component that has been stopped and re-started.

Container provides an API to find beans in the component tree:

class Root extends ContainerLifeCycle
{
}

class Service extends ContainerLifeCycle
{
    private ScheduledExecutorService scheduler;

    @Override
    protected void doStart() throws Exception
    {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        addBean(scheduler);
        super.doStart();
    }

    @Override
    protected void doStop() throws Exception
    {
        super.doStop();
        removeBean(scheduler);
        scheduler.shutdown();
    }
}

Root root = new Root();
Service service = new Service();
root.addBean(service);

// Start the Root component.
root.start();

// Find all the direct children of root.
Collection<Object> children = root.getBeans();
// children contains only service

// Find all descendants of root that are instance of a particular class.
Collection<ScheduledExecutorService> schedulers = root.getContainedBeans(ScheduledExecutorService.class);
// schedulers contains the service scheduler.

You can add your own beans to the component tree at application startup time, and later find them from your application code to access their services.

The component tree should be used for long-lived or medium-lived components such as thread pools, web application contexts, etc.

It is not recommended adding to, and removing from, the component tree short-lived objects such as HTTP requests or TCP connections, for performance reasons.

If you need component tree features such as automatic export to JMX or dump capabilities for short-lived objects, consider having a long-lived container in the component tree instead. You can make the long-lived container efficient at adding/removing the short-lived components using a data structure that is not part of the component tree, and make the long-lived container handle the JMX and dump features for the short-lived components.

Jetty Component Listeners

A component that extends AbstractLifeCycle inherits the possibility to add/remove event listeners for various events emitted by components.

A component that implements java.util.EventListener that is added to a ContainerLifeCycle is also registered as an event listener.

The following sections describe in details the various listeners available in the Jetty component architecture.

LifeCycle.Listener

A LifeCycle.Listener emits events for life cycle events such as starting, stopping and failures:

Server server = new Server();

// Add an event listener of type LifeCycle.Listener.
server.addEventListener(new LifeCycle.Listener()
{
    @Override
    public void lifeCycleStarted(LifeCycle lifeCycle)
    {
        System.getLogger("server").log(INFO, "Server {0} has been started", lifeCycle);
    }

    @Override
    public void lifeCycleFailure(LifeCycle lifeCycle, Throwable failure)
    {
        System.getLogger("server").log(INFO, "Server {0} failed to start", lifeCycle, failure);
    }

    @Override
    public void lifeCycleStopped(LifeCycle lifeCycle)
    {
        System.getLogger("server").log(INFO, "Server {0} has been stopped", lifeCycle);
    }
});

For example, a life cycle listener attached to a Server instance could be used to create (for the started event) and delete (for the stopped event) a file containing the process ID of the JVM that runs the Server.

Container.Listener

A component that implements Container is a container for other components and ContainerLifeCycle is the typical implementation.

A Container emits events when a component (also called bean) is added to or removed from the container:

Server server = new Server();

// Add an event listener of type LifeCycle.Listener.
server.addEventListener(new Container.Listener()
{
    @Override
    public void beanAdded(Container parent, Object child)
    {
        System.getLogger("server").log(INFO, "Added bean {1} to {0}", parent, child);
    }

    @Override
    public void beanRemoved(Container parent, Object child)
    {
        System.getLogger("server").log(INFO, "Removed bean {1} from {0}", parent, child);
    }
});

A Container.Listener added as a bean will also be registered as a listener:

class Parent extends ContainerLifeCycle
{
}

class Child
{
}

// The older child takes care of its siblings.
class OlderChild extends Child implements Container.Listener
{
    private Set<Object> siblings = new HashSet<>();

    @Override
    public void beanAdded(Container parent, Object child)
    {
        siblings.add(child);
    }

    @Override
    public void beanRemoved(Container parent, Object child)
    {
        siblings.remove(child);
    }
}

Parent parent = new Parent();

Child older = new OlderChild();
// The older child is a child bean _and_ a listener.
parent.addBean(older);

Child younger = new Child();
// Adding a younger child will notify the older child.
parent.addBean(younger);
Container.InheritedListener

A Container.InheritedListener is a listener that will be added to all descendants that are also Containers.

Listeners of this type may be added to the component tree root only, but will be notified of every descendant component that is added to or removed from the component tree (not only first level children).

The primary use of Container.InheritedListener within the Jetty Libraries is MBeanContainer from the Jetty JMX support.

MBeanContainer listens for every component added to the tree, converts it to an MBean and registers it to the MBeanServer; for every component removed from the tree, it unregisters the corresponding MBean from the MBeanServer.

Jetty JMX Support

The Java Management Extensions (JMX) APIs are standard API for managing and monitoring resources such as applications, devices, services, and the Java Virtual Machine itself.

The JMX API includes remote access, so a remote management console such as Java Mission Control can interact with a running application for these purposes.

Jetty architecture is based on components organized in a tree. Every time a component is added to or removed from the component tree, an event is emitted, and Container.Listener implementations can listen to those events and perform additional actions.

org.eclipse.jetty.jmx.MBeanContainer listens to those events and registers/unregisters the Jetty components as MBeans into the platform MBeanServer.

The Jetty components are annotated with Jetty JMX annotations so that they can provide specific JMX metadata such as attributes and operations that should be exposed via JMX.

Therefore, when a component is added to the component tree, MBeanContainer is notified, it creates the MBean from the component POJO and registers it to the MBeanServer. Similarly, when a component is removed from the tree, MBeanContainer is notified, and unregisters the MBean from the MBeanServer.

The Maven coordinates for the Jetty JMX support are:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-jmx</artifactId>
  <version>10.0.0.beta1</version>
</dependency>

Enabling JMX Support

Enabling JMX support is always recommended because it provides valuable information about the system, both for monitoring purposes and for troubleshooting purposes in case of problems.

To enable JMX support on the server:

Server server = new Server();

// Create an MBeanContainer with the platform MBeanServer.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());

// Add MBeanContainer to the root component.
server.addBean(mbeanContainer);

Similarly on the client:

HttpClient httpClient = new HttpClient();

// Create an MBeanContainer with the platform MBeanServer.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());

// Add MBeanContainer to the root component.
httpClient.addBean(mbeanContainer);

The MBeans exported to the platform MBeanServer can only be accessed locally (from the same machine), not from remote machines.

This means that this configuration is enough for development, where you have easy access (with graphical user interface) to the machine where Jetty runs, but it is typically not enough when the machine where Jetty runs is remote, or only accessible via SSH or otherwise without graphical user interface support. In these cases, you have to enable JMX Remote Access.

Enabling JMX Remote Access

There are two ways of enabling remote connectivity so that JMC can connect to the remote JVM to visualize MBeans.

  • Use the com.sun.management.jmxremote system property on the command line. Unfortunately, this solution does not work well with firewalls and is not flexible.

  • Use Jetty’s ConnectorServer class.

org.eclipse.jetty.jmx.ConnectorServer will use by default RMI to allow connection from remote clients, and it is a wrapper around the standard JDK class JMXConnectorServer, which is the class that provides remote access to JMX clients.

Connecting to the remote JVM is a two step process:

  • First, the client will connect to the RMI registry to download the RMI stub for the JMXConnectorServer; this RMI stub contains the IP address and port to connect to the RMI server, i.e. the remote JMXConnectorServer.

  • Second, the client uses the RMI stub to connect to the RMI server (i.e. the remote JMXConnectorServer) typically on an address and port that may be different from the RMI registry address and port.

The host and port configuration for the RMI registry and the RMI server is specified by a JMXServiceURL. The string format of an RMI JMXServiceURL is:

service:jmx:rmi://<rmi_server_host>:<rmi_server_port>/jndi/rmi://<rmi_registry_host>:<rmi_registry_port>/jmxrmi

Default values are:

rmi_server_host = localhost
rmi_server_port = 1099
rmi_registry_host = localhost
rmi_registry_port = 1099

With the default configuration, only clients that are local to the server machine can connect to the RMI registry and RMI server - this is done for security reasons. With this configuration it would still be possible to access the MBeans from remote using a SSH tunnel.

By specifying an appropriate JMXServiceURL, you can fine tune the network interfaces the RMI registry and the RMI server bind to, and the ports that the RMI registry and the RMI server listen to. The RMI server and RMI registry hosts and ports can be the same (as in the default configuration) because RMI is able to multiplex traffic arriving to a port to multiple RMI objects.

If you need to allow JMX remote access through a firewall, you must open both the RMI registry and the RMI server ports.

JMXServiceURL common examples:

service:jmx:rmi:///jndi/rmi:///jmxrmi
  rmi_server_host = local host address
  rmi_server_port = randomly chosen
  rmi_registry_host = local host address
  rmi_registry_port = 1099

service:jmx:rmi://0.0.0.0:1099/jndi/rmi://0.0.0.0:1099/jmxrmi
  rmi_server_host = any address
  rmi_server_port = 1099
  rmi_registry_host = any address
  rmi_registry_port = 1099

service:jmx:rmi://localhost:1100/jndi/rmi://localhost:1099/jmxrmi
  rmi_server_host = loopback address
  rmi_server_port = 1100
  rmi_registry_host = loopback address
  rmi_registry_port = 1099

When ConnectorServer is started, its RMI stub is exported to the RMI registry. The RMI stub contains the IP address and port to connect to the RMI object, but the IP address is typically the machine host name, not the host specified in the JMXServiceURL.

To control the IP address stored in the RMI stub you need to set the system property java.rmi.server.hostname with the desired value. This is especially important when binding the RMI server host to the loopback address for security reasons. See also JMX Remote Access via SSH Tunnel.

To allow JMX remote access, create and configure a ConnectorServer:

Server server = new Server();

// Setup Jetty JMX.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);

// Setup ConnectorServer.

// Bind the RMI server to the wildcard address and port 1999.
// Bind the RMI registry to the wildcard address and port 1099.
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1999, "/jndi/rmi:///jmxrmi");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, "org.eclipse.jetty.jmx:name=rmiconnectorserver");

// Add ConnectorServer as a bean, so it is started
// with the Server and also exported as MBean.
server.addBean(jmxServer);

server.start();

JMX Remote Access Authorization

The standard JMXConnectorServer provides several options to authorize access, for example via JAAS or via configuration files. For a complete guide to controlling authentication and authorization in JMX, see the official JMX documentation.

In the sections below we detail one way to setup JMX authentication and authorization, using configuration files for users, passwords and roles:

Server server = new Server();

// Setup Jetty JMX.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);

// Setup ConnectorServer.
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
Map<String, Object> env = new HashMap<>();
env.put("com.sun.management.jmxremote.access.file", "/path/to/users.access");
env.put("com.sun.management.jmxremote.password.file", "/path/to/users.password");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, env, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
server.addBean(jmxServer);

server.start();

The users.access file format is defined in the $JAVA_HOME/conf/management/jmxremote.access file. A simplified version is the following:

users.access
user1 readonly
user2 readwrite

The users.password file format is defined in the $JAVA_HOME/conf/management/jmxremote.password.template file. A simplified version is the following:

users.password
user1 password1
user2 password2
The users.access and users.password files are not standard *.properties files — the user must be separated from the role or password by a space character.
Securing JMX Remote Access with TLS

The JMX communication via RMI happens by default in clear-text.

It is possible to configure the ConnectorServer with a SslContextFactory so that the JMX communication via RMI is encrypted:

Server server = new Server();

// Setup Jetty JMX.
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);

// Setup SslContextFactory.
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath("/path/to/keystore");
sslContextFactory.setKeyStorePassword("secret");

// Setup ConnectorServer with SslContextFactory.
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, null, "org.eclipse.jetty.jmx:name=rmiconnectorserver", sslContextFactory);
server.addBean(jmxServer);

server.start();

It is possible to use the same SslContextFactory.Server used to configure the Jetty ServerConnector that supports TLS also for the JMX communication via RMI.

The keystore must contain a valid certificate signed by a Certification Authority.

The RMI mechanic is the usual one: the RMI client (typically a monitoring console) will connect first to the RMI registry (using TLS), download the RMI server stub that contains the address and port of the RMI server to connect to, then connect to the RMI server (using TLS).

This also mean that if the RMI registry and the RMI server are on different hosts, the RMI client must have available the cryptographic material to validate both hosts.

Having certificates signed by a Certification Authority simplifies by a lot the configuration needed to get the JMX communication over TLS working properly.

If that is not the case (for example the certificate is self-signed), then you need to specify the required system properties that allow RMI (especially when acting as an RMI client) to retrieve the cryptographic material necessary to establish the TLS connection.

For example, trying to connect using the JDK standard JMXConnector with both the RMI server and the RMI registry via TLS to domain.com with a self-signed certificate:

// System properties necessary for an RMI client to trust a self-signed certificate.
System.setProperty("javax.net.ssl.trustStore", "/path/to/trustStore");
System.setProperty("javax.net.ssl.trustStorePassword", "secret");

JMXServiceURL jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://domain.com:1100/jmxrmi");

Map<String, Object> clientEnv = new HashMap<>();
// Required to connect to the RMI registry via TLS.
clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());

try (JMXConnector client = JMXConnectorFactory.connect(jmxURL, clientEnv))
{
    Set<ObjectName> names = client.getMBeanServerConnection().queryNames(null, null);
}

Similarly, to launch JMC:

$ jmc -vmargs -Djavax.net.ssl.trustStore=/path/to/trustStore -Djavax.net.ssl.trustStorePassword=secret
These system properties are required when launching the ConnectorServer too, on the server, because it acts as an RMI client with respect to the RMI registry.
JMX Remote Access with Port Forwarding via SSH Tunnel

You can access JMX MBeans on a remote machine when the RMI ports are not open, for example because of firewall policies, but you have SSH access to the machine using local port forwarding via an SSH tunnel.

In this case you want to configure the ConnectorServer with a JMXServiceURL that binds the RMI server and the RMI registry to the loopback interface only: service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi.

Then you setup the local port forwarding with the SSH tunnel:

$ ssh -L 1099:localhost:1099 <user>@<machine_host>

Now you can use JConsole or JMC to connect to localhost:1099 on your local computer. The traffic will be forwarded to machine_host and when there, SSH will forward the traffic to localhost:1099, which is exactly where the ConnectorServer listens.

When you configure ConnectorServer in this way, you must set the system property -Djava.rmi.server.hostname=localhost, on the server. This is required because when the RMI server is exported, its address and port are stored in the RMI stub. You want the address in the RMI stub to be localhost so that when the RMI stub is downloaded to the remote client, the RMI communication will go through the SSH tunnel.

Jetty JMX Annotations

The Jetty JMX support, and in particular MBeanContainer, is notified every time a bean is added to the component tree.

The bean is scanned for Jetty JMX annotations to obtain JMX metadata: the JMX attributes and JMX operations.

// Annotate the class with @ManagedObject and provide a description.
@ManagedObject("Services that provide useful features")
class Services
{
    private final Map<String, Object> services = new ConcurrentHashMap<>();
    private boolean enabled = true;

    // A read-only attribute with description.
    @ManagedAttribute(value = "The number of services", readonly = true)
    public int getServiceCount()
    {
        return services.size();
    }

    // A read-write attribute with description.
    // Only the getter is annotated.
    @ManagedAttribute(value = "Whether the services are enabled")
    public boolean isEnabled()
    {
        return enabled;
    }

    // There is no need to annotate the setter.
    public void setEnabled(boolean enabled)
    {
        this.enabled = enabled;
    }

    // An operation with description and impact.
    // The @Name annotation is used to annotate parameters
    // for example to display meaningful parameter names.
    @ManagedOperation(value = "Retrieves the service with the given name", impact = "INFO")
    public Object getService(@Name(value = "serviceName") String n)
    {
        return services.get(n);
    }
}

The JMX metadata and the bean are wrapped by an instance of org.eclipse.jetty.jmx.ObjectMBean that exposes the JMX metadata and, upon request from JMX consoles, invokes methods on the bean to get/set attribute values and perform operations.

You can provide a custom subclass of ObjectMBean to further customize how the bean is exposed to JMX.

The custom ObjectMBean subclass must respect the following naming convention: <package>.jmx.<class>MBean. For example, class com.acme.Foo may have a custom ObjectMBean subclass named com.acme.jmx.FooMBean.

//package com.acme;
@ManagedObject
class Service
{
}

//package com.acme.jmx;
class ServiceMBean extends ObjectMBean
{
    ServiceMBean(Object service)
    {
        super(service);
    }
}

The custom ObjectMBean subclass is also scanned for Jetty JMX annotations and overrides the JMX metadata obtained by scanning the bean class. This allows to annotate only the custom ObjectMBean subclass and keep the bean class free of the Jetty JMX annotations.

//package com.acme;
// No Jetty JMX annotations.
class CountService
{
    private int count;

    public int getCount()
    {
        return count;
    }

    public void addCount(int value)
    {
        count += value;
    }
}

//package com.acme.jmx;
@ManagedObject("the count service")
class CountServiceMBean extends ObjectMBean
{
    public CountServiceMBean(Object service)
    {
        super(service);
    }

    private CountService getCountService()
    {
        return (CountService)super.getManagedObject();
    }

    @ManagedAttribute("the current service count")
    public int getCount()
    {
        return getCountService().getCount();
    }

    @ManagedOperation(value = "adds the given value to the service count", impact = "ACTION")
    public void addCount(@Name("count delta") int value)
    {
        getCountService().addCount(value);
    }
}

The scan for Jetty JMX annotations is performed on the bean class and all the interfaces implemented by the bean class, then on the super-class and all the interfaces implemented by the super-class and so on until java.lang.Object is reached. For each type — class or interface, the corresponding *.jmx.*MBean is looked up and scanned as well with the same algorithm. For each type, the scan looks for the class-level annotation @ManagedObject. If it is found, the scan looks for method-level @ManagedAttribute and @ManagedOperation annotations; otherwise it skips the current type and moves to the next type to scan.

@ManagedObject

The @ManagedObject annotation is used on a class at the top level to indicate that it should be exposed as an MBean. It has only one attribute to it which is used as the description of the MBean.

@ManagedAttribute

The @ManagedAttribute annotation is used to indicate that a given method is exposed as a JMX attribute. This annotation is placed always on the getter method of a given attribute. Unless the readonly attribute is set to true in the annotation, a corresponding setter is looked up following normal naming conventions. For example if this annotation is on a method called String getFoo() then a method called void setFoo(String) would be looked up, and if found wired as the setter for the JMX attribute.

@ManagedOperation

The @ManagedOperation annotation is used to indicate that a given method is exposed as a JMX operation. A JMX operation has an impact that can be INFO if the operation returns a value without modifying the object, ACTION if the operation does not return a value but modifies the object, and "ACTION_INFO" if the operation both returns a value and modifies the object. If the impact is not specified, it has the default value of UNKNOWN.

@Name

The @Name annotation is used to assign a name and description to parameters in method signatures so that when rendered by JMX consoles it is clearer what the parameter meaning is.

Jetty Listeners

The Jetty architecture is based on components, typically organized in a component tree. These components have an internal state that varies with the component life cycle (that is, whether the component is started or stopped), as well as with the component use at runtime. The typical example is a thread pool, whose internal state — such as the number of pooled threads or the job queue size — changes as the thread pool is used by the running client or server.

In many cases, the component state change produces an event that is broadcast to listeners. Applications can register listeners to these components to be notified of the events they produce.

This section lists the listeners available in the Jetty components, but the events and listener APIs are discussed in the component specific sections.

Jetty I/O Architecture

Jetty libraries (both client and server) use Java NIO to handle I/O, so that at its core Jetty I/O is completely non-blocking.

Jetty I/O: SelectorManager

The core class of Jetty I/O is SelectorManager.

SelectorManager manages internally a configurable number of ManagedSelectors. Each ManagedSelector wraps an instance of java.nio.channels.Selector that in turn manages a number of java.nio.channels.SocketChannel instances.

TODO: add image

SocketChannel instances can be created by network clients when connecting to a server and by a network server when accepting connections from network clients. In both cases the SocketChannel instance is passed to SelectorManager (which passes it to ManagedSelector and eventually to java.nio.channels.Selector) to be registered for use within Jetty.

It is possible for an application to create the SocketChannel instances outside Jetty, even perform some initial network traffic also outside Jetty (for example for authentication purposes), and then pass the SocketChannel instance to SelectorManager for use within Jetty.

This example shows how a client can connect to a server:

public void connect(SelectorManager selectorManager, Map<String, Object> context) throws IOException
{
    String host = "host";
    int port = 8080;

    // Create an unconnected SocketChannel.
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);

    // Connect and register to Jetty.
    if (socketChannel.connect(new InetSocketAddress(host, port)))
        selectorManager.accept(socketChannel, context);
    else
        selectorManager.connect(socketChannel, context);
}

This example shows how a server accepts a client connection:

public void accept(ServerSocketChannel acceptor, SelectorManager selectorManager) throws IOException
{
    // Wait until a client connects.
    SocketChannel socketChannel = acceptor.accept();
    socketChannel.configureBlocking(false);

    // Accept and register to Jetty.
    Object attachment = null;
    selectorManager.accept(socketChannel, attachment);
}

Jetty I/O: EndPoint and Connection

SocketChannels that are passed to SelectorManager are wrapped into two related components: an EndPoint and a Connection.

EndPoint is the Jetty abstraction for a SocketChannel: you can read bytes from an EndPoint via EndPoint.fill(ByteBuffer), you can write bytes to an EndPoint via EndPoint.flush(ByteBuffer…​) and EndPoint.write(Callback, ByteBuffer…​), you can close an EndPoint via EndPoint.close(), etc.

Connection is the Jetty abstraction that is responsible to read bytes from the EndPoint and to deserialize the read bytes into objects. For example, a HTTP/1.1 server-side Connection implementation is responsible to deserialize HTTP/1.1 request bytes into a HTTP request object. Conversely, a HTTP/1.1 client-side Connection implementation is responsible to deserialize HTTP/1.1 response bytes into a HTTP response object.

Connection is the abstraction that implements the reading side of a specific protocol such as HTTP/1.1, or HTTP/2, or WebSocket: it is able to read incoming communication in that protocol.

The writing side for a specific protocol may be implemented in the Connection but may also be implemented in other components, although eventually the bytes to be written will be written through the EndPoint.

While there is primarily just one implementation of EndPoint,SocketChannelEndPoint (used both on the client-side and on the server-side), there are many implementations of Connection, typically two for each protocol (one for the client-side and one for the server-side).

The EndPoint and Connection pairs can be chained, for example in case of encrypted communication using the TLS protocol. There is an EndPoint and Connection TLS pair where the EndPoint reads the encrypted bytes from the network and the Connection decrypts them; next in the chain there is an EndPoint and Connection pair where the EndPoint "reads" decrypted bytes (provided by the previous Connection) and the Connection deserializes them into specific protocol objects (for example HTTP/2 frame objects).

Certain protocols, such as WebSocket, start the communication with the server using one protocol (e.g. HTTP/1.1), but then change the communication to use another protocol (e.g. WebSocket). EndPoint supports changing the Connection object on-the-fly via EndPoint.upgrade(Connection). This allows to use the HTTP/1.1 Connection during the initial communication and later to replace it with a WebSocket Connection.

TODO: add a section on UpgradeFrom and UpgradeTo?

SelectorManager is an abstract class because while it knows how to create concrete EndPoint instances, it does not know how to create protocol specific Connection instances.

Creating Connection instances is performed on the server-side by ConnectionFactorys and on the client-side by ClientConnectionFactorys

On the server-side, the component that aggregates a SelectorManager with a set of ConnectionFactorys is ServerConnectors, see Server Libraries I/O Architecture.

On the client-side, the components that aggregates a SelectorManager with a set of ClientConnectionFactorys are HttpClientTransport subclasses, see Client Libraries I/O Architecture.

Jetty I/O: EndPoint

The Jetty I/O library use Java NIO to handle I/O, so that I/O is non-blocking.

At the Java NIO level, in order to be notified when a SocketChannel has data to be read, the SelectionKey.OP_READ flag must be set.

In the Jetty I/O library, you can call EndPoint.fillInterested(Callback) to declare interest in the "read" (or "fill") event, and the Callback parameter is the object that is notified when such event occurs.

At the Java NIO level, a SocketChannel is always writable, unless it becomes TCP congested. In order to be notified when a SocketChannel uncongests and it is therefore writable again, the SelectionKey.OP_WRITE flag must be set.

In the Jetty I/O library, you can call EndPoint.write(Callback, ByteBuffer…​) to write the ByteBuffers and the Callback parameter is the object that is notified when the whole write is finished (i.e. all ByteBuffers have been fully written, even if they are delayed by TCP congestion/uncongestion).

The EndPoint APIs abstract out the Java NIO details by providing non-blocking APIs based on Callback objects for I/O operations. The EndPoint APIs are typically called by Connection implementations, see this section.

Jetty I/O: Connection

Connection is the abstraction that deserializes incoming bytes into objects, for example a HTTP request object or a WebSocket frame object, that can be used by more abstract layers.

Connection instances have two lifecycle methods:

  • Connection.onOpen(), invoked when the Connection is associated with the EndPoint

  • Connection.onClose(Throwable), invoked when the Connection is disassociated from the EndPoint, where the Throwable parameter indicates whether the disassociation was normal (when the parameter is null) or was due to an error (when the parameter is not null)

When a Connection is first created, it is not registered for any Java NIO event. It is therefore typical to implement onOpen() to call EndPoint.fillInterested(Callback) so that the Connection declares interest for read events and it is invoked (via the Callback) when the read event happens.

Abstract class AbstractConnection partially implements Connection and provides simpler APIs. The example below shows a typical implementation that extends AbstractConnection:

// Extend AbstractConnection to inherit basic implementation.
class MyConnection extends AbstractConnection
{
    public MyConnection(EndPoint endPoint, Executor executor)
    {
        super(endPoint, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();

        // Declare interest for fill events.
        fillInterested();
    }

    @Override
    public void onFillable()
    {
        // Called when a fill event happens.
    }
}
Jetty I/O: Connection.Listener

Jetty I/O: Network Echo

With the concepts above it is now possible to write a simple, fully non-blocking, Connection implementation that simply echoes the bytes that it reads back to the other peer.

A naive, but wrong, implementation may be the following:

class WrongEchoConnection extends AbstractConnection implements Callback
{
    public WrongEchoConnection(EndPoint endPoint, Executor executor)
    {
        super(endPoint, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();

        // Declare interest for fill events.
        fillInterested();
    }

    @Override
    public void onFillable()
    {
        try
        {
            ByteBuffer buffer = BufferUtil.allocate(1024);
            int filled = getEndPoint().fill(buffer);
            if (filled > 0)
            {
                // Filled some bytes, echo them back.
                getEndPoint().write(this, buffer);
            }
            else if (filled == 0)
            {
                // No more bytes to fill, declare
                // again interest for fill events.
                fillInterested();
            }
            else
            {
                // The other peer closed the
                // connection, close it back.
                getEndPoint().close();
            }
        }
        catch (Exception x)
        {
            getEndPoint().close(x);
        }
    }

    @Override
    public void succeeded()
    {
        // The write is complete, fill again.
        onFillable();
    }

    @Override
    public void failed(Throwable x)
    {
        getEndPoint().close(x);
    }
}
The implementation above is wrong and leads to StackOverflowError.

The problem with this implementation is that if the writes always complete synchronously (i.e. without being delayed by TCP congestion), you end up with this sequence of calls:

Connection.onFillable()
  EndPoint.write()
    Callback.succeeded()
      Connection.onFillable()
        EndPoint.write()
          Callback.succeeded()
          ...

which leads to StackOverflowError.

This is a typical side effect of asynchronous programming using non-blocking APIs, and happens in the Jetty I/O library as well.

The callback is invoked synchronously for efficiency reasons. Submitting the invocation of the callback to an Executor to be invoked in a different thread would cause a context switch and make simple writes extremely inefficient.

A correct implementation is the following:

class EchoConnection extends AbstractConnection implements Callback
{
    public static final int IDLE = 0;
    public static final int WRITING = 1;
    public static final int PENDING = 2;

    private final AtomicInteger state = new AtomicInteger();

    public EchoConnection(EndPoint endp, Executor executor)
    {
        super(endp, executor);
    }

    @Override
    public void onOpen()
    {
        super.onOpen();

        // Declare interest for fill events.
        fillInterested();
    }

    @Override
    public void onFillable()
    {
        try
        {
            ByteBuffer buffer = BufferUtil.allocate(1024);
            while (true)
            {
                int filled = getEndPoint().fill(buffer);
                if (filled > 0)
                {
                    // We have filled some bytes, echo them back.
                    if (write(buffer))
                    {
                        // If the write completed, continue to fill.
                        continue;
                    }
                    else
                    {
                        // The write is pending, return to wait for completion.
                        return;
                    }
                }
                else if (filled == 0)
                {
                    // No more bytes to read, declare
                    // again interest for fill events.
                    fillInterested();
                    return;
                }
                else
                {
                    // The other peer closed the connection.
                    close();
                    return;
                }
            }
        }
        catch (Throwable x)
        {
            getEndPoint().close(x);
        }
    }

    private boolean write(ByteBuffer buffer)
    {
        // Check if we are writing concurrently.
        if (!state.compareAndSet(IDLE, WRITING))
            throw new WritePendingException();

        // Write the buffer using "this" as a callback.
        getEndPoint().write(this, buffer);

        // Check if the write is already completed.
        boolean writeIsPending = state.compareAndSet(WRITING, PENDING);

        // Return true if the write was completed.
        return !writeIsPending;
    }

    @Override
    public void succeeded()
    {
        // The write is complete, reset the state.
        int prevState = state.getAndSet(IDLE);

        // If the write was pending we need
        // to resume reading from the network.
        if (prevState == PENDING)
            onFillable();
    }

    @Override
    public void failed(Throwable x)
    {
        getEndPoint().close(x);
    }
}

The correct implementation performs consecutive reads in a loop (rather than recursively), but only if the correspondent write is completed successfully.

In order to detect whether the write is completed, a concurrent state machine is used. This is necessary because the notification of the completion of the write may happen in a different thread, while the original writing thread may still be changing the state.

The original writing thread starts moves the state from IDLE to WRITING, then issues the actual write() call. The original writing thread then assumes that the write() did not complete and tries to move to the PENDING state just after the write(). If it fails to move from the WRITING state to the PENDING state, it means that the write was completed. Otherwise, the write is now PENDING and waiting for the callback to be notified of the completion at a later time. When the callback is notified of the write() completion, it checks whether the write() was PENDING, and if it was it resumes reading.

TODO: Introduce IteratingCallback?

Appendix B: Troubleshooting Jetty

TODO: introduction

Logging

The Jetty libraries (both client and server) use SLF4J as logging APIs. You can therefore plug in any SLF4J logging implementation, and configure the logging category org.eclipse.jetty at the desired level.

When you have problems with Jetty, the first thing that you want to do is to enable DEBUG logging. This is helpful because by reading the DEBUG logs you get a better understanding of what is going on in the system (and that alone may give you the answers you need to fix the problem), and because Jetty developers will probably need the DEBUG logs to help you.

Jetty SLF4J Binding

The Jetty artifact jetty-slf4j-impl is a SLF4J binding, that is the Jetty implementation of the SLF4J APIs, and provides a number of easy-to-use features to configure logging.

The Jetty SLF4J binding only provides an appender that writes to System.err. For more advanced configurations (for example, logging to a file), use LogBack, or Log4J2, or your preferred SLF4J binding.

Only one binding can be present in the class-path or module-path. If you use the LogBack SLF4J binding or the Log4J2 SLF4J binding, remember to remove the Jetty SLF4J binding.

The Jetty SLF4J binding reads a file in the class-path (or module-path) called jetty-logging.properties that can be configured with the logging levels for various logger categories:

jetty-logging.properties
# By default, log at INFO level all Jetty classes.
org.eclipse.jetty.LEVEL=INFO

# However, the Jetty client classes are logged at DEBUG level.
org.eclipse.jetty.client.LEVEL=DEBUG

Similarly to how you configure the jetty-logging.properties file, you can set the system property org.eclipse.jetty[.<package_names>].LEVEL=DEBUG to quickly change the logging level to DEBUG without editing any file. The system property can be set on the command line, or in your IDE when you run your tests or your Jetty-based application and will override the jetty-logging.properties file configuration. For example to enable DEBUG logging for all the Jetty classes (very verbose):

java -Dorg.eclipse.jetty.LEVEL=DEBUG --class-path ...

If you want to enable DEBUG logging but only for the HTTP/2 classes:

java -Dorg.eclipse.jetty.http2.LEVEL=DEBUG --class-path ...

JVM Thread Dump

TODO

Jetty Component Tree Dump

Jetty components are organized in a component tree.

At the root of the component tree there is typically a ContainerLifeCycle instance — typically a Server instance on the server and an HttpClient instance on the client.

ContainerLifeCycle has built-in dump APIs that can be invoked either directly or via JMX.

You can get more details from a Jetty’s QueuedThreadPool dump by enabling detailed dumps via queuedThreadPool.setDetailedDump(true).

Debugging

Sometimes, in order to figure out a problem, enabling DEBUG logging is not enough and you really need to debug the code with a debugger.

Debugging an embedded Jetty application is most easily done from your preferred IDE, so refer to your IDE instruction for how to debug Java applications.

Remote debugging can be enabled in a Jetty application via command line options:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000 --class-path ...

The example above enables remote debugging so that debuggers (for example, your preferred IDE) can connect to port 8000 on the host running the Jetty application to receive debugging events.

More technically, remote debugging exchanges JVM Tools Interface (JVMTI) events and commands via the Java Debug Wire Protocol (JDWP).