Java tutorial
/* * Copyright (C) 2014 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.okhttp; import com.squareup.okhttp.internal.NamedRunnable; import com.squareup.okhttp.internal.http.HttpEngine; import com.squareup.okhttp.internal.http.RequestException; import com.squareup.okhttp.internal.http.RouteException; import com.squareup.okhttp.internal.http.StreamAllocation; import java.io.IOException; import java.net.ProtocolException; import java.util.logging.Level; import static com.squareup.okhttp.internal.Internal.logger; import static com.squareup.okhttp.internal.http.HttpEngine.MAX_FOLLOW_UPS; /** * A call is a request that has been prepared for execution. A call can be * canceled. As this object represents a single request/response pair (stream), * it cannot be executed twice. */ public class Call { private final OkHttpClient client; // Guarded by this. private boolean executed; volatile boolean canceled; /** The application's original request unadulterated by redirects or auth headers. */ Request originalRequest; HttpEngine engine; protected Call(OkHttpClient client, Request originalRequest) { // Copy the client. Otherwise changes (socket factory, redirect policy, // etc.) may incorrectly be reflected in the request when it is executed. this.client = client.copyWithDefaults(); this.originalRequest = originalRequest; } /** * Invokes the request immediately, and blocks until the response can be * processed or is in error. * * <p>The caller may read the response body with the response's * {@link Response#body} method. To facilitate connection recycling, callers * should always {@link ResponseBody#close() close the response body}. * * <p>Note that transport-layer success (receiving a HTTP response code, * headers and body) does not necessarily indicate application-layer success: * {@code response} may still indicate an unhappy HTTP response code like 404 * or 500. * * @throws IOException if the request could not be executed due to * cancellation, a connectivity problem or timeout. Because networks can * fail during an exchange, it is possible that the remote server * accepted the request before the failure. * * @throws IllegalStateException when the call has already been executed. */ public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } try { client.getDispatcher().executed(this); Response result = getResponseWithInterceptorChain(false); if (result == null) throw new IOException("Canceled"); return result; } finally { client.getDispatcher().finished(this); } } Object tag() { return originalRequest.tag(); } /** * Schedules the request to be executed at some point in the future. * * <p>The {@link OkHttpClient#getDispatcher dispatcher} defines when the * request will run: usually immediately unless there are several other * requests currently being executed. * * <p>This client will later call back {@code responseCallback} with either * an HTTP response or a failure exception. If you {@link #cancel} a request * before it completes the callback will not be invoked. * * @throws IllegalStateException when the call has already been executed. */ public void enqueue(Callback responseCallback) { enqueue(responseCallback, false); } void enqueue(Callback responseCallback, boolean forWebSocket) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); } /** * Cancels the request, if possible. Requests that are already complete * cannot be canceled. */ public void cancel() { canceled = true; if (engine != null) engine.cancel(); } /** * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain * #enqueue(Callback) enqueued}. It is an error to execute a call more than once. */ public synchronized boolean isExecuted() { return executed; } public boolean isCanceled() { return canceled; } final class AsyncCall extends NamedRunnable { private final Callback responseCallback; private final boolean forWebSocket; private AsyncCall(Callback responseCallback, boolean forWebSocket) { super("OkHttp %s", originalRequest.urlString()); this.responseCallback = responseCallback; this.forWebSocket = forWebSocket; } String host() { return originalRequest.httpUrl().host(); } Request request() { return originalRequest; } Object tag() { return originalRequest.tag(); } void cancel() { Call.this.cancel(); } Call get() { return Call.this; } @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(forWebSocket); if (canceled) { signalledCallback = true; responseCallback.onFailure(originalRequest, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e); } else { Request request = engine == null ? originalRequest : engine.getRequest(); responseCallback.onFailure(request, e); } } finally { client.getDispatcher().finished(this); } } } /** * Returns a string that describes this call. Doesn't include a full URL as that might contain * sensitive information. */ private String toLoggableString() { String string = canceled ? "canceled call" : "call"; HttpUrl redactedUrl = originalRequest.httpUrl().resolve("/..."); return string + " to " + redactedUrl; } private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException { Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket); return chain.proceed(originalRequest); } class ApplicationInterceptorChain implements Interceptor.Chain { private final int index; private final Request request; private final boolean forWebSocket; ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) { this.index = index; this.request = request; this.forWebSocket = forWebSocket; } @Override public Connection connection() { return null; } @Override public Request request() { return request; } @Override public Response proceed(Request request) throws IOException { // If there's another interceptor in the chain, call that. if (index < client.interceptors().size()) { Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket); Interceptor interceptor = client.interceptors().get(index); Response interceptedResponse = interceptor.intercept(chain); if (interceptedResponse == null) { throw new NullPointerException("application interceptor " + interceptor + " returned null"); } return interceptedResponse; } // No more interceptors. Do HTTP. return getResponse(request, forWebSocket); } } /** * Performs the request and returns the response. May return null if this * call was canceled. */ Response getResponse(Request request, boolean forWebSocket) throws IOException { // Copy body metadata to the appropriate request headers. RequestBody body = request.body(); if (body != null) { Request.Builder requestBuilder = request.newBuilder(); MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } request = requestBuilder.build(); } // Create the initial HTTP engine. Retries and redirects need new engine for each attempt. engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null); int followUpCount = 0; while (true) { if (canceled) { engine.releaseStreamAllocation(); throw new IOException("Canceled"); } boolean releaseConnection = true; try { engine.sendRequest(); engine.readResponse(); releaseConnection = false; } catch (RequestException e) { // The attempt to interpret the request failed. Give up. throw e.getCause(); } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. HttpEngine retryEngine = engine.recover(e); if (retryEngine != null) { releaseConnection = false; engine = retryEngine; continue; } // Give up; recovery is not possible. throw e.getLastConnectException(); } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. HttpEngine retryEngine = engine.recover(e, null); if (retryEngine != null) { releaseConnection = false; engine = retryEngine; continue; } // Give up; recovery is not possible. throw e; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) { StreamAllocation streamAllocation = engine.close(); streamAllocation.release(); } } Response response = engine.getResponse(); Request followUp = engine.followUpRequest(); if (followUp == null) { if (!forWebSocket) { engine.releaseStreamAllocation(); } return response; } StreamAllocation streamAllocation = engine.close(); if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } if (!engine.sameConnection(followUp.httpUrl())) { streamAllocation.release(); streamAllocation = null; } request = followUp; engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null, response); } } }