mirror of
https://github.com/square/okhttp.git
synced 2025-12-03 18:31:17 +03:00
Use OkBuffer in http/2 source stream
This commit is contained in:
@@ -344,7 +344,6 @@ public final class MockWebServer {
|
||||
.handler(spdySocketHandler).build();
|
||||
openSpdyConnections.put(spdyConnection, Boolean.TRUE);
|
||||
openClientSockets.remove(socket);
|
||||
spdyConnection.readConnectionHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -71,11 +72,12 @@ public interface FrameReader extends Closeable {
|
||||
* on a new connection if they are idempotent.
|
||||
*
|
||||
* @param lastGoodStreamId the last stream ID the peer processed before
|
||||
* sending this message. If {@lastGoodStreamId} is zero, the peer processed no frames.
|
||||
* sending this message. If {@code lastGoodStreamId} is zero, the peer
|
||||
* processed no frames.
|
||||
* @param errorCode reason for closing the connection.
|
||||
* @param debugData only valid for http/2; opaque debug data to send.
|
||||
*/
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData);
|
||||
void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData);
|
||||
|
||||
/**
|
||||
* Notifies that an additional {@code windowSizeIncrement} bytes can be
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.BitArray;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import com.squareup.okhttp.internal.bytes.Deadline;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffer;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffers;
|
||||
import com.squareup.okhttp.internal.bytes.Source;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -13,8 +15,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.squareup.okhttp.internal.Util.asciiLowerCase;
|
||||
|
||||
/**
|
||||
* Read and write HPACK v05.
|
||||
*
|
||||
@@ -99,8 +99,9 @@ final class HpackDraft05 {
|
||||
static final class Reader {
|
||||
private final Huffman.Codec huffmanCodec;
|
||||
|
||||
private final InputStream in;
|
||||
private final List<Header> emittedHeaders = new ArrayList<Header>();
|
||||
private final Source source;
|
||||
private final OkBuffer buffer = new OkBuffer();
|
||||
private int maxHeaderTableByteCount;
|
||||
|
||||
// Visible for testing.
|
||||
@@ -123,10 +124,10 @@ final class HpackDraft05 {
|
||||
long referencedStaticHeaders = 0L;
|
||||
int headerTableByteCount = 0;
|
||||
|
||||
Reader(boolean client, int maxHeaderTableByteCount, InputStream in) {
|
||||
Reader(boolean client, int maxHeaderTableByteCount, Source source) {
|
||||
this.huffmanCodec = client ? Huffman.Codec.RESPONSE : Huffman.Codec.REQUEST;
|
||||
this.maxHeaderTableByteCount = maxHeaderTableByteCount;
|
||||
this.in = in;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
int maxHeaderTableByteCount() {
|
||||
@@ -181,8 +182,9 @@ final class HpackDraft05 {
|
||||
* set of emitted headers.
|
||||
*/
|
||||
void readHeaders() throws IOException {
|
||||
while (in.available() > 0) {
|
||||
int b = in.read() & 0xff;
|
||||
while (buffer.byteCount() > 0
|
||||
|| source.read(buffer, 2048, Deadline.NONE) != -1) {
|
||||
int b = buffer.readByte() & 0xff;
|
||||
if (b == 0x80) { // 10000000
|
||||
clearReferenceSet();
|
||||
} else if ((b & 0x80) == 0x80) { // 1NNNNNNN
|
||||
@@ -333,7 +335,8 @@ final class HpackDraft05 {
|
||||
}
|
||||
|
||||
private int readByte() throws IOException {
|
||||
return in.read() & 0xff;
|
||||
OkBuffers.require(source, buffer, 1, Deadline.NONE);
|
||||
return buffer.readByte() & 0xff;
|
||||
}
|
||||
|
||||
int readInt(int firstByte, int prefixMask) throws IOException {
|
||||
@@ -365,16 +368,25 @@ final class HpackDraft05 {
|
||||
ByteString readByteString(boolean asciiLowercase) throws IOException {
|
||||
int firstByte = readByte();
|
||||
int length = readInt(firstByte, PREFIX_8_BITS);
|
||||
|
||||
boolean huffmanDecode = false;
|
||||
if ((length & 0x80) == 0x80) { // 1NNNNNNN
|
||||
length &= ~0x80;
|
||||
byte[] buff = new byte[length];
|
||||
Util.readFully(in, buff);
|
||||
buff = huffmanCodec.decode(buff); // TODO: streaming Huffman!
|
||||
if (asciiLowercase) asciiLowerCase(buff);
|
||||
return ByteString.of(buff);
|
||||
huffmanDecode = true;
|
||||
}
|
||||
return length == 0 ? ByteString.EMPTY
|
||||
: asciiLowercase ? ByteString.readLowerCase(in, length) : ByteString.read(in, length);
|
||||
|
||||
OkBuffers.require(source, buffer, length, Deadline.NONE);
|
||||
ByteString byteString = buffer.readByteString(length);
|
||||
|
||||
if (huffmanDecode) {
|
||||
byteString = huffmanCodec.decode(byteString); // TODO: streaming Huffman!
|
||||
}
|
||||
|
||||
if (asciiLowercase) {
|
||||
byteString = byteString.toAsciiLowercase();
|
||||
}
|
||||
|
||||
return byteString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,16 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.Protocol;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import com.squareup.okhttp.internal.bytes.Deadline;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffer;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffers;
|
||||
import com.squareup.okhttp.internal.bytes.Source;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -37,8 +38,8 @@ public final class Http20Draft09 implements Variant {
|
||||
return Protocol.HTTP_2;
|
||||
}
|
||||
|
||||
private static final byte[] CONNECTION_HEADER =
|
||||
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(Util.UTF_8);
|
||||
private static final ByteString CONNECTION_HEADER
|
||||
= ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
|
||||
|
||||
static final byte TYPE_DATA = 0x0;
|
||||
static final byte TYPE_HEADERS = 0x1;
|
||||
@@ -59,7 +60,7 @@ public final class Http20Draft09 implements Variant {
|
||||
static final byte FLAG_PRIORITY = 0x8;
|
||||
|
||||
@Override public FrameReader newReader(InputStream in, boolean client) {
|
||||
return new Reader(in, 4096, client);
|
||||
return new Reader(OkBuffers.source(in), 4096, client);
|
||||
}
|
||||
|
||||
@Override public FrameWriter newWriter(OutputStream out, boolean client) {
|
||||
@@ -67,39 +68,38 @@ public final class Http20Draft09 implements Variant {
|
||||
}
|
||||
|
||||
static final class Reader implements FrameReader {
|
||||
private final DataInputStream in;
|
||||
private final ContinuationInputStream continuation;
|
||||
private final OkBuffer buffer = new OkBuffer();
|
||||
private final Source source;
|
||||
private final ContinuationSource continuation;
|
||||
private final boolean client;
|
||||
|
||||
// Visible for testing.
|
||||
final HpackDraft05.Reader hpackReader;
|
||||
|
||||
Reader(InputStream in, int headerTableSize, boolean client) {
|
||||
this.in = new DataInputStream(in);
|
||||
Reader(Source source, int headerTableSize, boolean client) {
|
||||
this.source = source;
|
||||
this.client = client;
|
||||
this.continuation = new ContinuationInputStream(this.in);
|
||||
this.continuation = new ContinuationSource(source, buffer);
|
||||
this.hpackReader = new HpackDraft05.Reader(client, headerTableSize, continuation);
|
||||
}
|
||||
|
||||
@Override public void readConnectionHeader() throws IOException {
|
||||
if (client) return; // Nothing to read; servers don't send connection headers!
|
||||
byte[] connectionHeader = new byte[CONNECTION_HEADER.length];
|
||||
Util.readFully(in, connectionHeader);
|
||||
if (!Arrays.equals(connectionHeader, CONNECTION_HEADER)) {
|
||||
throw ioException("Expected a connection header but was %s",
|
||||
Arrays.toString(connectionHeader));
|
||||
OkBuffers.require(source, buffer, CONNECTION_HEADER.size(), Deadline.NONE);
|
||||
ByteString connectionHeader = buffer.readByteString(CONNECTION_HEADER.size());
|
||||
if (!CONNECTION_HEADER.equals(connectionHeader)) {
|
||||
throw ioException("Expected a connection header but was %s", connectionHeader.utf8());
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean nextFrame(Handler handler) throws IOException {
|
||||
int w1;
|
||||
try {
|
||||
w1 = in.readInt();
|
||||
OkBuffers.require(source, buffer, 8, Deadline.NONE);
|
||||
} catch (IOException e) {
|
||||
return false; // This might be a normal socket close.
|
||||
}
|
||||
|
||||
int w2 = in.readInt();
|
||||
int w1 = buffer.readInt();
|
||||
int w2 = buffer.readInt();
|
||||
|
||||
// boolean r = (w1 & 0xc0000000) != 0; // Reserved: Ignore first 2 bits.
|
||||
short length = (short) ((w1 & 0x3fff0000) >> 16); // 14-bit unsigned == max 16383
|
||||
@@ -147,7 +147,7 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
default:
|
||||
// Implementations MUST ignore frames of unsupported or unrecognized types.
|
||||
Util.skipByReading(in, length);
|
||||
OkBuffers.skip(source, buffer, length, Deadline.NONE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -160,7 +160,8 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
int priority = -1;
|
||||
if ((flags & FLAG_PRIORITY) != 0) {
|
||||
priority = in.readInt() & 0x7fffffff;
|
||||
OkBuffers.require(source, buffer, 4, Deadline.NONE);
|
||||
priority = buffer.readInt() & 0x7fffffff;
|
||||
length -= 4; // account for above read.
|
||||
}
|
||||
|
||||
@@ -187,14 +188,15 @@ public final class Http20Draft09 implements Variant {
|
||||
throws IOException {
|
||||
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
|
||||
// TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
|
||||
handler.data(inFinished, streamId, in, length);
|
||||
handler.data(inFinished, streamId, OkBuffers.inputStream(source, buffer), length);
|
||||
}
|
||||
|
||||
private void readPriority(Handler handler, short length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (length != 4) throw ioException("TYPE_PRIORITY length: %d != 4", length);
|
||||
if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
|
||||
int w1 = in.readInt();
|
||||
OkBuffers.require(source, buffer, 4, Deadline.NONE);
|
||||
int w1 = buffer.readInt();
|
||||
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
|
||||
int priority = (w1 & 0x7fffffff);
|
||||
handler.priority(streamId, priority);
|
||||
@@ -204,7 +206,8 @@ public final class Http20Draft09 implements Variant {
|
||||
throws IOException {
|
||||
if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
|
||||
if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
|
||||
int errorCodeInt = in.readInt();
|
||||
OkBuffers.require(source, buffer, 4, Deadline.NONE);
|
||||
int errorCodeInt = buffer.readInt();
|
||||
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
|
||||
if (errorCode == null) {
|
||||
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
|
||||
@@ -223,9 +226,10 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
|
||||
Settings settings = new Settings();
|
||||
OkBuffers.require(source, buffer, length, Deadline.NONE);
|
||||
for (int i = 0; i < length; i += 8) {
|
||||
int w1 = in.readInt();
|
||||
int value = in.readInt();
|
||||
int w1 = buffer.readInt();
|
||||
int value = buffer.readInt();
|
||||
// int r = (w1 & 0xff000000) >>> 24; // Reserved.
|
||||
int id = w1 & 0xffffff;
|
||||
settings.set(id, 0, value);
|
||||
@@ -241,7 +245,8 @@ public final class Http20Draft09 implements Variant {
|
||||
if (streamId == 0) {
|
||||
throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
|
||||
}
|
||||
int promisedStreamId = in.readInt() & 0x7fffffff;
|
||||
OkBuffers.require(source, buffer, 4, Deadline.NONE);
|
||||
int promisedStreamId = buffer.readInt() & 0x7fffffff;
|
||||
length -= 4; // account for above read.
|
||||
List<Header> headerBlock = readHeaderBlock(length, flags, streamId);
|
||||
handler.pushPromise(streamId, promisedStreamId, headerBlock);
|
||||
@@ -251,8 +256,9 @@ public final class Http20Draft09 implements Variant {
|
||||
throws IOException {
|
||||
if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
|
||||
if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
|
||||
int payload1 = in.readInt();
|
||||
int payload2 = in.readInt();
|
||||
OkBuffers.require(source, buffer, 8, Deadline.NONE);
|
||||
int payload1 = buffer.readInt();
|
||||
int payload2 = buffer.readInt();
|
||||
boolean ack = (flags & FLAG_ACK) != 0;
|
||||
handler.ping(ack, payload1, payload2);
|
||||
}
|
||||
@@ -261,17 +267,18 @@ public final class Http20Draft09 implements Variant {
|
||||
throws IOException {
|
||||
if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
|
||||
if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
|
||||
int lastStreamId = in.readInt();
|
||||
int errorCodeInt = in.readInt();
|
||||
OkBuffers.require(source, buffer, 8, Deadline.NONE);
|
||||
int lastStreamId = buffer.readInt();
|
||||
int errorCodeInt = buffer.readInt();
|
||||
int opaqueDataLength = length - 8;
|
||||
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
|
||||
if (errorCode == null) {
|
||||
throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
|
||||
}
|
||||
byte[] debugData = Util.EMPTY_BYTE_ARRAY;
|
||||
ByteString debugData = ByteString.EMPTY;
|
||||
if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
|
||||
debugData = new byte[opaqueDataLength];
|
||||
Util.readFully(in, debugData);
|
||||
OkBuffers.require(source, buffer, opaqueDataLength, Deadline.NONE);
|
||||
debugData = buffer.readByteString(opaqueDataLength);
|
||||
}
|
||||
handler.goAway(lastStreamId, errorCode, debugData);
|
||||
}
|
||||
@@ -279,13 +286,14 @@ public final class Http20Draft09 implements Variant {
|
||||
private void readWindowUpdate(Handler handler, short length, byte flags, int streamId)
|
||||
throws IOException {
|
||||
if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
|
||||
long increment = (in.readInt() & 0x7fffffff);
|
||||
OkBuffers.require(source, buffer, 4, Deadline.NONE);
|
||||
long increment = (buffer.readInt() & 0x7fffffff);
|
||||
if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
|
||||
handler.windowUpdate(streamId, increment);
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
in.close();
|
||||
source.close(Deadline.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +324,7 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
@Override public synchronized void connectionHeader() throws IOException {
|
||||
if (!client) return; // Nothing to write; servers don't send connection headers!
|
||||
out.write(CONNECTION_HEADER);
|
||||
out.write(CONNECTION_HEADER.toByteArray());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
@@ -477,11 +485,13 @@ public final class Http20Draft09 implements Variant {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompression of the header block occurs above the framing layer. This class lazily reads
|
||||
* continuation frames as they are needed by {@link HpackDraft05.Reader#readHeaders()}.
|
||||
* Decompression of the header block occurs above the framing layer. This
|
||||
* class lazily reads continuation frames as they are needed by {@link
|
||||
* HpackDraft05.Reader#readHeaders()}.
|
||||
*/
|
||||
static final class ContinuationInputStream extends InputStream {
|
||||
private final DataInputStream in;
|
||||
static final class ContinuationSource implements Source {
|
||||
private final Source source;
|
||||
private final OkBuffer buffer;
|
||||
|
||||
int length;
|
||||
byte flags;
|
||||
@@ -489,65 +499,36 @@ public final class Http20Draft09 implements Variant {
|
||||
|
||||
int left;
|
||||
|
||||
ContinuationInputStream(DataInputStream in) {
|
||||
this.in = in;
|
||||
public ContinuationSource(Source source, OkBuffer buffer) {
|
||||
this.source = source;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@Override public int read() throws IOException {
|
||||
if (left == 0) {
|
||||
if (endHeaders()) {
|
||||
throw eofReading(1);
|
||||
} else {
|
||||
readContinuationHeader();
|
||||
}
|
||||
}
|
||||
left--;
|
||||
return in.read();
|
||||
@Override public long read(OkBuffer sink, long byteCount, Deadline deadline)
|
||||
throws IOException {
|
||||
while (left == 0) {
|
||||
if ((flags & FLAG_END_HEADERS) != 0) return -1;
|
||||
readContinuationHeader(deadline);
|
||||
// TODO: test case for empty continuation header?
|
||||
}
|
||||
|
||||
@Override public int available() throws IOException {
|
||||
if (left == 0) {
|
||||
if (endHeaders()) {
|
||||
return 0;
|
||||
} else {
|
||||
readContinuationHeader();
|
||||
}
|
||||
}
|
||||
return left;
|
||||
long toRead = Math.min(byteCount, left);
|
||||
long read = buffer.byteCount() > 0
|
||||
? buffer.read(sink, toRead, deadline)
|
||||
: source.read(sink, toRead, deadline);
|
||||
if (read == -1) return -1;
|
||||
left -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override public int read(byte[] dst, int offset, int byteCount) throws IOException {
|
||||
if (byteCount > left) {
|
||||
if (endHeaders()) {
|
||||
throw eofReading(byteCount);
|
||||
} else {
|
||||
int beforeContinuation = left;
|
||||
Util.readFully(in, dst, offset, beforeContinuation);
|
||||
readContinuationHeader();
|
||||
int afterContinuation = byteCount - beforeContinuation;
|
||||
offset += beforeContinuation;
|
||||
Util.readFully(in, dst, offset, afterContinuation);
|
||||
left -= afterContinuation;
|
||||
return byteCount;
|
||||
}
|
||||
} else {
|
||||
Util.readFully(in, dst, offset, byteCount);
|
||||
left -= byteCount;
|
||||
return byteCount;
|
||||
}
|
||||
@Override public void close(Deadline deadline) throws IOException {
|
||||
}
|
||||
|
||||
private EOFException eofReading(int byteCount) throws EOFException {
|
||||
int read = length - left;
|
||||
throw new EOFException(
|
||||
String.format("EOF reading %s more bytes; read %s/%s of frame.", byteCount, read,
|
||||
length));
|
||||
}
|
||||
|
||||
private void readContinuationHeader() throws IOException {
|
||||
private void readContinuationHeader(Deadline deadline) throws IOException {
|
||||
OkBuffers.require(source, buffer, 8, deadline);
|
||||
int previousStreamId = streamId;
|
||||
int w1 = in.readInt();
|
||||
int w2 = in.readInt();
|
||||
int w1 = buffer.readInt();
|
||||
int w2 = buffer.readInt();
|
||||
length = left = (short) ((w1 & 0x3fff0000) >> 16);
|
||||
byte type = (byte) ((w1 & 0xff00) >> 8);
|
||||
flags = (byte) (w1 & 0xff);
|
||||
@@ -555,9 +536,5 @@ public final class Http20Draft09 implements Variant {
|
||||
if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
|
||||
if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
|
||||
}
|
||||
|
||||
private boolean endHeaders() {
|
||||
return (flags & FLAG_END_HEADERS) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -84,6 +85,10 @@ class Huffman {
|
||||
return (int) ((len + 7) >> 3);
|
||||
}
|
||||
|
||||
ByteString decode(ByteString buf) throws IOException {
|
||||
return ByteString.of(decode(buf.toByteArray()));
|
||||
}
|
||||
|
||||
byte[] decode(byte[] buf) throws IOException {
|
||||
// FIXME
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@@ -31,8 +31,6 @@ import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/**
|
||||
@@ -184,8 +182,6 @@ final class Spdy3 implements Variant {
|
||||
return true;
|
||||
|
||||
default:
|
||||
Logger logger = Logger.getLogger("com.squareup.okhttp.internal.spdy.Spdy3");
|
||||
logger.log(Level.INFO, "Ignoring unknown frame type " + type);
|
||||
OkBuffers.skip(source, buffer, length, Deadline.NONE);
|
||||
return true;
|
||||
}
|
||||
@@ -271,7 +267,7 @@ final class Spdy3 implements Variant {
|
||||
if (errorCode == null) {
|
||||
throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
|
||||
}
|
||||
handler.goAway(lastGoodStreamId, errorCode, Util.EMPTY_BYTE_ARRAY);
|
||||
handler.goAway(lastGoodStreamId, errorCode, ByteString.EMPTY);
|
||||
}
|
||||
|
||||
private void readSettings(Handler handler, int flags, int length) throws IOException {
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.spdy;
|
||||
import com.squareup.okhttp.Protocol;
|
||||
import com.squareup.okhttp.internal.NamedRunnable;
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -444,14 +445,6 @@ public final class SpdyConnection implements Closeable {
|
||||
frameWriter.settings(okHttpSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a connection header if the current variant requires it. This should
|
||||
* be called after {@link Builder#build} for all new connections.
|
||||
*/
|
||||
public void readConnectionHeader() throws IOException {
|
||||
frameReader.readConnectionHeader();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String hostName;
|
||||
private InputStream in;
|
||||
@@ -515,6 +508,9 @@ public final class SpdyConnection implements Closeable {
|
||||
ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;
|
||||
ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;
|
||||
try {
|
||||
if (!client) {
|
||||
frameReader.readConnectionHeader();
|
||||
}
|
||||
while (frameReader.nextFrame(this)) {
|
||||
}
|
||||
connectionErrorCode = ErrorCode.NO_ERROR;
|
||||
@@ -665,8 +661,8 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
if (debugData.length > 0) { // TODO: log the debugData
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
if (debugData.size() > 0) { // TODO: log the debugData
|
||||
}
|
||||
synchronized (SpdyConnection.this) {
|
||||
shutdown = true;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
@@ -49,13 +50,11 @@ class BaseTestHandler implements FrameReader.Handler {
|
||||
fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
fail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowUpdate(int streamId, long windowSizeIncrement) {
|
||||
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
|
||||
fail();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Square, Inc.
|
||||
*
|
||||
* 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.squareup.okhttp.internal.spdy;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.squareup.okhttp.internal.spdy.HpackDraft05Test.MutableByteArrayInputStream;
|
||||
import static com.squareup.okhttp.internal.spdy.Http20Draft09.ContinuationInputStream;
|
||||
import static com.squareup.okhttp.internal.spdy.Http20Draft09.FLAG_END_STREAM;
|
||||
import static com.squareup.okhttp.internal.spdy.Http20Draft09.TYPE_DATA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public final class ContinuationInputStreamTest {
|
||||
private final MutableByteArrayInputStream bytesIn = new MutableByteArrayInputStream();
|
||||
private final ContinuationInputStream continuation =
|
||||
new ContinuationInputStream(new DataInputStream(bytesIn));
|
||||
|
||||
@Test public void readCantOverrunHeaderPayload() throws IOException {
|
||||
bytesIn.set(onlyHeadersPayloadFollowedByData());
|
||||
|
||||
continuation.length = continuation.left = 3;
|
||||
continuation.flags = Http20Draft09.FLAG_END_HEADERS;
|
||||
continuation.streamId = 12345;
|
||||
|
||||
assertEquals(1, continuation.read());
|
||||
assertEquals(2, continuation.read());
|
||||
assertEquals(3, continuation.read());
|
||||
|
||||
try {
|
||||
continuation.read();
|
||||
fail();
|
||||
} catch (EOFException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readCantOverrunHeaderContinuationPayload() throws IOException {
|
||||
bytesIn.set(headersPayloadWithContinuationFollowedByData());
|
||||
|
||||
continuation.length = continuation.left = 2;
|
||||
continuation.flags = Http20Draft09.FLAG_NONE;
|
||||
continuation.streamId = 12345;
|
||||
|
||||
assertEquals(1, continuation.read());
|
||||
assertEquals(2, continuation.read());
|
||||
assertEquals(3, continuation.read());
|
||||
assertEquals(0, continuation.available());
|
||||
|
||||
try {
|
||||
continuation.read();
|
||||
fail();
|
||||
} catch (EOFException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void availableWithContinuation() throws IOException {
|
||||
bytesIn.set(headersPayloadWithContinuationFollowedByData());
|
||||
|
||||
continuation.length = continuation.left = 2;
|
||||
continuation.flags = Http20Draft09.FLAG_NONE;
|
||||
continuation.streamId = 12345;
|
||||
|
||||
assertEquals(1, continuation.read());
|
||||
assertEquals(2, continuation.read()); // exhaust frame one
|
||||
|
||||
assertEquals(0, continuation.left);
|
||||
assertEquals(1, continuation.available()); // lazy reads next
|
||||
|
||||
assertEquals(1, continuation.length);
|
||||
assertEquals(1, continuation.left);
|
||||
assertEquals(3, continuation.read());
|
||||
|
||||
assertEquals(0, continuation.available());
|
||||
assertEquals(0, continuation.left);
|
||||
|
||||
try {
|
||||
continuation.read();
|
||||
fail();
|
||||
} catch (EOFException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readArrayCantOverrunHeaderPayload() throws IOException {
|
||||
bytesIn.set(onlyHeadersPayloadFollowedByData());
|
||||
|
||||
continuation.length = continuation.left = 3;
|
||||
continuation.flags = Http20Draft09.FLAG_END_HEADERS;
|
||||
continuation.streamId = 12345;
|
||||
|
||||
byte[] buff = new byte[3];
|
||||
assertEquals(3, continuation.read(buff));
|
||||
assertTrue(Arrays.equals(buff, new byte[] {1, 2, 3}));
|
||||
|
||||
try {
|
||||
continuation.read(buff);
|
||||
fail();
|
||||
} catch (EOFException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test public void readArrayCantOverrunHeaderContinuationPayload() throws IOException {
|
||||
bytesIn.set(headersPayloadWithContinuationFollowedByData());
|
||||
|
||||
continuation.length = continuation.left = 2;
|
||||
continuation.flags = Http20Draft09.FLAG_NONE;
|
||||
continuation.streamId = 12345;
|
||||
|
||||
byte[] buff = new byte[3];
|
||||
assertEquals(3, continuation.read(buff));
|
||||
assertTrue(Arrays.equals(buff, new byte[] {1, 2, 3}));
|
||||
|
||||
try {
|
||||
continuation.read(buff);
|
||||
fail();
|
||||
} catch (EOFException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] onlyHeadersPayloadFollowedByData() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream dataOut = new DataOutputStream(out);
|
||||
dataOut.write(new byte[] {1, 2, 3});
|
||||
dataOut.writeShort(0);
|
||||
dataOut.write(TYPE_DATA);
|
||||
dataOut.write(FLAG_END_STREAM);
|
||||
dataOut.writeInt(0xFFFFFFFF);
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
static byte[] headersPayloadWithContinuationFollowedByData() throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
DataOutputStream dataOut = new DataOutputStream(out);
|
||||
dataOut.write(new byte[] {1, 2});
|
||||
dataOut.writeShort(1);
|
||||
dataOut.write(Http20Draft09.TYPE_CONTINUATION);
|
||||
dataOut.write(Http20Draft09.FLAG_END_HEADERS);
|
||||
dataOut.writeInt(12345);
|
||||
dataOut.write(3);
|
||||
dataOut.writeShort(0);
|
||||
dataOut.write(TYPE_DATA);
|
||||
dataOut.write(FLAG_END_STREAM);
|
||||
dataOut.writeInt(0xFFFFFFFF);
|
||||
return out.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffers;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -30,7 +31,6 @@ import static com.squareup.okhttp.internal.Util.headerEntries;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HpackDraft05Test {
|
||||
@@ -795,12 +795,12 @@ public class HpackDraft05Test {
|
||||
@Test public void emptyHeaderName() throws IOException {
|
||||
hpackWriter.writeByteString(ByteString.encodeUtf8(""));
|
||||
assertBytes(0);
|
||||
assertSame(ByteString.EMPTY, newReader(byteStream(0)).readByteString(true));
|
||||
assertSame(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false));
|
||||
assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(true));
|
||||
assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString(false));
|
||||
}
|
||||
|
||||
private HpackDraft05.Reader newReader(InputStream input) {
|
||||
return new HpackDraft05.Reader(false, 4096, input);
|
||||
return new HpackDraft05.Reader(false, 4096, OkBuffers.source(input));
|
||||
}
|
||||
|
||||
private InputStream byteStream(int... bytes) {
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import com.squareup.okhttp.internal.bytes.OkBuffer;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -434,10 +435,11 @@ public class Http20Draft09Test {
|
||||
FrameReader fr = newReader(out);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
@Override public void goAway(
|
||||
int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
assertEquals(expectedStreamId, lastGoodStreamId);
|
||||
assertEquals(expectedError, errorCode);
|
||||
assertEquals(0, debugData.length);
|
||||
assertEquals(0, debugData.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -447,34 +449,36 @@ public class Http20Draft09Test {
|
||||
DataOutputStream dataOut = new DataOutputStream(out);
|
||||
|
||||
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
|
||||
final byte[] expectedData = new byte[8];
|
||||
Arrays.fill(expectedData, (byte) '*');
|
||||
final ByteString expectedData = ByteString.encodeUtf8("abcdefgh");
|
||||
|
||||
// Compose the expected GOAWAY frame without debug data.
|
||||
dataOut.writeShort(8 + expectedData.length);
|
||||
dataOut.writeShort(8 + expectedData.size());
|
||||
dataOut.write(Http20Draft09.TYPE_GOAWAY);
|
||||
dataOut.write(0); // no flags.
|
||||
dataOut.writeInt(0); // connection-scope
|
||||
dataOut.writeInt(0); // never read any stream!
|
||||
dataOut.writeInt(expectedError.httpCode);
|
||||
dataOut.write(expectedData);
|
||||
dataOut.write(expectedData.toByteArray());
|
||||
|
||||
// Check writer sends the same bytes.
|
||||
assertArrayEquals(out.toByteArray(), sendGoAway(0, expectedError, expectedData));
|
||||
assertArrayEquals(out.toByteArray(), sendGoAway(0, expectedError, expectedData.toByteArray()));
|
||||
|
||||
FrameReader fr = newReader(out);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
@Override public void goAway(
|
||||
int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
assertEquals(0, lastGoodStreamId);
|
||||
assertEquals(expectedError, errorCode);
|
||||
assertArrayEquals(expectedData, debugData);
|
||||
assertEquals(expectedData, debugData);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Http20Draft09.Reader newReader(ByteArrayOutputStream out) {
|
||||
return new Http20Draft09.Reader(new ByteArrayInputStream(out.toByteArray()), 4096, false);
|
||||
OkBuffer buffer = new OkBuffer();
|
||||
buffer.write(ByteString.of(out.toByteArray()));
|
||||
return new Http20Draft09.Reader(buffer, 4096, false);
|
||||
}
|
||||
|
||||
@Test public void frameSizeError() throws IOException {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import com.squareup.okhttp.internal.bytes.ByteString;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
@@ -256,13 +257,12 @@ public final class MockSpdyPeer implements Closeable {
|
||||
this.payload2 = payload2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
if (this.type != -1) throw new IllegalStateException();
|
||||
this.type = Spdy3.TYPE_GOAWAY;
|
||||
this.streamId = lastGoodStreamId;
|
||||
this.errorCode = errorCode;
|
||||
this.data = debugData;
|
||||
this.data = debugData.toByteArray();
|
||||
}
|
||||
|
||||
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
|
||||
|
||||
@@ -79,10 +79,11 @@ public class Spdy3Test {
|
||||
FrameReader fr = newReader(out);
|
||||
|
||||
fr.nextFrame(new BaseTestHandler() { // Consume the goAway frame.
|
||||
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
|
||||
@Override public void goAway(
|
||||
int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
|
||||
assertEquals(expectedStreamId, lastGoodStreamId);
|
||||
assertEquals(expectedError, errorCode);
|
||||
assertEquals(0, debugData.length);
|
||||
assertEquals(0, debugData.size());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user