1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.squareup.okhttp.internal.http; 18 19import com.squareup.okhttp.Request; 20import com.squareup.okhttp.Response; 21import java.io.IOException; 22import java.net.CacheRequest; 23import okio.Sink; 24import okio.Source; 25 26public final class HttpTransport implements Transport { 27 private final HttpEngine httpEngine; 28 private final HttpConnection httpConnection; 29 30 public HttpTransport(HttpEngine httpEngine, HttpConnection httpConnection) { 31 this.httpEngine = httpEngine; 32 this.httpConnection = httpConnection; 33 } 34 35 @Override public Sink createRequestBody(Request request) throws IOException { 36 long contentLength = OkHeaders.contentLength(request); 37 38 if (httpEngine.bufferRequestBody) { 39 if (contentLength > Integer.MAX_VALUE) { 40 throw new IllegalStateException("Use setFixedLengthStreamingMode() or " 41 + "setChunkedStreamingMode() for requests larger than 2 GiB."); 42 } 43 44 if (contentLength != -1) { 45 // Buffer a request body of a known length. 46 writeRequestHeaders(request); 47 return new RetryableSink((int) contentLength); 48 } else { 49 // Buffer a request body of an unknown length. Don't write request 50 // headers until the entire body is ready; otherwise we can't set the 51 // Content-Length header correctly. 52 return new RetryableSink(); 53 } 54 } 55 56 if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) { 57 // Stream a request body of unknown length. 58 writeRequestHeaders(request); 59 return httpConnection.newChunkedSink(); 60 } 61 62 if (contentLength != -1) { 63 // Stream a request body of a known length. 64 writeRequestHeaders(request); 65 return httpConnection.newFixedLengthSink(contentLength); 66 } 67 68 throw new IllegalStateException( 69 "Cannot stream a request body without chunked encoding or a known content length!"); 70 } 71 72 @Override public void flushRequest() throws IOException { 73 httpConnection.flush(); 74 } 75 76 @Override public void writeRequestBody(RetryableSink requestBody) throws IOException { 77 httpConnection.writeRequestBody(requestBody); 78 } 79 80 /** 81 * Prepares the HTTP headers and sends them to the server. 82 * 83 * <p>For streaming requests with a body, headers must be prepared 84 * <strong>before</strong> the output stream has been written to. Otherwise 85 * the body would need to be buffered! 86 * 87 * <p>For non-streaming requests with a body, headers must be prepared 88 * <strong>after</strong> the output stream has been written to and closed. 89 * This ensures that the {@code Content-Length} header field receives the 90 * proper value. 91 */ 92 public void writeRequestHeaders(Request request) throws IOException { 93 httpEngine.writingRequestHeaders(); 94 String requestLine = RequestLine.get(request, 95 httpEngine.getConnection().getRoute().getProxy().type(), 96 httpEngine.getConnection().getHttpMinorVersion()); 97 httpConnection.writeRequest(request.getHeaders(), requestLine); 98 } 99 100 @Override public Response.Builder readResponseHeaders() throws IOException { 101 return httpConnection.readResponse(); 102 } 103 104 @Override public void releaseConnectionOnIdle() throws IOException { 105 if (canReuseConnection()) { 106 httpConnection.poolOnIdle(); 107 } else { 108 httpConnection.closeOnIdle(); 109 } 110 } 111 112 @Override public boolean canReuseConnection() { 113 // If the request specified that the connection shouldn't be reused, don't reuse it. 114 if ("close".equalsIgnoreCase(httpEngine.getRequest().header("Connection"))) { 115 return false; 116 } 117 118 // If the response specified that the connection shouldn't be reused, don't reuse it. 119 if ("close".equalsIgnoreCase(httpEngine.getResponse().header("Connection"))) { 120 return false; 121 } 122 123 if (httpConnection.isClosed()) { 124 return false; 125 } 126 127 return true; 128 } 129 130 @Override public void emptyTransferStream() throws IOException { 131 httpConnection.emptyResponseBody(); 132 } 133 134 @Override public Source getTransferStream(CacheRequest cacheRequest) throws IOException { 135 if (!httpEngine.hasResponseBody()) { 136 return httpConnection.newFixedLengthSource(cacheRequest, 0); 137 } 138 139 if ("chunked".equalsIgnoreCase(httpEngine.getResponse().header("Transfer-Encoding"))) { 140 return httpConnection.newChunkedSource(cacheRequest, httpEngine); 141 } 142 143 long contentLength = OkHeaders.contentLength(httpEngine.getResponse()); 144 if (contentLength != -1) { 145 return httpConnection.newFixedLengthSource(cacheRequest, contentLength); 146 } 147 148 // Wrap the input stream from the connection (rather than just returning 149 // "socketIn" directly here), so that we can control its use after the 150 // reference escapes. 151 return httpConnection.newUnknownLengthSource(cacheRequest); 152 } 153 154 @Override public void disconnect(HttpEngine engine) throws IOException { 155 httpConnection.closeIfOwnedBy(engine); 156 } 157} 158