OkHttp utilizes blocking I/O operations and implements TCP connection reuse through connection caching mechanisms.
Core Interceptor Architecture
The framework operates through an interceptor-based architecture where requests pass through a chain of interceptors. Each interceptor performs specific processing before passing control to the next component in the chain.
Response executeRequestWithInterceptorChain() throws IOException {
// Construct complete interceptor sequence
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(httpClient.interceptors());
interceptors.add(retryAndFollowUpHandler);
interceptors.add(new ConnectionBridge(httpClient.cookieManager()));
interceptors.add(new ResponseCacheHandler(httpClient.internalCache()));
interceptors.add(new ConnectionManager(httpClient));
if (!websocketMode) {
interceptors.addAll(httpClient.networkInterceptors());
}
interceptors.add(new ServerCommunicationInterceptor(websocketMode));
Interceptor.Chain processingChain = new RealInterceptorChain(interceptors, null, null, null, 0,
initialRequest, this, eventListener, httpClient.connectionTimeout(),
httpClient.readTimeout(), httpClient.writeTimeout());
return processingChain.proceed(initialRequest);
}
Caching Mechanisms
Response Caching
The library caches HTTP responses that are marked as cacheable by the server. When a request includes cache-permitting headers, the response can be stored locally.
Subsequent requests that return 304 (Not Modified) responses without content payload can be served directly from the cache.
This implementation closely follows HTTP protocol specifications, though detailed protocol analysis is beyond the current scope.
Connection Pooling
During request processing, the system identifies and reuses existing connections based on target addresses, preventing redundant connection establishment.
The connection pool maintains up to 5 idle connections by default, with a 300-second timeout for idle connections before they're recycled.
As long as connections remain active, the pool can theoretically store an unlimited number of connections. When no cached connections are available, new connections are created indefinitely.
Synchronous vs Asynchronous Operations
HTTP requests are executed through the executeRequestWithInterceptorChain method.
In synchronous mode, after submission to the thread pool, the calling thread blocks until the result is available.
public Response performRequest() throws IOException {
synchronized (this) {
if (requestExecuted) throw new IllegalStateException("Request Already Processed");
requestExecuted = true;
}
captureExecutionTrace();
eventListener.requestStarted(this);
try {
httpClient.dispatcher().markAsExecuting(this);
// Direct blocking until completion
Response result = executeRequestWithInterceptorChain();
if (result == null) throw new IOException("Operation Canceled");
return result;
} catch (IOException e) {
eventListener.requestFailed(this, e);
throw e;
} finally {
httpClient.dispatcher().markAsCompleted(this);
}
}
Asynchronous operations submit tasks to the thread pool and invoke callback functions once results are available.
The implementation uses a thread pool with zero core threads and an unbounded queue, ensuring at least one worker thread remains active.
// First, enqueue the request
public void submitRequest(Callback responseHandler) {
synchronized (this) {
if (requestExecuted) throw new IllegalStateException("Request Already Processed");
requestExecuted = true;
}
captureExecutionTrace();
eventListener.requestStarted(this);
httpClient.dispatcher().queueRequest(new AsyncRequestHandler(responseHandler));
}
// Process the request when thread pool resources become available
protected void processRequest() {
boolean callbackSignaled = false;
try {
Response response = executeRequestWithInterceptorChain();
if (retryAndFollowUpHandler.isCanceled()) {
callbackSignaled = true;
responseHandler.onFailure(RealRequest.this, new IOException("Operation Canceled"));
} else {
callbackSignaled = true;
responseHandler.onResponse(RealRequest.this, response);
}
} catch (IOException e) {
if (callbackSignaled) {
// Prevent duplicate callback invocation
Platform.get().log(INFO, "Callback error for " + toLoggableString(), e);
} else {
eventListener.requestFailed(RealRequest.this, e);
responseHandler.onFailure(RealRequest.this, e);
}
} finally {
httpClient.dispatcher().markAsCompleted(this);
}
}
Response Matching and HTTP Protocol Handling
Network communication occurs through the ServerCommunicationInterceptor.
Normal network responses don't inherently iedntify which request they correspond to. OkHttp ensures proper response matching through several mechanisms:
- Connection references are tracked with reference counting during connection reuse. Only connections without active references can be acquired, preventing simultaneous HTTP requests over the same connection.
- HTTP protocol parsing: responses are processed by first parsing headers (terminated by newline characters), then parsing the body based on the content-length specified in headers, ensuring correct response association.
Implementation Examples
Synchronous GET Request
OkHttpClient httpClient = new OkHttpClient();
String fetchContent(String targetUrl) throws IOException {
Request httpRequest = new Request.Builder()
.url(targetUrl)
.build();
try (Response httpResponse = httpClient.newCall(httpRequest).execute()) {
return httpResponse.body().string();
}
}
public static void main(String[] args) throws IOException {
GetRequestExample example = new GetRequestExample();
String content = example.fetchContent("https://raw.github.com/square/okhttp/master/README.md");
System.out.println(content);
}
Synchronous POST Request
public static final MediaType JSON_MEDIA
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient httpClient = new OkHttpClient();
String submitData(String targetUrl, String jsonData) throws IOException {
RequestBody payload = RequestBody.create(JSON_MEDIA, jsonData);
Request httpRequest = new Request.Builder()
.url(targetUrl)
.post(payload)
.build();
try (Response httpResponse = httpClient.newCall(httpRequest).execute()) {
return httpResponse.body().string();
}
}
String generateBowlingData(String player1, String player2) {
return "{'winCondition':'HIGH_SCORE',"
+ "'gameName':'Bowling',"
+ "'currentRound':4,"
+ "'lastSaved':1367702411696,"
+ "'startDate':1367702378785,"
+ "'participants':["
+ "{'name':'" + player1 + "','scores':[10,8,6,7,8],'color':-13388315,'total':39},"
+ "{'name':'" + player2 + "','scores':[6,10,5,10,10],'color':-48060,'total':41}"
+ "]}";
}
public static void main(String[] args) throws IOException {
PostRequestExample example = new PostRequestExample();
String jsonData = example.generateBowlingData("Jesse", "Jake");
String response = example.submitData("http://www.roundsapp.com/post", jsonData);
System.out.println(response);
}
Asynchronous GET Request
OkHttpClient httpClient = new OkHttpClient();
void performAsyncRequest(String targetUrl, Callback responseHandler) {
Request httpRequest = new Request.Builder()
.url(targetUrl)
.build();
httpClient.newCall(httpRequest).enqueue(responseHandler);
}
public static void main(String[] args) {
AsyncRequestExample example = new AsyncRequestExample();
example.performAsyncRequest("https://raw.github.com/square/okhttp/master/README.md", new Callback() {
@Override
public void onFailure(Call call, IOException e) {
System.err.println("Request failed: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody body = response.body()) {
System.out.println(body.string());
}
}
});
}
Note:
When multiple concurrent requests compete for connection pool resources, the system will create new connections indefinitely if none are available in the pool.
public ConnectionPool() {
// 5 represents maximum idle connections - connections without active references
// If idle connections exceed 5, they'll be recycled
// 6 represents 6-minute timeout for idle connections, independent of the 5-connection limit
this(5, 6, TimeUnit.MINUTES);
}