okhttp3.internal.http2.Http2Codec Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.document.library.opener.onedrive.web
Show all versions of com.liferay.document.library.opener.onedrive.web
Liferay Document Library Opener OneDrive Web
/*
* 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 okhttp3.internal.http2;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.Internal;
import okhttp3.internal.Util;
import okhttp3.internal.connection.StreamAllocation;
import okhttp3.internal.http.HttpCodec;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.http.RealResponseBody;
import okhttp3.internal.http.RequestLine;
import okhttp3.internal.http.StatusLine;
import okio.Buffer;
import okio.ByteString;
import okio.ForwardingSource;
import okio.Okio;
import okio.Sink;
import okio.Source;
import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE;
import static okhttp3.internal.http2.Header.RESPONSE_STATUS_UTF8;
import static okhttp3.internal.http2.Header.TARGET_AUTHORITY;
import static okhttp3.internal.http2.Header.TARGET_AUTHORITY_UTF8;
import static okhttp3.internal.http2.Header.TARGET_METHOD;
import static okhttp3.internal.http2.Header.TARGET_METHOD_UTF8;
import static okhttp3.internal.http2.Header.TARGET_PATH;
import static okhttp3.internal.http2.Header.TARGET_PATH_UTF8;
import static okhttp3.internal.http2.Header.TARGET_SCHEME;
import static okhttp3.internal.http2.Header.TARGET_SCHEME_UTF8;
/** Encode requests and responses using HTTP/2 frames. */
public final class Http2Codec implements HttpCodec {
private static final String CONNECTION = "connection";
private static final String HOST = "host";
private static final String KEEP_ALIVE = "keep-alive";
private static final String PROXY_CONNECTION = "proxy-connection";
private static final String TRANSFER_ENCODING = "transfer-encoding";
private static final String TE = "te";
private static final String ENCODING = "encoding";
private static final String UPGRADE = "upgrade";
/** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
private static final List HTTP_2_SKIPPED_REQUEST_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TE,
TRANSFER_ENCODING,
ENCODING,
UPGRADE,
TARGET_METHOD_UTF8,
TARGET_PATH_UTF8,
TARGET_SCHEME_UTF8,
TARGET_AUTHORITY_UTF8);
private static final List HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
CONNECTION,
HOST,
KEEP_ALIVE,
PROXY_CONNECTION,
TE,
TRANSFER_ENCODING,
ENCODING,
UPGRADE);
private final Interceptor.Chain chain;
final StreamAllocation streamAllocation;
private final Http2Connection connection;
private Http2Stream stream;
private final Protocol protocol;
public Http2Codec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation,
Http2Connection connection) {
this.chain = chain;
this.streamAllocation = streamAllocation;
this.connection = connection;
this.protocol = client.protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)
? Protocol.H2_PRIOR_KNOWLEDGE
: Protocol.HTTP_2;
}
@Override public Sink createRequestBody(Request request, long contentLength) {
return stream.getSink();
}
@Override public void writeRequestHeaders(Request request) throws IOException {
if (stream != null) return;
boolean hasRequestBody = request.body() != null;
List requestHeaders = http2HeadersList(request);
stream = connection.newStream(requestHeaders, hasRequestBody);
stream.readTimeout().timeout(chain.readTimeoutMillis(), TimeUnit.MILLISECONDS);
stream.writeTimeout().timeout(chain.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
}
@Override public void flushRequest() throws IOException {
connection.flush();
}
@Override public void finishRequest() throws IOException {
stream.getSink().close();
}
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
Headers headers = stream.takeHeaders();
Response.Builder responseBuilder = readHttp2HeadersList(headers, protocol);
if (expectContinue && Internal.instance.code(responseBuilder) == HTTP_CONTINUE) {
return null;
}
return responseBuilder;
}
public static List http2HeadersList(Request request) {
Headers headers = request.headers();
List result = new ArrayList<>(headers.size() + 4);
result.add(new Header(TARGET_METHOD, request.method()));
result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url())));
String host = request.header("Host");
if (host != null) {
result.add(new Header(TARGET_AUTHORITY, host)); // Optional.
}
result.add(new Header(TARGET_SCHEME, request.url().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.utf8())) {
result.add(new Header(name, headers.value(i)));
}
}
return result;
}
/** Returns headers for a name value block containing an HTTP/2 response. */
public static Response.Builder readHttp2HeadersList(Headers headerBlock,
Protocol protocol) throws IOException {
StatusLine statusLine = null;
Headers.Builder headersBuilder = new Headers.Builder();
for (int i = 0, size = headerBlock.size(); i < size; i++) {
String name = headerBlock.name(i);
String value = headerBlock.value(i);
if (name.equals(RESPONSE_STATUS_UTF8)) {
statusLine = StatusLine.parse("HTTP/1.1 " + value);
} else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {
Internal.instance.addLenient(headersBuilder, name, value);
}
}
if (statusLine == null) throw new ProtocolException("Expected ':status' header not present");
return new Response.Builder()
.protocol(protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(headersBuilder.build());
}
@Override public ResponseBody openResponseBody(Response response) throws IOException {
streamAllocation.eventListener.responseBodyStart(streamAllocation.call);
String contentType = response.header("Content-Type");
long contentLength = HttpHeaders.contentLength(response);
Source source = new StreamFinishingSource(stream.getSource());
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
}
@Override public void cancel() {
if (stream != null) stream.closeLater(ErrorCode.CANCEL);
}
class StreamFinishingSource extends ForwardingSource {
boolean completed = false;
long bytesRead = 0;
StreamFinishingSource(Source delegate) {
super(delegate);
}
@Override public long read(Buffer sink, long byteCount) throws IOException {
try {
long read = delegate().read(sink, byteCount);
if (read > 0) {
bytesRead += read;
}
return read;
} catch (IOException e) {
endOfInput(e);
throw e;
}
}
@Override public void close() throws IOException {
super.close();
endOfInput(null);
}
private void endOfInput(IOException e) {
if (completed) return;
completed = true;
streamAllocation.streamFinished(false, Http2Codec.this, bytesRead, e);
}
}
}