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