Skip to content

Instantly share code, notes, and snippets.

@yschimke
Created April 13, 2019 13:58
Show Gist options
  • Save yschimke/4172ab720c03c374b3803bd9372821f6 to your computer and use it in GitHub Desktop.
Save yschimke/4172ab720c03c374b3803bd9372821f6 to your computer and use it in GitHub Desktop.
commit dbadaa6b614d02b37b6c7c773a418fd0216d85d6
Author: Yuri Schimke <yuri@schimke.ee>
Date: Sat Apr 13 10:59:43 2019 +0100
HttpExchange Kotlin conversion
diff --git a/okhttp/src/main/java/okhttp3/internal/http/ExchangeCode.kt b/okhttp/src/main/java/okhttp3/internal/http/ExchangeCode.kt
index 9fa1a70d..b0c3e2e7 100644
--- a/okhttp/src/main/java/okhttp3/internal/http/ExchangeCode.kt
+++ b/okhttp/src/main/java/okhttp3/internal/http/ExchangeCode.kt
@@ -27,7 +27,7 @@ import okio.Source
interface ExchangeCodec {
/** Returns the connection that carries this codec. */
- fun connection(): RealConnection
+ fun connection(): RealConnection?
/** Returns an output stream where the request body can be streamed. */
@Throws(IOException::class)
diff --git a/okhttp/src/main/java/okhttp3/internal/http1/Http1ExchangeCodec.java b/okhttp/src/main/java/okhttp3/internal/http1/Http1ExchangeCodec.java
index 6dfef025..d395308a 100644
--- a/okhttp/src/main/java/okhttp3/internal/http1/Http1ExchangeCodec.java
+++ b/okhttp/src/main/java/okhttp3/internal/http1/Http1ExchangeCodec.java
@@ -13,530 +13,542 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package okhttp3.internal.http1;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.ProtocolException;
-import java.util.concurrent.TimeUnit;
-import okhttp3.Headers;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.internal.Util;
-import okhttp3.internal.connection.RealConnection;
-import okhttp3.internal.http.ExchangeCodec;
-import okhttp3.internal.http.HttpHeaders;
-import okhttp3.internal.http.RequestLine;
-import okhttp3.internal.http.StatusLine;
-import okio.Buffer;
-import okio.BufferedSink;
-import okio.BufferedSource;
-import okio.ForwardingTimeout;
-import okio.Sink;
-import okio.Source;
-import okio.Timeout;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static okhttp3.internal.InternalKtKt.addHeaderLenient;
-import static okhttp3.internal.Util.checkOffsetAndCount;
-import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE;
+package okhttp3.internal.http1
+
+import okhttp3.Headers
+import okhttp3.HttpUrl
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.internal.Util
+import okhttp3.internal.Util.checkOffsetAndCount
+import okhttp3.internal.addHeaderLenient
+import okhttp3.internal.connection.RealConnection
+import okhttp3.internal.http.ExchangeCodec
+import okhttp3.internal.http.HttpHeaders
+import okhttp3.internal.http.RequestLine
+import okhttp3.internal.http.StatusLine
+import okhttp3.internal.http.StatusLine.HTTP_CONTINUE
+import okio.Buffer
+import okio.BufferedSink
+import okio.BufferedSource
+import okio.ForwardingTimeout
+import okio.Sink
+import okio.Source
+import okio.Timeout
+import java.io.EOFException
+import java.io.IOException
+import java.net.ProtocolException
+import java.util.concurrent.TimeUnit.MILLISECONDS
/**
* A socket connection that can be used to send HTTP/1.1 messages. This class strictly enforces the
* following lifecycle:
*
- * <ol>
- * <li>{@linkplain #writeRequest Send request headers}.
- * <li>Open a sink to write the request body. Either {@linkplain #newKnownLengthSink known
- * length} or {@link #newChunkedSink chunked}.
- * <li>Write to and then close that sink.
- * <li>{@linkplain #readResponseHeaders Read response headers}.
- * <li>Open a source to read the response body. Either {@linkplain #newFixedLengthSource
- * fixed-length}, {@linkplain #newChunkedSource chunked} or {@linkplain
- * #newUnknownLengthSource unknown length}.
- * <li>Read from and close that source.
- * </ol>
+ * 1. [Send request headers][.writeRequest].
+ * 2. Open a sink to write the request body. Either [known][newKnownLengthSink] or
+ * [chunked][newChunkedSink].
+ * 3. Write to and then close that sink.
+ * 4. [Read response headers][.readResponseHeaders].
+ * 5. Open a source to read the response body. Either [fixed-length][newFixedLengthSource],
+ * [chunked][newChunkedSource] or [unknown][newUnknownLengthSource].
+ * 6. Read from and close that source.
*
- * <p>Exchanges that do not have a request body may skip creating and closing the request body.
- * Exchanges that do not have a response body can call {@link #newFixedLengthSource(long)
- * newFixedLengthSource(0)} and may skip reading and closing that source.
+ * Exchanges that do not have a request body may skip creating and closing the request body.
+ * Exchanges that do not have a response body can call
+ * [newFixedLengthSource(0)][newFixedLengthSource] and may skip reading and closing that source.
*/
-public final class Http1ExchangeCodec implements ExchangeCodec {
- private static final int STATE_IDLE = 0; // Idle connections are ready to write request headers.
- private static final int STATE_OPEN_REQUEST_BODY = 1;
- private static final int STATE_WRITING_REQUEST_BODY = 2;
- private static final int STATE_READ_RESPONSE_HEADERS = 3;
- private static final int STATE_OPEN_RESPONSE_BODY = 4;
- private static final int STATE_READING_RESPONSE_BODY = 5;
- private static final int STATE_CLOSED = 6;
- private static final int HEADER_LIMIT = 256 * 1024;
-
- /** The client that configures this stream. May be null for HTTPS proxy tunnels. */
- private final OkHttpClient client;
-
- /** The connection that carries this stream. */
- private final RealConnection realConnection;
-
- private final BufferedSource source;
- private final BufferedSink sink;
- private int state = STATE_IDLE;
- private long headerLimit = HEADER_LIMIT;
-
- /**
- * Received trailers. Null unless the response body uses chunked transfer-encoding and includes
- * trailers. Undefined until the end of the response body.
- */
- private Headers trailers;
-
- public Http1ExchangeCodec(OkHttpClient client, RealConnection realConnection,
- BufferedSource source, BufferedSink sink) {
- this.client = client;
- this.realConnection = realConnection;
- this.source = source;
- this.sink = sink;
- }
-
- @Override public RealConnection connection() {
- return realConnection;
- }
-
- @Override public Sink createRequestBody(Request request, long contentLength) throws IOException {
- if (request.body() != null && request.body().isDuplex()) {
- throw new ProtocolException("Duplex connections are not supported for HTTP/1");
- }
+class Http1ExchangeCodec(
+ /** The client that configures this stream. May be null for HTTPS proxy tunnels. */
+ private val client: OkHttpClient?,
+ /** The connection that carries this stream. */
+ private val realConnection: RealConnection?,
+ private val source: BufferedSource,
+ private val sink: BufferedSink
+) : ExchangeCodec {
+ private var state = STATE_IDLE
+ private var headerLimit = HEADER_LIMIT.toLong()
- if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
- // Stream a request body of unknown length.
- return newChunkedSink();
- }
+ /**
+ * Received trailers. Null unless the response body uses chunked transfer-encoding and includes
+ * trailers. Undefined until the end of the response body.
+ */
+ private var trailers: Headers? = null
- if (contentLength != -1L) {
- // Stream a request body of a known length.
- return newKnownLengthSink();
- }
+ /** Returns true if this connection is closed. */
+ val isClosed: Boolean
+ get() = state == STATE_CLOSED
- throw new IllegalStateException(
- "Cannot stream a request body without chunked encoding or a known content length!");
- }
-
- @Override public void cancel() {
- if (realConnection != null) realConnection.cancel();
- }
-
- /**
- * Prepares the HTTP headers and sends them to the server.
- *
- * <p>For streaming requests with a body, headers must be prepared <strong>before</strong> the
- * output stream has been written to. Otherwise the body would need to be buffered!
- *
- * <p>For non-streaming requests with a body, headers must be prepared <strong>after</strong> the
- * output stream has been written to and closed. This ensures that the {@code Content-Length}
- * header field receives the proper value.
- */
- @Override public void writeRequestHeaders(Request request) throws IOException {
- String requestLine = RequestLine.get(
- request, realConnection.route().proxy().type());
- writeRequest(request.headers(), requestLine);
- }
-
- @Override public long reportedContentLength(Response response) {
- if (!HttpHeaders.hasBody(response)) {
- return 0L;
+ override fun connection(): RealConnection? {
+ return realConnection
}
- if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
- return -1L;
- }
+ @Throws(IOException::class)
+ override fun createRequestBody(request: Request, contentLength: Long): Sink {
+ if (request.body() != null && request.body()!!.isDuplex()) {
+ throw ProtocolException("Duplex connections are not supported for HTTP/1")
+ }
- return HttpHeaders.contentLength(response);
- }
+ if ("chunked".equals(request.header("Transfer-Encoding"), ignoreCase = true)) {
+ // Stream a request body of unknown length.
+ return newChunkedSink()
+ }
- @Override public Source openResponseBodySource(Response response) {
- if (!HttpHeaders.hasBody(response)) {
- return newFixedLengthSource(0);
+ if (contentLength != -1L) {
+ // Stream a request body of a known length.
+ return newKnownLengthSink()
+ }
+
+ throw IllegalStateException(
+ "Cannot stream a request body without chunked encoding or a known content length!")
}
- if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
- return newChunkedSource(response.request().url());
+ override fun cancel() {
+ realConnection?.cancel()
}
- long contentLength = HttpHeaders.contentLength(response);
- if (contentLength != -1) {
- return newFixedLengthSource(contentLength);
+ /**
+ * 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 `Content-Length`
+ * header field receives the proper value.
+ */
+ @Throws(IOException::class)
+ override fun writeRequestHeaders(request: Request) {
+ val requestLine = RequestLine.get(
+ request, realConnection!!.route().proxy().type())
+ writeRequest(request.headers(), requestLine)
}
- return newUnknownLengthSource();
- }
+ override fun reportedContentLength(response: Response): Long {
+ if (!HttpHeaders.hasBody(response)) {
+ return 0L
+ }
- @Override public Headers trailers() {
- if (state != STATE_CLOSED) {
- throw new IllegalStateException("too early; can't read the trailers yet");
+ return if ("chunked".equals(response.header("Transfer-Encoding"), ignoreCase = true)) {
+ -1L
+ } else HttpHeaders.contentLength(response)
}
- return trailers != null ? trailers : Util.EMPTY_HEADERS;
- }
-
- /** Returns true if this connection is closed. */
- public boolean isClosed() {
- return state == STATE_CLOSED;
- }
-
- @Override public void flushRequest() throws IOException {
- sink.flush();
- }
-
- @Override public void finishRequest() throws IOException {
- sink.flush();
- }
-
- /** Returns bytes of a request header for sending on an HTTP transport. */
- public void writeRequest(Headers headers, String requestLine) throws IOException {
- if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
- sink.writeUtf8(requestLine).writeUtf8("\r\n");
- for (int i = 0, size = headers.size(); i < size; i++) {
- sink.writeUtf8(headers.name(i))
- .writeUtf8(": ")
- .writeUtf8(headers.value(i))
- .writeUtf8("\r\n");
- }
- sink.writeUtf8("\r\n");
- state = STATE_OPEN_REQUEST_BODY;
- }
- @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
- if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
- throw new IllegalStateException("state: " + state);
- }
+ override fun openResponseBodySource(response: Response): Source {
+ if (!HttpHeaders.hasBody(response)) {
+ return newFixedLengthSource(0)
+ }
- try {
- StatusLine statusLine = StatusLine.parse(readHeaderLine());
-
- Response.Builder responseBuilder = new Response.Builder()
- .protocol(statusLine.protocol)
- .code(statusLine.code)
- .message(statusLine.message)
- .headers(readHeaders());
-
- if (expectContinue && statusLine.code == HTTP_CONTINUE) {
- return null;
- } else if (statusLine.code == HTTP_CONTINUE) {
- state = STATE_READ_RESPONSE_HEADERS;
- return responseBuilder;
- }
-
- state = STATE_OPEN_RESPONSE_BODY;
- return responseBuilder;
- } catch (EOFException e) {
- // Provide more context if the server ends the stream before sending a response.
- throw new IOException("unexpected end of stream on "
- + realConnection.route().address().url().redact(), e);
- }
- }
-
- private String readHeaderLine() throws IOException {
- String line = source.readUtf8LineStrict(headerLimit);
- headerLimit -= line.length();
- return line;
- }
-
- /** Reads headers or trailers. */
- private Headers readHeaders() throws IOException {
- Headers.Builder headers = new Headers.Builder();
- // parse the result headers until the first blank line
- for (String line; (line = readHeaderLine()).length() != 0; ) {
- addHeaderLenient(headers, line);
+ if ("chunked".equals(response.header("Transfer-Encoding"), ignoreCase = true)) {
+ return newChunkedSource(response.request().url())
+ }
+
+ val contentLength = HttpHeaders.contentLength(response)
+ return if (contentLength != -1L) {
+ newFixedLengthSource(contentLength)
+ } else newUnknownLengthSource()
}
- return headers.build();
- }
-
- private Sink newChunkedSink() {
- if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
- state = STATE_WRITING_REQUEST_BODY;
- return new ChunkedSink();
- }
-
- private Sink newKnownLengthSink() {
- if (state != STATE_OPEN_REQUEST_BODY) throw new IllegalStateException("state: " + state);
- state = STATE_WRITING_REQUEST_BODY;
- return new KnownLengthSink();
- }
-
- private Source newFixedLengthSource(long length) {
- if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
- state = STATE_READING_RESPONSE_BODY;
- return new FixedLengthSource(length);
- }
-
- private Source newChunkedSource(HttpUrl url) {
- if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
- state = STATE_READING_RESPONSE_BODY;
- return new ChunkedSource(url);
- }
-
- private Source newUnknownLengthSource() {
- if (state != STATE_OPEN_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
- state = STATE_READING_RESPONSE_BODY;
- realConnection.noNewExchanges();
- return new UnknownLengthSource();
- }
-
- /**
- * Sets the delegate of {@code timeout} to {@link Timeout#NONE} and resets its underlying timeout
- * to the default configuration. Use this to avoid unexpected sharing of timeouts between pooled
- * connections.
- */
- private void detachTimeout(ForwardingTimeout timeout) {
- Timeout oldDelegate = timeout.delegate();
- timeout.setDelegate(Timeout.NONE);
- oldDelegate.clearDeadline();
- oldDelegate.clearTimeout();
- }
-
- /**
- * The response body from a CONNECT should be empty, but if it is not then we should consume it
- * before proceeding.
- */
- public void skipConnectBody(Response response) throws IOException {
- long contentLength = HttpHeaders.contentLength(response);
- if (contentLength == -1L) return;
- Source body = newFixedLengthSource(contentLength);
- Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
- body.close();
- }
-
- /** An HTTP request body. */
- private final class KnownLengthSink implements Sink {
- private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
- private boolean closed;
-
- @Override public Timeout timeout() {
- return timeout;
+
+ override fun trailers(): Headers {
+ if (state != STATE_CLOSED) {
+ throw IllegalStateException("too early; can't read the trailers yet")
+ }
+ return trailers ?: Util.EMPTY_HEADERS
}
- @Override public void write(Buffer source, long byteCount) throws IOException {
- if (closed) throw new IllegalStateException("closed");
- checkOffsetAndCount(source.size(), 0, byteCount);
- sink.write(source, byteCount);
+ @Throws(IOException::class)
+ override fun flushRequest() {
+ sink.flush()
}
- @Override public void flush() throws IOException {
- if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
- sink.flush();
+ @Throws(IOException::class)
+ override fun finishRequest() {
+ sink.flush()
}
- @Override public void close() throws IOException {
- if (closed) return;
- closed = true;
- detachTimeout(timeout);
- state = STATE_READ_RESPONSE_HEADERS;
+ /** Returns bytes of a request header for sending on an HTTP transport. */
+ @Throws(IOException::class)
+ fun writeRequest(headers: Headers, requestLine: String) {
+ if (state != STATE_IDLE) throw IllegalStateException("state: $state")
+ sink.writeUtf8(requestLine).writeUtf8("\r\n")
+ var i = 0
+ val size = headers.size()
+ while (i < size) {
+ sink.writeUtf8(headers.name(i))
+ .writeUtf8(": ")
+ .writeUtf8(headers.value(i))
+ .writeUtf8("\r\n")
+ i++
+ }
+ sink.writeUtf8("\r\n")
+ state = STATE_OPEN_REQUEST_BODY
}
- }
- /**
- * An HTTP body with alternating chunk sizes and chunk bodies. It is the caller's responsibility
- * to buffer chunks; typically by using a buffered sink with this sink.
- */
- private final class ChunkedSink implements Sink {
- private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
- private boolean closed;
+ @Throws(IOException::class)
+ override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
+ if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
+ throw IllegalStateException("state: $state")
+ }
- ChunkedSink() {
+ try {
+ val statusLine = StatusLine.parse(readHeaderLine())
+
+ val responseBuilder = Response.Builder()
+ .protocol(statusLine.protocol)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(readHeaders())
+
+ if (expectContinue && statusLine.code == HTTP_CONTINUE) {
+ return null
+ } else if (statusLine.code == HTTP_CONTINUE) {
+ state = STATE_READ_RESPONSE_HEADERS
+ return responseBuilder
+ }
+
+ state = STATE_OPEN_RESPONSE_BODY
+ return responseBuilder
+ } catch (e: EOFException) {
+ // Provide more context if the server ends the stream before sending a response.
+ val address = if (realConnection != null) realConnection.route().address().url().redact() else "unknown"
+ throw IOException("unexpected end of stream on $address", e)
+ }
}
- @Override public Timeout timeout() {
- return timeout;
+ @Throws(IOException::class)
+ private fun readHeaderLine(): String {
+ val line = source.readUtf8LineStrict(headerLimit)
+ headerLimit -= line.length.toLong()
+ return line
}
- @Override public void write(Buffer source, long byteCount) throws IOException {
- if (closed) throw new IllegalStateException("closed");
- if (byteCount == 0) return;
+ /** Reads headers or trailers. */
+ @Throws(IOException::class)
+ private fun readHeaders(): Headers {
+ val headers = Headers.Builder()
+ // parse the result headers until the first blank line
+ var line = readHeaderLine()
+ while (line.isNotEmpty()) {
+ addHeaderLenient(headers, line)
+ line = readHeaderLine()
+ }
+ return headers.build()
+ }
- sink.writeHexadecimalUnsignedLong(byteCount);
- sink.writeUtf8("\r\n");
- sink.write(source, byteCount);
- sink.writeUtf8("\r\n");
+ private fun newChunkedSink(): Sink {
+ if (state != STATE_OPEN_REQUEST_BODY) throw IllegalStateException("state: $state")
+ state = STATE_WRITING_REQUEST_BODY
+ return ChunkedSink()
}
- @Override public synchronized void flush() throws IOException {
- if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
- sink.flush();
+ private fun newKnownLengthSink(): Sink {
+ if (state != STATE_OPEN_REQUEST_BODY) throw IllegalStateException("state: $state")
+ state = STATE_WRITING_REQUEST_BODY
+ return KnownLengthSink()
}
- @Override public synchronized void close() throws IOException {
- if (closed) return;
- closed = true;
- sink.writeUtf8("0\r\n\r\n");
- detachTimeout(timeout);
- state = STATE_READ_RESPONSE_HEADERS;
+ private fun newFixedLengthSource(length: Long): Source {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw IllegalStateException("state: $state")
+ state = STATE_READING_RESPONSE_BODY
+ return FixedLengthSource(length)
}
- }
- private abstract class AbstractSource implements Source {
- protected final ForwardingTimeout timeout = new ForwardingTimeout(source.timeout());
- protected boolean closed;
+ private fun newChunkedSource(url: HttpUrl): Source {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw IllegalStateException("state: $state")
+ state = STATE_READING_RESPONSE_BODY
+ return ChunkedSource(url)
+ }
- @Override public Timeout timeout() {
- return timeout;
+ private fun newUnknownLengthSource(): Source {
+ if (state != STATE_OPEN_RESPONSE_BODY) throw IllegalStateException("state: $state")
+ state = STATE_READING_RESPONSE_BODY
+ realConnection!!.noNewExchanges()
+ return UnknownLengthSource()
}
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- try {
- return source.read(sink, byteCount);
- } catch (IOException e) {
- realConnection.noNewExchanges();
- responseBodyComplete();
- throw e;
- }
+ /**
+ * Sets the delegate of `timeout` to [Timeout.NONE] and resets its underlying timeout
+ * to the default configuration. Use this to avoid unexpected sharing of timeouts between pooled
+ * connections.
+ */
+ private fun detachTimeout(timeout: ForwardingTimeout) {
+ val oldDelegate = timeout.delegate
+ timeout.setDelegate(Timeout.NONE)
+ oldDelegate.clearDeadline()
+ oldDelegate.clearTimeout()
}
/**
- * Closes the cache entry and makes the socket available for reuse. This should be invoked when
- * the end of the body has been reached.
+ * The response body from a CONNECT should be empty, but if it is not then we should consume it
+ * before proceeding.
*/
- final void responseBodyComplete() {
- if (state == STATE_CLOSED) return;
- if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
+ @Throws(IOException::class)
+ fun skipConnectBody(response: Response) {
+ val contentLength = HttpHeaders.contentLength(response)
+ if (contentLength == -1L) return
+ val body = newFixedLengthSource(contentLength)
+ Util.skipAll(body, Integer.MAX_VALUE, MILLISECONDS)
+ body.close()
+ }
+
+ /** An HTTP request body. */
+ private inner class KnownLengthSink : Sink {
+ private val timeout = ForwardingTimeout(sink.timeout())
+ private var closed: Boolean = false
+
+ override fun timeout(): Timeout {
+ return timeout
+ }
- detachTimeout(timeout);
+ @Throws(IOException::class)
+ override fun write(source: Buffer, byteCount: Long) {
+ if (closed) throw IllegalStateException("closed")
+ checkOffsetAndCount(source.size, 0, byteCount)
+ sink.write(source, byteCount)
+ }
+
+ @Throws(IOException::class)
+ override fun flush() {
+ if (closed) return // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush()
+ }
- state = STATE_CLOSED;
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+ closed = true
+ detachTimeout(timeout)
+ state = STATE_READ_RESPONSE_HEADERS
+ }
}
- }
- /** An HTTP body with a fixed length specified in advance. */
- private class FixedLengthSource extends AbstractSource {
- private long bytesRemaining;
+ /**
+ * An HTTP body with alternating chunk sizes and chunk bodies. It is the caller's responsibility
+ * to buffer chunks; typically by using a buffered sink with this sink.
+ */
+ private inner class ChunkedSink internal constructor() : Sink {
+ private val timeout = ForwardingTimeout(sink.timeout())
+ private var closed: Boolean = false
- FixedLengthSource(long length) {
- bytesRemaining = length;
- if (bytesRemaining == 0) {
- responseBodyComplete();
- }
- }
+ override fun timeout(): Timeout {
+ return timeout
+ }
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
- if (closed) throw new IllegalStateException("closed");
- if (bytesRemaining == 0) return -1;
-
- long read = super.read(sink, Math.min(bytesRemaining, byteCount));
- if (read == -1) {
- realConnection.noNewExchanges(); // The server didn't supply the promised content length.
- ProtocolException e = new ProtocolException("unexpected end of stream");
- responseBodyComplete();
- throw e;
- }
-
- bytesRemaining -= read;
- if (bytesRemaining == 0) {
- responseBodyComplete();
- }
- return read;
- }
+ @Throws(IOException::class)
+ override fun write(source: Buffer, byteCount: Long) {
+ if (closed) throw IllegalStateException("closed")
+ if (byteCount == 0L) return
- @Override public void close() throws IOException {
- if (closed) return;
+ sink.writeHexadecimalUnsignedLong(byteCount)
+ sink.writeUtf8("\r\n")
+ sink.write(source, byteCount)
+ sink.writeUtf8("\r\n")
+ }
- if (bytesRemaining != 0 && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
- realConnection.noNewExchanges(); // Unread bytes remain on the stream.
- responseBodyComplete();
- }
+ @Synchronized
+ @Throws(IOException::class)
+ override fun flush() {
+ if (closed) return // Don't throw; this stream might have been closed on the caller's behalf.
+ sink.flush()
+ }
- closed = true;
+ @Synchronized
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+ closed = true
+ sink.writeUtf8("0\r\n\r\n")
+ detachTimeout(timeout)
+ state = STATE_READ_RESPONSE_HEADERS
+ }
}
- }
- /** An HTTP body with alternating chunk sizes and chunk bodies. */
- private class ChunkedSource extends AbstractSource {
- private static final long NO_CHUNK_YET = -1L;
- private final HttpUrl url;
- private long bytesRemainingInChunk = NO_CHUNK_YET;
- private boolean hasMoreChunks = true;
+ private abstract inner class AbstractSource : Source {
+ protected val timeout = ForwardingTimeout(source.timeout())
+ protected var closed: Boolean = false
- ChunkedSource(HttpUrl url) {
- this.url = url;
- }
+ override fun timeout(): Timeout {
+ return timeout
+ }
+
+ @Throws(IOException::class)
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ try {
+ return source.read(sink, byteCount)
+ } catch (e: IOException) {
+ realConnection!!.noNewExchanges()
+ responseBodyComplete()
+ throw e
+ }
+ }
+
+ /**
+ * Closes the cache entry and makes the socket available for reuse. This should be invoked when
+ * the end of the body has been reached.
+ */
+ internal fun responseBodyComplete() {
+ if (state == STATE_CLOSED) return
+ if (state != STATE_READING_RESPONSE_BODY) throw IllegalStateException("state: $state")
- @Override public long read(Buffer sink, long byteCount) throws IOException {
- if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
- if (closed) throw new IllegalStateException("closed");
- if (!hasMoreChunks) return -1;
-
- if (bytesRemainingInChunk == 0 || bytesRemainingInChunk == NO_CHUNK_YET) {
- readChunkSize();
- if (!hasMoreChunks) return -1;
- }
-
- long read = super.read(sink, Math.min(byteCount, bytesRemainingInChunk));
- if (read == -1) {
- realConnection.noNewExchanges(); // The server didn't supply the promised chunk length.
- ProtocolException e = new ProtocolException("unexpected end of stream");
- responseBodyComplete();
- throw e;
- }
- bytesRemainingInChunk -= read;
- return read;
+ detachTimeout(timeout)
+
+ state = STATE_CLOSED
+ }
}
- private void readChunkSize() throws IOException {
- // Read the suffix of the previous chunk.
- if (bytesRemainingInChunk != NO_CHUNK_YET) {
- source.readUtf8LineStrict();
- }
- try {
- bytesRemainingInChunk = source.readHexadecimalUnsignedLong();
- String extensions = source.readUtf8LineStrict().trim();
- if (bytesRemainingInChunk < 0 || (!extensions.isEmpty() && !extensions.startsWith(";"))) {
- throw new ProtocolException("expected chunk size and optional extensions but was \""
- + bytesRemainingInChunk + extensions + "\"");
+ /** An HTTP body with a fixed length specified in advance. */
+ private inner class FixedLengthSource internal constructor(private var bytesRemaining: Long) :
+ AbstractSource() {
+
+ init {
+ if (bytesRemaining == 0L) {
+ responseBodyComplete()
+ }
+ }
+
+ @Throws(IOException::class)
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ if (byteCount < 0) throw IllegalArgumentException("byteCount < 0: $byteCount")
+ if (closed) throw IllegalStateException("closed")
+ if (bytesRemaining == 0L) return -1
+
+ val read = super.read(sink, Math.min(bytesRemaining, byteCount))
+ if (read == -1L) {
+ realConnection!!.noNewExchanges() // The server didn't supply the promised content length.
+ val e = ProtocolException("unexpected end of stream")
+ responseBodyComplete()
+ throw e
+ }
+
+ bytesRemaining -= read
+ if (bytesRemaining == 0L) {
+ responseBodyComplete()
+ }
+ return read
+ }
+
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+
+ if (bytesRemaining != 0L && !Util.discard(this,
+ ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
+ realConnection!!.noNewExchanges() // Unread bytes remain on the stream.
+ responseBodyComplete()
+ }
+
+ closed = true
}
- } catch (NumberFormatException e) {
- throw new ProtocolException(e.getMessage());
- }
- if (bytesRemainingInChunk == 0L) {
- hasMoreChunks = false;
- trailers = readHeaders();
- HttpHeaders.receiveHeaders(client.cookieJar(), url, trailers);
- responseBodyComplete();
- }
}
- @Override public void close() throws IOException {
- if (closed) return;
- if (hasMoreChunks && !Util.discard(this, DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
- realConnection.noNewExchanges(); // Unread bytes remain on the stream.
- responseBodyComplete();
- }
- closed = true;
+ /** An HTTP body with alternating chunk sizes and chunk bodies. */
+ private inner class ChunkedSource internal constructor(private val url: HttpUrl) :
+ AbstractSource() {
+ private var bytesRemainingInChunk = NO_CHUNK_YET
+ private var hasMoreChunks = true
+
+ @Throws(IOException::class)
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ if (byteCount < 0) throw IllegalArgumentException("byteCount < 0: $byteCount")
+ if (closed) throw IllegalStateException("closed")
+ if (!hasMoreChunks) return -1
+
+ if (bytesRemainingInChunk == 0L || bytesRemainingInChunk == NO_CHUNK_YET) {
+ readChunkSize()
+ if (!hasMoreChunks) return -1
+ }
+
+ val read = super.read(sink, Math.min(byteCount, bytesRemainingInChunk))
+ if (read == -1L) {
+ realConnection!!.noNewExchanges() // The server didn't supply the promised chunk length.
+ val e = ProtocolException("unexpected end of stream")
+ responseBodyComplete()
+ throw e
+ }
+ bytesRemainingInChunk -= read
+ return read
+ }
+
+ @Throws(IOException::class)
+ private fun readChunkSize() {
+ // Read the suffix of the previous chunk.
+ if (bytesRemainingInChunk != NO_CHUNK_YET) {
+ source.readUtf8LineStrict()
+ }
+ try {
+ bytesRemainingInChunk = source.readHexadecimalUnsignedLong()
+ val extensions = source.readUtf8LineStrict().trim { it <= ' ' }
+ if (bytesRemainingInChunk < 0 || extensions.isNotEmpty() && !extensions.startsWith(
+ ";")) {
+ throw ProtocolException(
+ "expected chunk size and optional extensions but was \"" +
+ bytesRemainingInChunk + extensions + "\"")
+ }
+ } catch (e: NumberFormatException) {
+ throw ProtocolException(e.message)
+ }
+
+ if (bytesRemainingInChunk == 0L) {
+ hasMoreChunks = false
+ trailers = readHeaders()
+ HttpHeaders.receiveHeaders(client!!.cookieJar(), url, trailers)
+ responseBodyComplete()
+ }
+ }
+
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+ if (hasMoreChunks && !Util.discard(this, ExchangeCodec.DISCARD_STREAM_TIMEOUT_MILLIS,
+ MILLISECONDS)) {
+ realConnection!!.noNewExchanges() // Unread bytes remain on the stream.
+ responseBodyComplete()
+ }
+ closed = true
+ }
}
- }
-
- /** An HTTP message body terminated by the end of the underlying stream. */
- private class UnknownLengthSource extends AbstractSource {
- private boolean inputExhausted;
-
- @Override public long read(Buffer sink, long byteCount)
- throws IOException {
- if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
- if (closed) throw new IllegalStateException("closed");
- if (inputExhausted) return -1;
-
- long read = super.read(sink, byteCount);
- if (read == -1) {
- inputExhausted = true;
- responseBodyComplete();
- return -1;
- }
- return read;
+
+ /** An HTTP message body terminated by the end of the underlying stream. */
+ private inner class UnknownLengthSource : AbstractSource() {
+ private var inputExhausted: Boolean = false
+
+ @Throws(IOException::class)
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ if (byteCount < 0) throw IllegalArgumentException("byteCount < 0: $byteCount")
+ if (closed) throw IllegalStateException("closed")
+ if (inputExhausted) return -1
+
+ val read = super.read(sink, byteCount)
+ if (read == -1L) {
+ inputExhausted = true
+ responseBodyComplete()
+ return -1
+ }
+ return read
+ }
+
+ @Throws(IOException::class)
+ override fun close() {
+ if (closed) return
+ if (!inputExhausted) {
+ responseBodyComplete()
+ }
+ closed = true
+ }
}
- @Override public void close() throws IOException {
- if (closed) return;
- if (!inputExhausted) {
- responseBodyComplete();
- }
- closed = true;
+ companion object {
+ private const val NO_CHUNK_YET = -1L
+
+ private const val STATE_IDLE = 0 // Idle connections are ready to write request headers.
+ private const val STATE_OPEN_REQUEST_BODY = 1
+ private const val STATE_WRITING_REQUEST_BODY = 2
+ private const val STATE_READ_RESPONSE_HEADERS = 3
+ private const val STATE_OPEN_RESPONSE_BODY = 4
+ private const val STATE_READING_RESPONSE_BODY = 5
+ private const val STATE_CLOSED = 6
+ private const val HEADER_LIMIT = 256 * 1024
}
- }
}
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2ExchangeCodec.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2ExchangeCodec.java
index 453e26e0..acd80845 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Http2ExchangeCodec.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2ExchangeCodec.java
@@ -13,194 +13,204 @@
* 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.internal.Internal;
-import okhttp3.internal.Util;
-import okhttp3.internal.connection.RealConnection;
-import okhttp3.internal.http.ExchangeCodec;
-import okhttp3.internal.http.HttpHeaders;
-import okhttp3.internal.http.RequestLine;
-import okhttp3.internal.http.StatusLine;
-import okio.Sink;
-import okio.Source;
-
-import static okhttp3.internal.InternalKtKt.addHeaderLenient;
-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 Http2ExchangeCodec implements ExchangeCodec {
- 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<String> 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<String> HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
- CONNECTION,
- HOST,
- KEEP_ALIVE,
- PROXY_CONNECTION,
- TE,
- TRANSFER_ENCODING,
- ENCODING,
- UPGRADE);
-
- private final Interceptor.Chain chain;
- private final RealConnection realConnection;
- private final Http2Connection connection;
- private volatile Http2Stream stream;
- private final Protocol protocol;
- private volatile boolean canceled;
-
- public Http2ExchangeCodec(OkHttpClient client, RealConnection realConnection,
- Interceptor.Chain chain, Http2Connection connection) {
- this.realConnection = realConnection;
- this.chain = chain;
- this.connection = connection;
- this.protocol = client.protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)
- ? Protocol.H2_PRIOR_KNOWLEDGE
- : Protocol.HTTP_2;
- }
-
- @Override public RealConnection connection() {
- return realConnection;
- }
-
- @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<Header> requestHeaders = http2HeadersList(request);
- stream = connection.newStream(requestHeaders, hasRequestBody);
- // We may have been asked to cancel while creating the new stream and sending the request
- // headers, but there was still no stream to close.
- if (canceled) {
- stream.closeLater(ErrorCode.CANCEL);
- throw new IOException("Canceled");
+package okhttp3.internal.http2
+
+import java.io.IOException
+import java.net.ProtocolException
+import java.util.ArrayList
+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.internal.Internal
+import okhttp3.internal.Util
+import okhttp3.internal.connection.RealConnection
+import okhttp3.internal.http.ExchangeCodec
+import okhttp3.internal.http.HttpHeaders
+import okhttp3.internal.http.RequestLine
+import okhttp3.internal.http.StatusLine
+import okio.Sink
+import okio.Source
+
+import okhttp3.internal.addHeaderLenient
+import okhttp3.internal.http.StatusLine.HTTP_CONTINUE
+import okhttp3.internal.http2.Header.Companion.RESPONSE_STATUS_UTF8
+import okhttp3.internal.http2.Header.Companion.TARGET_AUTHORITY
+import okhttp3.internal.http2.Header.Companion.TARGET_AUTHORITY_UTF8
+import okhttp3.internal.http2.Header.Companion.TARGET_METHOD
+import okhttp3.internal.http2.Header.Companion.TARGET_METHOD_UTF8
+import okhttp3.internal.http2.Header.Companion.TARGET_PATH
+import okhttp3.internal.http2.Header.Companion.TARGET_PATH_UTF8
+import okhttp3.internal.http2.Header.Companion.TARGET_SCHEME
+import okhttp3.internal.http2.Header.Companion.TARGET_SCHEME_UTF8
+
+/** Encode requests and responses using HTTP/2 frames. */
+class Http2ExchangeCodec(
+ client: OkHttpClient,
+ private val realConnection: RealConnection,
+ private val chain: Interceptor.Chain,
+ private val connection: Http2Connection
+) :
+ ExchangeCodec {
+ @Volatile
+ private var stream: Http2Stream? = null
+
+ private val protocol: Protocol = if (client.protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE))
+ Protocol.H2_PRIOR_KNOWLEDGE
+ else
+ Protocol.HTTP_2
+
+ @Volatile
+ private var canceled: Boolean = false
+
+ override fun connection(): RealConnection {
+ return realConnection
}
- 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;
+
+ override fun createRequestBody(request: Request, contentLength: Long): Sink {
+ return stream!!.getSink()
+ }
+
+ @Throws(IOException::class)
+ override fun writeRequestHeaders(request: Request) {
+ if (stream != null) return
+
+ val hasRequestBody = request.body() != null
+ val requestHeaders = http2HeadersList(request)
+ stream = connection.newStream(requestHeaders, hasRequestBody)
+ // We may have been asked to cancel while creating the new stream and sending the request
+ // headers, but there was still no stream to close.
+ if (canceled) {
+ stream!!.closeLater(ErrorCode.CANCEL)
+ throw IOException("Canceled")
+ }
+ stream!!.readTimeout().timeout(chain.readTimeoutMillis().toLong(), TimeUnit.MILLISECONDS)
+ stream!!.writeTimeout().timeout(chain.writeTimeoutMillis().toLong(), TimeUnit.MILLISECONDS)
+ }
+
+ @Throws(IOException::class)
+ override fun flushRequest() {
+ connection.flush()
+ }
+
+ @Throws(IOException::class)
+ override fun finishRequest() {
+ stream!!.getSink().close()
+ }
+
+ @Throws(IOException::class)
+ override fun readResponseHeaders(expectContinue: Boolean): Response.Builder? {
+ val headers = stream!!.takeHeaders()
+ val responseBuilder = readHttp2HeadersList(headers, protocol)
+ return if (expectContinue && Internal.instance.code(responseBuilder) == HTTP_CONTINUE) {
+ null
+ } else responseBuilder
+ }
+
+ override fun reportedContentLength(response: Response): Long {
+ return HttpHeaders.contentLength(response)
+ }
+
+ override fun openResponseBodySource(response: Response): Source {
+ return stream!!.source
}
- return responseBuilder;
- }
-
- 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.url())));
- String host = request.header("Host");
- if (host != null) {
- result.add(new Header(TARGET_AUTHORITY, host)); // Optional.
+
+ @Throws(IOException::class)
+ override fun trailers(): Headers {
+ return stream!!.trailers()
}
- result.add(new Header(TARGET_SCHEME, request.url().scheme()));
-
- for (int i = 0, size = headers.size(); i < size; i++) {
- // header names must be lowercase.
- String name = headers.name(i).toLowerCase(Locale.US);
- if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)
- || name.equals(TE) && headers.value(i).equals("trailers")) {
- result.add(new Header(name, headers.value(i)));
- }
+
+ override fun cancel() {
+ canceled = true
+ if (stream != null) stream!!.closeLater(ErrorCode.CANCEL)
}
- 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)) {
- addHeaderLenient(headersBuilder, name, value);
- }
+
+ companion object {
+ private const val CONNECTION = "connection"
+ private const val HOST = "host"
+ private const val KEEP_ALIVE = "keep-alive"
+ private const val PROXY_CONNECTION = "proxy-connection"
+ private const val TRANSFER_ENCODING = "transfer-encoding"
+ private const val TE = "te"
+ private const val ENCODING = "encoding"
+ private const val UPGRADE = "upgrade"
+
+ /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
+ private val 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 val HTTP_2_SKIPPED_RESPONSE_HEADERS = Util.immutableList(
+ CONNECTION,
+ HOST,
+ KEEP_ALIVE,
+ PROXY_CONNECTION,
+ TE,
+ TRANSFER_ENCODING,
+ ENCODING,
+ UPGRADE)
+
+ fun http2HeadersList(request: Request): List<Header> {
+ val headers = request.headers()
+ val result = ArrayList<Header>(headers.size() + 4)
+ result.add(Header(TARGET_METHOD, request.method()))
+ result.add(Header(TARGET_PATH, RequestLine.requestPath(request.url())))
+ val host = request.header("Host")
+ if (host != null) {
+ result.add(Header(TARGET_AUTHORITY, host)) // Optional.
+ }
+ result.add(Header(TARGET_SCHEME, request.url().scheme()))
+
+ var i = 0
+ val size = headers.size()
+ while (i < size) {
+ // header names must be lowercase.
+ val name = headers.name(i).toLowerCase(Locale.US)
+ if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name) || name == TE && headers.value(
+ i) == "trailers") {
+ result.add(Header(name, headers.value(i)))
+ }
+ i++
+ }
+ return result
+ }
+
+ /** Returns headers for a name value block containing an HTTP/2 response. */
+ fun readHttp2HeadersList(headerBlock: Headers, protocol: Protocol): Response.Builder {
+ var statusLine: StatusLine? = null
+ val headersBuilder = Headers.Builder()
+ var i = 0
+ val size = headerBlock.size()
+ while (i < size) {
+ val name = headerBlock.name(i)
+ val value = headerBlock.value(i)
+ if (name == RESPONSE_STATUS_UTF8) {
+ statusLine = StatusLine.parse("HTTP/1.1 $value")
+ } else if (!HTTP_2_SKIPPED_RESPONSE_HEADERS.contains(name)) {
+ addHeaderLenient(headersBuilder, name, value)
+ }
+ i++
+ }
+ if (statusLine == null) throw ProtocolException("Expected ':status' header not present")
+
+ return Response.Builder()
+ .protocol(protocol)
+ .code(statusLine.code)
+ .message(statusLine.message)
+ .headers(headersBuilder.build())
+ }
}
- 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 long reportedContentLength(Response response) {
- return HttpHeaders.contentLength(response);
- }
-
- @Override public Source openResponseBodySource(Response response) {
- return stream.getSource();
- }
-
- @Override public Headers trailers() throws IOException {
- return stream.trailers();
- }
-
- @Override public void cancel() {
- canceled = true;
- if (stream != null) stream.closeLater(ErrorCode.CANCEL);
- }
}
diff --git a/okhttp/src/test/java/okhttp3/HeadersTest.java b/okhttp/src/test/java/okhttp3/HeadersTest.java
index fb244c31..b5e14f7d 100644
--- a/okhttp/src/test/java/okhttp3/HeadersTest.java
+++ b/okhttp/src/test/java/okhttp3/HeadersTest.java
@@ -49,7 +49,7 @@ public final class HeadersTest {
":version", "HTTP/1.1",
"connection", "close");
Request request = new Request.Builder().url("http://square.com/").build();
- Response response = Http2ExchangeCodec.readHttp2HeadersList(headerBlock, Protocol.HTTP_2).request(request).build();
+ Response response = Http2ExchangeCodec.Companion.readHttp2HeadersList(headerBlock, Protocol.HTTP_2).request(request).build();
Headers headers = response.headers();
assertThat(headers.size()).isEqualTo(1);
assertThat(headers.name(0)).isEqualTo(":version");
@@ -69,7 +69,7 @@ public final class HeadersTest {
":path", "/",
":authority", "square.com",
":scheme", "http");
- assertThat(Http2ExchangeCodec.http2HeadersList(request)).isEqualTo(expected);
+ assertThat(Http2ExchangeCodec.Companion.http2HeadersList(request)).isEqualTo(expected);
}
@Test public void http2HeadersListDontDropTeIfTrailersHttp2() {
@@ -82,7 +82,7 @@ public final class HeadersTest {
":path", "/",
":scheme", "http",
"te", "trailers");
- assertThat(Http2ExchangeCodec.http2HeadersList(request)).isEqualTo(expected);
+ assertThat(Http2ExchangeCodec.Companion.http2HeadersList(request)).isEqualTo(expected);
}
@Test public void ofTrims() {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment