351 lines
11 KiB
Java
351 lines
11 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
|
/*
|
||
|
* 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.android.okhttp;
|
||
|
|
||
|
import com.android.okhttp.internal.NamedRunnable;
|
||
|
import com.android.okhttp.internal.http.HttpEngine;
|
||
|
import com.android.okhttp.internal.http.RequestException;
|
||
|
import com.android.okhttp.internal.http.RouteException;
|
||
|
import com.android.okhttp.internal.http.StreamAllocation;
|
||
|
import java.io.IOException;
|
||
|
import java.net.ProtocolException;
|
||
|
import java.util.logging.Level;
|
||
|
|
||
|
import static com.android.okhttp.internal.Internal.logger;
|
||
|
import static com.android.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.
|
||
|
* @hide This class is not part of the Android public SDK API
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|