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 Response
s, 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:
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 HttpClientTransport
s 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 . |