1
0
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:
jwilson
2014-02-11 16:05:04 -05:00
parent a2cb34de5f
commit 72d5faec97
13 changed files with 148 additions and 322 deletions

View File

@@ -344,7 +344,6 @@ public final class MockWebServer {
.handler(spdySocketHandler).build();
openSpdyConnections.put(spdyConnection, Boolean.TRUE);
openClientSockets.remove(socket);
spdyConnection.readConnectionHeader();
return;
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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());
}
});
}