Client Threading Architecture

The Jetty HTTP client libraries are non-blocking and event-based. Client applications provide listeners that are notified of request and response events, and the listeners implement the client application logic.

In general, the thread that sends the Request is the thread that emits the request events. Writing blocking code in a request listener blocks the sending of the Request.

The processing of Responses, on the other hand, follows the general Jetty I/O architecture and Jetty threading architecture, and it is detailed in this section.

DNS Lookup Threading

In order to send a request, a connection needs to be established, and this involves a query to the DNS to convert the host name to an IP address (this is necessary only for network transports, see here).

The Java platform only offers blocking APIs to query the DNS, via InetAddress.getAllByName(String). While DNS queries are typically fast (few milliseconds), in unfortunate cases they may last several seconds, blocking the thread that sends the request.

To avoid blocking the request sender thread, HttpClient is configured by default with SocketAddressResolver.Async, that executes the DNS query in a different thread.

If you want to avoid this thread hand-off, especially when the request host is localhost or resolved locally (for example configured in /etc/hosts), you can configure HttpClient with SocketAddressResolver.Sync that will perform the DNS query in the request sender thread.

Response Processing Threading

As explained in the general Jetty threading architecture, client applications can specify the InvocationType of the Jetty implementation tasks that invoke client application code in Response listeners.

There are two main Jetty implementation tasks that invoke client application code in Response listeners:

  • The task that invokes the Response listeners, detailed here.

  • The task that invokes the response’s Content.Source demand callback, detailed here.

Response Listener InvocationType

The InvocationType of the task that invokes the Response listeners is configured in the HttpClientTransport passed to HttpClient:

HttpClient httpClient = new HttpClient();

// Configure the InvocationType for the response listeners.
httpClient.getHttpClientTransport().setInvocationType(Invocable.InvocationType.NON_BLOCKING);

httpClient.start();

The default InvocationType for all HttpClientTransports is BLOCKING.

Response Demand InvocationType

The InvocationType of the task that invokes the Response Content.Source demand callback is determined from the demand callback object passed to Content.Source.demand(Runnable), and defines the InvocationType with which the demand callback is invoked.

Rather than passing a simple Runnable or a lambda expression (for which the InvocationType is assumed to be BLOCKING), you can pass an Invocable.Task, that implements both Invocable (to specify the InvocationType) and Runnable (to specify the demand callback).

For example:

HttpClient httpClient = new HttpClient();
httpClient.getHttpClientTransport().setInvocationType(Invocable.InvocationType.NON_BLOCKING); (1)
httpClient.start();

httpClient.newRequest("http://domain.com/path")
    .onResponseContentSource((response, contentSource) ->
    {
        contentSource.demand(new Invocable.Task.Abstract(Invocable.InvocationType.NON_BLOCKING) (2)
        {
            @Override
            public void run()
            {
                while (true)
                {
                    Content.Chunk chunk = contentSource.read();

                    if (chunk == null)
                    {
                        contentSource.demand(this); (2)
                        return;
                    }

                    if (Content.Chunk.isFailure(chunk))
                    {
                        response.abort(chunk.getFailure());
                        return;
                    }

                    // Process the Chunk in non-blocking way.
                    processNonBlocking(chunk);

                    chunk.release();

                    if (chunk.isLast())
                        return;
                }
            }
        });
    }).send(null);
1 Specifies the InvocationType for response listener processing.
2 Specifies the demand callback to be an Invocable.Task with InvocationType NON_BLOCKING.