/** * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself * immutable. */ publicfinalclassRequest{ final HttpUrl url; final String method; final Headers headers; final@Nullable RequestBody body; final Object tag;
Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tag = builder.tag != null ? builder.tag : this; } /** * Attaches {@code tag} to the request. It can be used later to cancel the request. If the tag * is unspecified or null, the request is canceled by using the request itself as the tag. */ public Builder tag(Object tag){ this.tag = tag; returnthis; }
public Request build(){ if (url == null) thrownew IllegalStateException("url == null"); returnnew Request(this); } } }
/** * Prepares the {@code request} to be executed at some point in the future. */ @Overridepublic Call newCall(Request request){ return RealCall.newRealCall(this, request, false/* for web socket */); }
newCall方法内部调用RealCall.newRealCall方法并返回Call对象:
1 2 3 4 5 6
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket){ // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket);//(1) call.eventListener = client.eventListenerFactory().create(call);//(2) return call; }
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection)throws IOException { if (index >= interceptors.size()) thrownew AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it. if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { thrownew IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must retain the same host and port"); }
// If we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { thrownew IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must call proceed() exactly once"); }
// Call the next interceptor in the chain. RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next);//(1)
// Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { thrownew IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); }
// Confirm that the intercepted response isn't null. if (response == null) { thrownew NullPointerException("interceptor " + interceptor + " returned null"); }
if (response.body() == null) { thrownew IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); }
int followUpCount = 0; Response priorResponse = null; while (true) { if (canceled) { streamAllocation.release();//(2) thrownew IOException("Canceled"); }
Response response; boolean releaseConnection = true; try { response = realChain.proceed(request, streamAllocation, null, null);//(3) releaseConnection = false; } catch (RouteException e) { // The attempt to connect via a route failed. The request will not have been sent. if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getLastConnectException();//(4) } releaseConnection = false; continue; } catch (IOException e) { // An attempt to communicate with a server failed. The request may have been sent. boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false;//(5) continue; } finally { // We're throwing an unchecked exception. Release any resources. if (releaseConnection) {//(6) streamAllocation.streamFailed(null); streamAllocation.release(); } }
// Attach the prior response if it exists. Such responses never have a body. if (priorResponse != null) {//(7) response = response.newBuilder() .priorResponse(priorResponse.newBuilder() .body(null) .build()) .build(); }
if (!sameConnection(response, followUp.url())) {//(13) streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } elseif (streamAllocation.codec() != null) {//(14) thrownew IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); }
RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString());//(1) }
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"); } }
if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); }
if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); }
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); }
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); }
if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); }
if (cache != null) { cache.trackResponse(strategy); }
if (cacheCandidate != null && cacheResponse == null) {//(2) closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. }
// If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) {//(3) returnnew Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); }
// If we don't need the network, we're done. if (networkRequest == null) {//(4) return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); }
Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body());//(5) } }
// If we have a cache response too, then we're doing a conditional get. if (cacheResponse != null) {//(6) if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close();
// Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } }
// We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); RealConnection connection = streamAllocation.connection();//(1)
Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100 // Continue" response before transmitting the request body. If we don't get that, return // what we did get (such as a 4xx response) without ever transmitting the request body. if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); }
if (responseBuilder == null) { // Write the request body if the "Expect: 100-continue" expectation was met. realChain.eventListener().requestBodyStart(realChain.call()); long contentLength = request.body().contentLength(); CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, contentLength)); BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);//(2) bufferedRequestBody.close(); realChain.eventListener() .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount); } elseif (!connection.isMultiplexed()) { // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection // from being reused. Otherwise we're still obligated to transmit the request body to // leave the connection in a consistent state. streamAllocation.noNewStreams(); } }
httpCodec.finishRequest();
if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false);//(3) }
int code = response.code(); if (code == 100) { // server sent a 100-continue even though we did not request one. // try again to read the actual response responseBuilder = httpCodec.readResponseHeaders(false);
//(4) if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); }
if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); }