/* * Copyright (C) 2012 The Android Open Source Project * * 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.internal.http; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import java.io.IOException; import java.net.CacheRequest; import okio.Sink; import okio.Source; public final class HttpTransport implements Transport { private final HttpEngine httpEngine; private final HttpConnection httpConnection; public HttpTransport(HttpEngine httpEngine, HttpConnection httpConnection) { this.httpEngine = httpEngine; this.httpConnection = httpConnection; } @Override public Sink createRequestBody(Request request) throws IOException { long contentLength = OkHeaders.contentLength(request); if (httpEngine.bufferRequestBody) { if (contentLength > Integer.MAX_VALUE) { throw new IllegalStateException("Use setFixedLengthStreamingMode() or " + "setChunkedStreamingMode() for requests larger than 2 GiB."); } if (contentLength != -1) { // Buffer a request body of a known length. writeRequestHeaders(request); return new RetryableSink((int) contentLength); } else { // Buffer a request body of an unknown length. Don't write request // headers until the entire body is ready; otherwise we can't set the // Content-Length header correctly. return new RetryableSink(); } } if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) { // Stream a request body of unknown length. writeRequestHeaders(request); return httpConnection.newChunkedSink(); } if (contentLength != -1) { // Stream a request body of a known length. writeRequestHeaders(request); return httpConnection.newFixedLengthSink(contentLength); } throw new IllegalStateException( "Cannot stream a request body without chunked encoding or a known content length!"); } @Override public void flushRequest() throws IOException { httpConnection.flush(); } @Override public void writeRequestBody(RetryableSink requestBody) throws IOException { httpConnection.writeRequestBody(requestBody); } /** * Prepares the HTTP headers and sends them to the server. * *

For streaming requests with a body, headers must be prepared * before the output stream has been written to. Otherwise * the body would need to be buffered! * *

For non-streaming requests with a body, headers must be prepared * after the output stream has been written to and closed. * This ensures that the {@code Content-Length} header field receives the * proper value. */ public void writeRequestHeaders(Request request) throws IOException { httpEngine.writingRequestHeaders(); String requestLine = RequestLine.get(request, httpEngine.getConnection().getRoute().getProxy().type(), httpEngine.getConnection().getHttpMinorVersion()); httpConnection.writeRequest(request.getHeaders(), requestLine); } @Override public Response.Builder readResponseHeaders() throws IOException { return httpConnection.readResponse(); } @Override public void releaseConnectionOnIdle() throws IOException { if (canReuseConnection()) { httpConnection.poolOnIdle(); } else { httpConnection.closeOnIdle(); } } @Override public boolean canReuseConnection() { // If the request specified that the connection shouldn't be reused, don't reuse it. if ("close".equalsIgnoreCase(httpEngine.getRequest().header("Connection"))) { return false; } // If the response specified that the connection shouldn't be reused, don't reuse it. if ("close".equalsIgnoreCase(httpEngine.getResponse().header("Connection"))) { return false; } if (httpConnection.isClosed()) { return false; } return true; } @Override public void emptyTransferStream() throws IOException { httpConnection.emptyResponseBody(); } @Override public Source getTransferStream(CacheRequest cacheRequest) throws IOException { if (!httpEngine.hasResponseBody()) { return httpConnection.newFixedLengthSource(cacheRequest, 0); } if ("chunked".equalsIgnoreCase(httpEngine.getResponse().header("Transfer-Encoding"))) { return httpConnection.newChunkedSource(cacheRequest, httpEngine); } long contentLength = OkHeaders.contentLength(httpEngine.getResponse()); if (contentLength != -1) { return httpConnection.newFixedLengthSource(cacheRequest, contentLength); } // Wrap the input stream from the connection (rather than just returning // "socketIn" directly here), so that we can control its use after the // reference escapes. return httpConnection.newUnknownLengthSource(cacheRequest); } @Override public void disconnect(HttpEngine engine) throws IOException { httpConnection.closeIfOwnedBy(engine); } }