blob: 64908e09012810b604be967ce61a0165136bda76 [file] [log] [blame]
/* GENERATED SOURCE. DO NOT MODIFY. */
/*
* 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.android.okhttp.internal.http;
import com.android.okhttp.Headers;
import com.android.okhttp.Protocol;
import com.android.okhttp.Request;
import com.android.okhttp.Response;
import com.android.okhttp.ResponseBody;
import com.android.okhttp.internal.Util;
import com.android.okhttp.internal.framed.ErrorCode;
import com.android.okhttp.internal.framed.FramedConnection;
import com.android.okhttp.internal.framed.FramedStream;
import com.android.okhttp.internal.framed.Header;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.android.okhttp.okio.ByteString;
import com.android.okhttp.okio.ForwardingSource;
import com.android.okhttp.okio.Okio;
import com.android.okhttp.okio.Sink;
import com.android.okhttp.okio.Source;
import static com.android.okhttp.internal.framed.Header.RESPONSE_STATUS;
import static com.android.okhttp.internal.framed.Header.TARGET_AUTHORITY;
import static com.android.okhttp.internal.framed.Header.TARGET_HOST;
import static com.android.okhttp.internal.framed.Header.TARGET_METHOD;
import static com.android.okhttp.internal.framed.Header.TARGET_PATH;
import static com.android.okhttp.internal.framed.Header.TARGET_SCHEME;
import static com.android.okhttp.internal.framed.Header.VERSION;
/** An HTTP stream for HTTP/2 and SPDY.
* @hide This class is not part of the Android public SDK API*/
public final class Http2xStream implements HttpStream {
private static final ByteString CONNECTION = ByteString.encodeUtf8("connection");
private static final ByteString HOST = ByteString.encodeUtf8("host");
private static final ByteString KEEP_ALIVE = ByteString.encodeUtf8("keep-alive");
private static final ByteString PROXY_CONNECTION = ByteString.encodeUtf8("proxy-connection");
private static final ByteString TRANSFER_ENCODING = ByteString.encodeUtf8("transfer-encoding");
private static final ByteString TE = ByteString.encodeUtf8("te");
private static final ByteString ENCODING = ByteString.encodeUtf8("encoding");
private static final ByteString UPGRADE = ByteString.encodeUtf8("upgrade");
/** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */
private static final List<ByteString> SPDY_3_SKIPPED_REQUEST_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TRANSFER_ENCODING,
TARGET_METHOD,
TARGET_PATH,
TARGET_SCHEME,
TARGET_AUTHORITY,
TARGET_HOST,
VERSION);
private static final List<ByteString> SPDY_3_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TRANSFER_ENCODING);
/** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
private static final List<ByteString> HTTP_2_SKIPPED_REQUEST_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TE,
TRANSFER_ENCODING,
ENCODING,
UPGRADE,
TARGET_METHOD,
TARGET_PATH,
TARGET_SCHEME,
TARGET_AUTHORITY,
TARGET_HOST,
VERSION);
private static final List<ByteString> HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TE,
TRANSFER_ENCODING,
ENCODING,
UPGRADE);
private final StreamAllocation streamAllocation;
private final FramedConnection framedConnection;
private HttpEngine httpEngine;
private FramedStream stream;
public Http2xStream(StreamAllocation streamAllocation, FramedConnection framedConnection) {
this.streamAllocation = streamAllocation;
this.framedConnection = framedConnection;
}
@Override public void setHttpEngine(HttpEngine httpEngine) {
this.httpEngine = httpEngine;
}
@Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
return stream.getSink();
}
@Override public void writeRequestHeaders(Request request) throws IOException {
if (stream != null) return;
httpEngine.writingRequestHeaders();
boolean permitsRequestBody = httpEngine.permitsRequestBody(request);
List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
? http2HeadersList(request)
: spdy3HeadersList(request);
boolean hasResponseBody = true;
stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
stream.readTimeout().timeout(httpEngine.client.getReadTimeout(), TimeUnit.MILLISECONDS);
stream.writeTimeout().timeout(httpEngine.client.getWriteTimeout(), TimeUnit.MILLISECONDS);
}
@Override public void writeRequestBody(RetryableSink requestBody) throws IOException {
requestBody.writeToSocket(stream.getSink());
}
@Override public void finishRequest() throws IOException {
stream.getSink().close();
}
@Override public Response.Builder readResponseHeaders() throws IOException {
return framedConnection.getProtocol() == Protocol.HTTP_2
? readHttp2HeadersList(stream.getResponseHeaders())
: readSpdy3HeadersList(stream.getResponseHeaders());
}
/**
* Returns a list of alternating names and values containing a SPDY request.
* Names are all lowercase. No names are repeated. If any name has multiple
* values, they are concatenated using "\0" as a delimiter.
*/
public static List<Header> spdy3HeadersList(Request request) {
Headers headers = request.headers();
List<Header> result = new ArrayList<>(headers.size() + 5);
result.add(new Header(TARGET_METHOD, request.method()));
result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
result.add(new Header(VERSION, "HTTP/1.1"));
result.add(new Header(TARGET_HOST, Util.hostHeader(request.httpUrl(), false)));
result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
Set<ByteString> names = new LinkedHashSet<>();
for (int i = 0, size = headers.size(); i < size; i++) {
// header names must be lowercase.
ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
// Drop headers that are forbidden when layering HTTP over SPDY.
if (SPDY_3_SKIPPED_REQUEST_HEADERS.contains(name)) continue;
// If we haven't seen this name before, add the pair to the end of the list...
String value = headers.value(i);
if (names.add(name)) {
result.add(new Header(name, value));
continue;
}
// ...otherwise concatenate the existing values and this value.
for (int j = 0; j < result.size(); j++) {
if (result.get(j).name.equals(name)) {
String concatenated = joinOnNull(result.get(j).value.utf8(), value);
result.set(j, new Header(name, concatenated));
break;
}
}
}
return result;
}
private static String joinOnNull(String first, String second) {
return new StringBuilder(first).append('\0').append(second).toString();
}
public static List<Header> http2HeadersList(Request request) {
Headers headers = request.headers();
List<Header> result = new ArrayList<>(headers.size() + 4);
result.add(new Header(TARGET_METHOD, request.method()));
result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.httpUrl())));
result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.httpUrl(), false))); // Optional
result.add(new Header(TARGET_SCHEME, request.httpUrl().scheme()));
for (int i = 0, size = headers.size(); i < size; i++) {
// header names must be lowercase.
ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)) {
result.add(new Header(name, headers.value(i)));
}
}
return result;
}
/** Returns headers for a name value block containing a SPDY response. */
public static Response.Builder readSpdy3HeadersList(List<Header> headerBlock) throws IOException {
String status = null;
String version = "HTTP/1.1";
Headers.Builder headersBuilder = new Headers.Builder();
for (int i = 0, size = headerBlock.size(); i < size; i++) {
ByteString name = headerBlock.get(i).name;
String values = headerBlock.get(i).value.utf8();
for (int start = 0; start < values.length(); ) {
int end = values.indexOf('\0', start);
if (end == -1) {
end = values.length();
}
String value = values.substring(start, end);
if (name.equals(RESPONSE_STATUS)) {
status = value;
} else if (name.equals(VERSION)) {
version = value;
} else if (!SPDY_3_SKIPPED_RESPONSE_HEADERS.contains(name)) {
headersBuilder.add(name.utf8(), value);
}
start = end + 1;
}
}
if (status == null) throw new ProtocolException("Expected ':status' header not present");
StatusLine statusLine = StatusLine.parse(version + " " + status);
return new Response.Builder()
.protocol(Protocol.SPDY_3)
.code(statusLine.code)
.message(statusLine.message)
.headers(headersBuilder.build());
}
/** Returns headers for a name value block containing an HTTP/2 response. */
public static Response.Builder readHttp2HeadersList(List<Header> headerBlock) throws IOException {
String status = null;
Headers.Builder headersBuilder = new Headers.Builder();
for (int i = 0, size = headerBlock.size(); i < size; i++) {
ByteString name = headerBlock.get(i).name;
String value = headerBlock.get(i).value.utf8();
if (name.equals(RESPONSE_STATUS)) {
status = value;
} else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {
headersBuilder.add(name.utf8(), value);
}
}
if (status == null) throw new ProtocolException("Expected ':status' header not present");
StatusLine statusLine = StatusLine.parse("HTTP/1.1 " + status);
return new Response.Builder()
.protocol(Protocol.HTTP_2)
.code(statusLine.code)
.message(statusLine.message)
.headers(headersBuilder.build());
}
@Override public ResponseBody openResponseBody(Response response) throws IOException {
Source source = new StreamFinishingSource(stream.getSource());
return new RealResponseBody(response.headers(), Okio.buffer(source));
}
@Override public void cancel() {
if (stream != null) stream.closeLater(ErrorCode.CANCEL);
}
class StreamFinishingSource extends ForwardingSource {
public StreamFinishingSource(Source delegate) {
super(delegate);
}
@Override public void close() throws IOException {
streamAllocation.streamFinished(Http2xStream.this);
super.close();
}
}
}