1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-24 04:02:07 +03:00

feedback from #413 and a few optimizations on hpack.

This commit is contained in:
Adrian Cole
2014-01-06 20:54:48 -08:00
parent 09264ca41c
commit 379e4ff199
12 changed files with 193 additions and 160 deletions

View File

@@ -33,7 +33,7 @@ import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.eclipse.jetty.npn.NextProtoNego;
import static com.squareup.okhttp.internal.Util.asByteStringList;
import static com.squareup.okhttp.internal.Util.byteStringList;
/** A basic SPDY server that serves the contents of a local directory. */
public final class SpdyServer implements IncomingStreamHandler {
@@ -109,7 +109,7 @@ public final class SpdyServer implements IncomingStreamHandler {
private void send404(SpdyStream stream, String path) throws IOException {
List<ByteString> responseHeaders =
asByteStringList(":status", "404", ":version", "HTTP/1.1", "content-type", "text/plain");
byteStringList(":status", "404", ":version", "HTTP/1.1", "content-type", "text/plain");
stream.reply(responseHeaders, true);
OutputStream out = stream.getOutputStream();
String text = "Not found: " + path;
@@ -119,7 +119,7 @@ public final class SpdyServer implements IncomingStreamHandler {
private void serveDirectory(SpdyStream stream, String[] files) throws IOException {
List<ByteString> responseHeaders =
asByteStringList(":status", "200", ":version", "HTTP/1.1", "content-type",
byteStringList(":status", "200", ":version", "HTTP/1.1", "content-type",
"text/html; charset=UTF-8");
stream.reply(responseHeaders, true);
OutputStream out = stream.getOutputStream();
@@ -133,8 +133,9 @@ public final class SpdyServer implements IncomingStreamHandler {
private void serveFile(SpdyStream stream, File file) throws IOException {
InputStream in = new FileInputStream(file);
byte[] buffer = new byte[8192];
stream.reply(asByteStringList(":status", "200", ":version", "HTTP/1.1", "content-type",
contentType(file)), true);
stream.reply(
byteStringList(":status", "200", ":version", "HTTP/1.1", "content-type", contentType(file)),
true);
OutputStream out = stream.getOutputStream();
int count;
while ((count = in.read(buffer)) != -1) {

View File

@@ -104,6 +104,20 @@ public final class ByteString {
return new ByteString(result);
}
public static ByteString concat(ByteString... byteStrings) {
int size = 0;
for (ByteString byteString : byteStrings) {
size += byteString.size();
}
byte[] result = new byte[size];
int pos = 0;
for (ByteString byteString : byteStrings) {
System.arraycopy(byteString.data, 0, result, pos, byteString.size());
pos += byteString.size();
}
return ByteString.of(result);
}
private ByteString(byte[] data) {
this.data = data; // Trusted internal constructor doesn't clone data.
}

View File

@@ -393,11 +393,11 @@ public final class Util {
};
}
public static List<ByteString> asByteStringList(String... strings) {
List<ByteString> out = new ArrayList<ByteString>(strings.length);
public static List<ByteString> byteStringList(String... strings) {
List<ByteString> result = new ArrayList<ByteString>(strings.length);
for (String string : strings) {
out.add(ByteString.encodeUtf8(string));
result.add(ByteString.encodeUtf8(string));
}
return out;
return result;
}
}

View File

@@ -29,6 +29,7 @@ public interface FrameReader extends Closeable {
public interface Handler {
void data(boolean inFinished, int streamId, InputStream in, int length) throws IOException;
/**
* Create or update incoming headers, creating the corresponding streams
* if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
@@ -38,10 +39,9 @@ public interface FrameReader extends Closeable {
* @param inFinished true if the sender will not send further frames.
* @param streamId the stream owning these headers.
* @param associatedStreamId the stream that triggered the sender to create
* this stream.
* this stream.
* @param priority or -1 for no priority. For SPDY, priorities range from 0
* (highest) thru 7 (lowest). For HTTP/2.0, priorities range from 0
* @param nameValueBlock
* (highest) thru 7 (lowest). For HTTP/2.0, priorities range from 0
*/
void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
int priority, List<ByteString> nameValueBlock, HeadersMode headersMode);

View File

@@ -5,7 +5,6 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
@@ -16,22 +15,27 @@ import java.util.List;
final class HpackDraft05 {
// Visible for testing.
static class HeaderEntry implements Cloneable {
static class HeaderEntry {
final ByteString name;
final ByteString value;
final int size;
// read when in headerTable
boolean referenced = true;
HeaderEntry(ByteString name, ByteString value) {
this.name = name;
this.value = value;
this.size = 32 + name.size() + value.size();
this(name, value, 32 + name.size() + value.size());
}
public HeaderEntry(String name, String value) {
HeaderEntry(String name, String value) {
this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
}
private HeaderEntry(ByteString name, ByteString value, int size) {
this.name = name;
this.value = value;
this.size = size;
}
/** Adds name and value, if this entry is referenced. */
void addTo(List<ByteString> out) {
if (!referenced) return;
@@ -40,19 +44,15 @@ final class HpackDraft05 {
}
@Override public HeaderEntry clone() {
try {
return (HeaderEntry) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
return new HeaderEntry(name, value, size);
}
}
static final int PREFIX_6_BITS = 0x3f;
static final int PREFIX_7_BITS = 0x7f;
static final int PREFIX_8_BITS = 0xff;
private static final int PREFIX_6_BITS = 0x3f;
private static final int PREFIX_7_BITS = 0x7f;
private static final int PREFIX_8_BITS = 0xff;
static final List<HeaderEntry> STATIC_HEADER_TABLE = Arrays.asList(
private static final HeaderEntry[] STATIC_HEADER_TABLE = new HeaderEntry[] {
new HeaderEntry(":authority", ""),
new HeaderEntry(":method", "GET"),
new HeaderEntry(":method", "POST"),
@@ -113,7 +113,7 @@ final class HpackDraft05 {
new HeaderEntry("vary", ""),
new HeaderEntry("via", ""),
new HeaderEntry("www-authenticate", "")
);
};
private HpackDraft05() {
}
@@ -126,7 +126,7 @@ final class HpackDraft05 {
private long bytesLeft = 0;
// Visible for testing.
final List<HeaderEntry> headerTable = new ArrayList<HeaderEntry>(); // TODO: default capacity?
final List<HeaderEntry> headerTable = new ArrayList<HeaderEntry>(5); // average of 5 headers
final BitSet staticReferenceSet = new BitSet();
long headerTableSize = 0;
long maxHeaderTableSize = 4096; // TODO: needs to come from SETTINGS_HEADER_TABLE_SIZE.
@@ -180,7 +180,7 @@ final class HpackDraft05 {
public void emitReferenceSet() {
for (int i = staticReferenceSet.nextSetBit(0); i != -1;
i = staticReferenceSet.nextSetBit(i + 1)) {
STATIC_HEADER_TABLE.get(i).addTo(emittedHeaders);
STATIC_HEADER_TABLE[i].addTo(emittedHeaders);
}
for (int i = headerTable.size() - 1; i != -1; i--) {
headerTable.get(i).addTo(emittedHeaders);
@@ -202,7 +202,7 @@ final class HpackDraft05 {
if (maxHeaderTableSize == 0) {
staticReferenceSet.set(index - headerTable.size());
} else {
HeaderEntry staticEntry = STATIC_HEADER_TABLE.get(index - headerTable.size());
HeaderEntry staticEntry = STATIC_HEADER_TABLE[index - headerTable.size()];
insertIntoHeaderTable(-1, staticEntry.clone());
}
} else if (!headerTable.get(index).referenced) {
@@ -246,7 +246,7 @@ final class HpackDraft05 {
private ByteString getName(int index) {
if (isStaticHeader(index)) {
return STATIC_HEADER_TABLE.get(index - headerTable.size()).name;
return STATIC_HEADER_TABLE[index - headerTable.size()].name;
} else {
return headerTable.get(index).name;
}

View File

@@ -72,6 +72,16 @@ public class ByteStringTest {
assertByteArraysEquals(new byte[] { 0x62, 0x63 }, out.toByteArray());
}
@Test public void concat() {
assertEquals(ByteString.of(), ByteString.concat());
assertEquals(ByteString.of(), ByteString.concat(ByteString.EMPTY));
assertEquals(ByteString.of(), ByteString.concat(ByteString.EMPTY, ByteString.EMPTY));
ByteString foo = ByteString.encodeUtf8("foo");
ByteString bar = ByteString.encodeUtf8("bar");
assertEquals(foo, ByteString.concat(foo));
assertEquals(ByteString.encodeUtf8("foobar"), ByteString.concat(foo, bar));
}
private static void assertByteArraysEquals(byte[] a, byte[] b) {
assertEquals(Arrays.toString(a), Arrays.toString(b));
}

View File

@@ -26,7 +26,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.asByteStringList;
import static com.squareup.okhttp.internal.Util.byteStringList;
import static org.junit.Assert.assertEquals;
public class HpackDraft05Test {
@@ -45,7 +45,7 @@ public class HpackDraft05Test {
@Test public void tooLargeToHPackIsStillEmitted() throws IOException {
char[] tooLarge = new char[4096];
Arrays.fill(tooLarge, 'a');
final List<ByteString> sentHeaders = asByteStringList("foo", new String(tooLarge));
final List<ByteString> sentHeaders = byteStringList("foo", new String(tooLarge));
ByteArrayOutputStream out = literalHeaders(sentHeaders);
bytesIn.set(out.toByteArray());
@@ -80,7 +80,7 @@ public class HpackDraft05Test {
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, "custom-key", "custom-header", 55, true);
assertEquals(asByteStringList("custom-key", "custom-header"), hpackReader.getAndReset());
assertEquals(byteStringList("custom-key", "custom-header"), hpackReader.getAndReset());
}
/**
@@ -100,7 +100,7 @@ public class HpackDraft05Test {
assertEquals(0, hpackReader.headerTable.size());
assertEquals(asByteStringList(":path", "/sample/path"), hpackReader.getAndReset());
assertEquals(byteStringList(":path", "/sample/path"), hpackReader.getAndReset());
}
/**
@@ -122,7 +122,7 @@ public class HpackDraft05Test {
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, ":method", "GET", 42, true);
assertEquals(asByteStringList(":method", "GET"), hpackReader.getAndReset());
assertEquals(byteStringList(":method", "GET"), hpackReader.getAndReset());
}
/**
@@ -142,7 +142,7 @@ public class HpackDraft05Test {
// Not buffered in header table.
assertEquals(0, hpackReader.headerTable.size());
assertEquals(asByteStringList(":method", "GET"), hpackReader.getAndReset());
assertEquals(byteStringList(":method", "GET"), hpackReader.getAndReset());
}
/**
@@ -208,10 +208,10 @@ public class HpackDraft05Test {
assertEquals(180, hpackReader.headerTableSize);
// Decoded header set:
assertEquals(asByteStringList( //
":method", "GET", //
":scheme", "http", //
":path", "/", //
assertEquals(byteStringList(
":method", "GET",
":scheme", "http",
":path", "/",
":authority", "www.example.com"), hpackReader.getAndReset());
}
@@ -253,11 +253,11 @@ public class HpackDraft05Test {
assertEquals(233, hpackReader.headerTableSize);
// Decoded header set:
assertEquals(asByteStringList( //
":method", "GET", //
":scheme", "http", //
":path", "/", //
":authority", "www.example.com", //
assertEquals(byteStringList(
":method", "GET",
":scheme", "http",
":path", "/",
":authority", "www.example.com",
"cache-control", "no-cache"), hpackReader.getAndReset());
}
@@ -322,11 +322,11 @@ public class HpackDraft05Test {
// Decoded header set:
// TODO: order is not correct per docs, but then again, the spec doesn't require ordering.
assertEquals(asByteStringList( //
":method", "GET", //
":authority", "www.example.com", //
":scheme", "https", //
":path", "/index.html", //
assertEquals(byteStringList(
":method", "GET",
":authority", "www.example.com",
":scheme", "https",
":path", "/index.html",
"custom-key", "custom-value"), hpackReader.getAndReset());
}
@@ -394,7 +394,7 @@ public class HpackDraft05Test {
}
@Test public void headersRoundTrip() throws IOException {
List<ByteString> sentHeaders = asByteStringList("name", "value");
List<ByteString> sentHeaders = byteStringList("name", "value");
hpackWriter.writeHeaders(sentHeaders);
ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
HpackDraft05.Reader reader = new HpackDraft05.Reader(new DataInputStream(bytesIn));

View File

@@ -23,7 +23,7 @@ import java.io.IOException;
import java.util.List;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.asByteStringList;
import static com.squareup.okhttp.internal.Util.byteStringList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -32,7 +32,7 @@ public class Http20Draft09Test {
static final int expectedStreamId = 15;
@Test public void onlyOneLiteralHeadersFrame() throws IOException {
final List<ByteString> sentHeaders = asByteStringList("name", "value");
final List<ByteString> sentHeaders = byteStringList("name", "value");
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
@@ -74,7 +74,7 @@ public class Http20Draft09Test {
// Write the first headers frame.
{
byte[] headerBytes = literalHeaders(asByteStringList("foo", "bar"));
byte[] headerBytes = literalHeaders(byteStringList("foo", "bar"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft09.TYPE_HEADERS);
dataOut.write(0); // no flags
@@ -84,7 +84,7 @@ public class Http20Draft09Test {
// Write the continuation frame, specifying no more frames are expected.
{
byte[] headerBytes = literalHeaders(asByteStringList("baz", "qux"));
byte[] headerBytes = literalHeaders(byteStringList("baz", "qux"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft09.TYPE_CONTINUATION);
dataOut.write(Http20Draft09.FLAG_END_HEADERS | Http20Draft09.FLAG_END_STREAM);
@@ -106,7 +106,7 @@ public class Http20Draft09Test {
assertEquals(expectedStreamId, streamId);
assertEquals(-1, associatedStreamId);
assertEquals(-1, priority);
assertEquals(asByteStringList("foo", "bar", "baz", "qux"), nameValueBlock);
assertEquals(byteStringList("foo", "bar", "baz", "qux"), nameValueBlock);
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
}
});

View File

@@ -30,7 +30,7 @@ import org.junit.After;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.UTF_8;
import static com.squareup.okhttp.internal.Util.asByteStringList;
import static com.squareup.okhttp.internal.Util.byteStringList;
import static com.squareup.okhttp.internal.spdy.ErrorCode.CANCEL;
import static com.squareup.okhttp.internal.spdy.ErrorCode.FLOW_CONTROL_ERROR;
import static com.squareup.okhttp.internal.spdy.ErrorCode.INTERNAL_ERROR;
@@ -68,15 +68,15 @@ public final class SpdyConnectionTest {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame()
.synReply(false, 1, asByteStringList("a", "android"));
.synReply(false, 1, byteStringList("a", "android"));
peer.sendFrame().data(true, 1, "robot".getBytes("UTF-8"));
peer.acceptFrame(); // DATA
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
assertEquals(asByteStringList("a", "android"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
assertEquals(byteStringList("a", "android"), stream.getResponseHeaders());
assertStreamData("robot", stream.getInputStream());
writeAndClose(stream, "c3po");
assertEquals(0, connection.openStreamCount());
@@ -89,20 +89,20 @@ public final class SpdyConnectionTest {
assertFalse(synStream.outFinished);
assertEquals(1, synStream.streamId);
assertEquals(0, synStream.associatedStreamId);
assertEquals(asByteStringList("b", "banana"), synStream.nameValueBlock);
assertEquals(byteStringList("b", "banana"), synStream.nameValueBlock);
MockSpdyPeer.InFrame requestData = peer.takeFrame();
assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
}
@Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception {
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("b", "banana"));
peer.sendFrame().synReply(false, 1, byteStringList("b", "banana"));
peer.play();
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), false, false);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), false, false);
assertEquals(1, connection.openStreamCount());
assertEquals(asByteStringList("b", "banana"), stream.getResponseHeaders());
assertEquals(byteStringList("b", "banana"), stream.getResponseHeaders());
assertEquals(0, connection.openStreamCount());
}
@@ -110,13 +110,13 @@ public final class SpdyConnectionTest {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // PING
peer.sendFrame().synReply(true, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(true, 1, byteStringList("a", "android"));
peer.sendFrame().ping(true, 1, 0);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
connection.newStream(asByteStringList("b", "banana"), false, true);
connection.newStream(byteStringList("b", "banana"), false, true);
assertEquals(1, connection.openStreamCount());
connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received.
assertEquals(0, connection.openStreamCount());
@@ -131,7 +131,7 @@ public final class SpdyConnectionTest {
@Test public void serverCreatesStreamAndClientReplies() throws Exception {
// write the mocking script
peer.sendFrame().synStream(false, false, 2, 0, 5, 129, asByteStringList("a", "android"));
peer.sendFrame().synStream(false, false, 2, 0, 5, 129, byteStringList("a", "android"));
peer.acceptFrame(); // SYN_REPLY
peer.play();
@@ -140,10 +140,10 @@ public final class SpdyConnectionTest {
IncomingStreamHandler handler = new IncomingStreamHandler() {
@Override public void receive(SpdyStream stream) throws IOException {
receiveCount.incrementAndGet();
assertEquals(asByteStringList("a", "android"), stream.getRequestHeaders());
assertEquals(byteStringList("a", "android"), stream.getRequestHeaders());
assertEquals(null, stream.getErrorCode());
assertEquals(5, stream.getPriority());
stream.reply(asByteStringList("b", "banana"), true);
stream.reply(byteStringList("b", "banana"), true);
}
};
new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build();
@@ -154,13 +154,13 @@ public final class SpdyConnectionTest {
assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode);
assertFalse(reply.inFinished);
assertEquals(2, reply.streamId);
assertEquals(asByteStringList("b", "banana"), reply.nameValueBlock);
assertEquals(byteStringList("b", "banana"), reply.nameValueBlock);
assertEquals(1, receiveCount.get());
}
@Test public void replyWithNoData() throws Exception {
// write the mocking script
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, asByteStringList("a", "android"));
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, byteStringList("a", "android"));
peer.acceptFrame(); // SYN_REPLY
peer.play();
@@ -168,7 +168,7 @@ public final class SpdyConnectionTest {
final AtomicInteger receiveCount = new AtomicInteger();
IncomingStreamHandler handler = new IncomingStreamHandler() {
@Override public void receive(SpdyStream stream) throws IOException {
stream.reply(asByteStringList("b", "banana"), false);
stream.reply(byteStringList("b", "banana"), false);
receiveCount.incrementAndGet();
}
};
@@ -179,7 +179,7 @@ public final class SpdyConnectionTest {
assertEquals(TYPE_HEADERS, reply.type);
assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode);
assertTrue(reply.inFinished);
assertEquals(asByteStringList("b", "banana"), reply.nameValueBlock);
assertEquals(byteStringList("b", "banana"), reply.nameValueBlock);
assertEquals(1, receiveCount.get());
}
@@ -329,7 +329,7 @@ public final class SpdyConnectionTest {
@Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception {
// write the mocking script
peer.sendFrame().synReply(false, 42, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 42, byteStringList("a", "android"));
peer.acceptFrame(); // RST_STREAM
peer.sendFrame().ping(false, 2, 0);
peer.acceptFrame(); // PING
@@ -350,7 +350,7 @@ public final class SpdyConnectionTest {
@Test public void clientClosesClientOutputStream() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("b", "banana"));
peer.sendFrame().synReply(false, 1, byteStringList("b", "banana"));
peer.acceptFrame(); // TYPE_DATA
peer.acceptFrame(); // TYPE_DATA with FLAG_FIN
peer.acceptFrame(); // PING
@@ -361,7 +361,7 @@ public final class SpdyConnectionTest {
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, false);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, false);
OutputStream out = stream.getOutputStream();
out.write("square".getBytes(UTF_8));
out.flush();
@@ -407,7 +407,7 @@ public final class SpdyConnectionTest {
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, true);
OutputStream out = stream.getOutputStream();
connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received.
try {
@@ -449,7 +449,7 @@ public final class SpdyConnectionTest {
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), false, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), false, true);
InputStream in = stream.getInputStream();
OutputStream out = stream.getOutputStream();
in.close();
@@ -494,7 +494,7 @@ public final class SpdyConnectionTest {
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, true);
InputStream in = stream.getInputStream();
OutputStream out = stream.getOutputStream();
in.close();
@@ -530,7 +530,7 @@ public final class SpdyConnectionTest {
@Test public void serverClosesClientInputStream() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("b", "banana"));
peer.sendFrame().synReply(false, 1, byteStringList("b", "banana"));
peer.sendFrame().data(true, 1, "square".getBytes(UTF_8));
peer.play();
@@ -538,7 +538,7 @@ public final class SpdyConnectionTest {
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), false, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), false, true);
InputStream in = stream.getInputStream();
assertStreamData("square", in);
assertEquals(0, connection.openStreamCount());
@@ -554,17 +554,17 @@ public final class SpdyConnectionTest {
@Test public void remoteDoubleSynReply() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
peer.acceptFrame(); // PING
peer.sendFrame().synReply(false, 1, asByteStringList("b", "banana"));
peer.sendFrame().synReply(false, 1, byteStringList("b", "banana"));
peer.sendFrame().ping(true, 1, 0);
peer.acceptFrame(); // RST_STREAM
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("c", "cola"), true, true);
assertEquals(asByteStringList("a", "android"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("c", "cola"), true, true);
assertEquals(byteStringList("a", "android"), stream.getResponseHeaders());
connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received.
try {
stream.getInputStream().read();
@@ -587,9 +587,9 @@ public final class SpdyConnectionTest {
@Test public void remoteDoubleSynStream() throws Exception {
// write the mocking script
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, asByteStringList("a", "android"));
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, byteStringList("a", "android"));
peer.acceptFrame(); // SYN_REPLY
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, asByteStringList("b", "banana"));
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, byteStringList("b", "banana"));
peer.acceptFrame(); // RST_STREAM
peer.play();
@@ -598,9 +598,9 @@ public final class SpdyConnectionTest {
IncomingStreamHandler handler = new IncomingStreamHandler() {
@Override public void receive(SpdyStream stream) throws IOException {
receiveCount.incrementAndGet();
assertEquals(asByteStringList("a", "android"), stream.getRequestHeaders());
assertEquals(byteStringList("a", "android"), stream.getRequestHeaders());
assertEquals(null, stream.getErrorCode());
stream.reply(asByteStringList("c", "cola"), true);
stream.reply(byteStringList("c", "cola"), true);
}
};
new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build();
@@ -619,7 +619,7 @@ public final class SpdyConnectionTest {
@Test public void remoteSendsDataAfterInFinished() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
peer.sendFrame().data(true, 1, "robot".getBytes("UTF-8"));
peer.sendFrame().data(true, 1, "c3po".getBytes("UTF-8")); // Ignored.
peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded.
@@ -628,8 +628,8 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
assertEquals(asByteStringList("a", "android"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
assertEquals(byteStringList("a", "android"), stream.getResponseHeaders());
assertStreamData("robot", stream.getInputStream());
// verify the peer received what was expected
@@ -644,7 +644,7 @@ public final class SpdyConnectionTest {
@Test public void remoteSendsTooMuchData() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("b", "banana"));
peer.sendFrame().synReply(false, 1, byteStringList("b", "banana"));
peer.sendFrame().data(false, 1, new byte[64 * 1024 + 1]);
peer.acceptFrame(); // RST_STREAM
peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded.
@@ -653,8 +653,8 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, true);
assertEquals(asByteStringList("b", "banana"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, true);
assertEquals(byteStringList("b", "banana"), stream.getResponseHeaders());
// verify the peer received what was expected
MockSpdyPeer.InFrame synStream = peer.takeFrame();
@@ -679,7 +679,7 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, true);
try {
stream.getResponseHeaders();
fail();
@@ -709,8 +709,8 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream1 = connection.newStream(asByteStringList("a", "android"), true, true);
SpdyStream stream2 = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream1 = connection.newStream(byteStringList("a", "android"), true, true);
SpdyStream stream2 = connection.newStream(byteStringList("b", "banana"), true, true);
connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received.
stream1.getOutputStream().write("abc".getBytes(UTF_8));
try {
@@ -722,7 +722,7 @@ public final class SpdyConnectionTest {
stream1.getOutputStream().write("def".getBytes(UTF_8));
stream1.getOutputStream().close();
try {
connection.newStream(asByteStringList("c", "cola"), true, true);
connection.newStream(byteStringList("c", "cola"), true, true);
fail();
} catch (IOException expected) {
assertEquals("shutdown", expected.getMessage());
@@ -747,13 +747,13 @@ public final class SpdyConnectionTest {
peer.acceptFrame(); // SYN_STREAM 1
peer.acceptFrame(); // GOAWAY
peer.acceptFrame(); // PING
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, asByteStringList("b", "b")); // Should be ignored!
peer.sendFrame().synStream(false, false, 2, 0, 0, 0, byteStringList("b", "b")); // Should be ignored!
peer.sendFrame().ping(true, 1, 0);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
connection.newStream(asByteStringList("a", "android"), true, true);
connection.newStream(byteStringList("a", "android"), true, true);
Ping ping = connection.ping();
connection.shutdown(PROTOCOL_ERROR);
assertEquals(1, connection.openStreamCount());
@@ -800,12 +800,12 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("a", "android"), true, true);
SpdyStream stream = connection.newStream(byteStringList("a", "android"), true, true);
assertEquals(1, connection.openStreamCount());
connection.close();
assertEquals(0, connection.openStreamCount());
try {
connection.newStream(asByteStringList("b", "banana"), true, true);
connection.newStream(byteStringList("b", "banana"), true, true);
fail();
} catch (IOException expected) {
assertEquals("shutdown", expected.getMessage());
@@ -850,14 +850,14 @@ public final class SpdyConnectionTest {
@Test public void readTimeoutExpires() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
peer.acceptFrame(); // PING
peer.sendFrame().ping(true, 1, 0);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
stream.setReadTimeout(1000);
InputStream in = stream.getInputStream();
long startNanos = System.nanoTime();
@@ -880,16 +880,16 @@ public final class SpdyConnectionTest {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // PING
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().headers(1, asByteStringList("c", "c3po"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
peer.sendFrame().headers(1, byteStringList("c", "c3po"));
peer.sendFrame().ping(true, 1, 0);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
connection.ping().roundTripTime(); // Ensure that the HEADERS has been received.
assertEquals(asByteStringList("a", "android", "c", "c3po"), stream.getResponseHeaders());
assertEquals(byteStringList("a", "android", "c", "c3po"), stream.getResponseHeaders());
// verify the peer received what was expected
MockSpdyPeer.InFrame synStream = peer.takeFrame();
@@ -903,14 +903,14 @@ public final class SpdyConnectionTest {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // PING
peer.sendFrame().headers(1, asByteStringList("c", "c3po"));
peer.sendFrame().headers(1, byteStringList("c", "c3po"));
peer.acceptFrame(); // RST_STREAM
peer.sendFrame().ping(true, 1, 0);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
connection.ping().roundTripTime(); // Ensure that the HEADERS has been received.
try {
stream.getResponseHeaders();
@@ -933,7 +933,7 @@ public final class SpdyConnectionTest {
@Test public void readSendsWindowUpdate() throws Exception {
// Write the mocking script.
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
for (int i = 0; i < 3; i++) {
peer.sendFrame().data(false, 1, new byte[WINDOW_UPDATE_THRESHOLD]);
peer.acceptFrame(); // WINDOW UPDATE
@@ -943,8 +943,8 @@ public final class SpdyConnectionTest {
// Play it back.
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
assertEquals(asByteStringList("a", "android"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
assertEquals(byteStringList("a", "android"), stream.getResponseHeaders());
InputStream in = stream.getInputStream();
int total = 0;
byte[] buffer = new byte[1024];
@@ -976,7 +976,7 @@ public final class SpdyConnectionTest {
// Play it back.
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
OutputStream out = stream.getOutputStream();
out.write(new byte[Settings.DEFAULT_INITIAL_WINDOW_SIZE]);
interruptAfterDelay(500);
@@ -997,14 +997,14 @@ public final class SpdyConnectionTest {
@Test public void testTruncatedDataFrame() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, asByteStringList("a", "android"));
peer.sendFrame().synReply(false, 1, byteStringList("a", "android"));
peer.sendTruncatedFrame(8 + 100).data(false, 1, new byte[1024]);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
assertEquals(asByteStringList("a", "android"), stream.getResponseHeaders());
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
assertEquals(byteStringList("a", "android"), stream.getResponseHeaders());
InputStream in = stream.getInputStream();
try {
Util.readFully(in, new byte[101]);
@@ -1035,7 +1035,7 @@ public final class SpdyConnectionTest {
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(asByteStringList("b", "banana"), true, true);
SpdyStream stream = connection.newStream(byteStringList("b", "banana"), true, true);
assertEquals("a", stream.getResponseHeaders().get(0).utf8());
assertEquals(60, stream.getResponseHeaders().get(1).size());
assertStreamData("robot", stream.getInputStream());

View File

@@ -561,7 +561,7 @@ public final class DiskLruCache implements Closeable {
*/
private boolean journalRebuildRequired() {
final int redundantOpCompactThreshold = 2000;
return redundantOpCount >= redundantOpCompactThreshold //
return redundantOpCount >= redundantOpCompactThreshold
&& redundantOpCount >= lruEntries.size();
}

View File

@@ -35,6 +35,14 @@ import java.util.Locale;
import java.util.Set;
public final class SpdyTransport implements Transport {
private static final ByteString HEADER_METHOD = ByteString.encodeUtf8(":method");
private static final ByteString HEADER_PATH = ByteString.encodeUtf8(":path");
private static final ByteString HEADER_VERSION = ByteString.encodeUtf8(":version");
private static final ByteString HEADER_HOST = ByteString.encodeUtf8(":host");
private static final ByteString HEADER_AUTHORITY = ByteString.encodeUtf8(":authority");
private static final ByteString HEADER_SCHEME = ByteString.encodeUtf8(":scheme");
private static final ByteString NULL = ByteString.of((byte) 0x00);
private final HttpEngine httpEngine;
private final SpdyConnection spdyConnection;
private SpdyStream stream;
@@ -85,21 +93,21 @@ public final class SpdyTransport implements Transport {
Headers headers = request.headers();
// TODO: make the known header names constants.
List<ByteString> result = new ArrayList<ByteString>(headers.size() + 10);
result.add(ByteString.encodeUtf8(":method"));
result.add(HEADER_METHOD);
result.add(ByteString.encodeUtf8(request.method()));
result.add(ByteString.encodeUtf8(":path"));
result.add(HEADER_PATH);
result.add(ByteString.encodeUtf8(RequestLine.requestPath(request.url())));
result.add(ByteString.encodeUtf8(":version"));
result.add(HEADER_VERSION);
result.add(ByteString.encodeUtf8(version));
if (protocol.equals("spdy/3")) {
result.add(ByteString.encodeUtf8(":host"));
result.add(HEADER_HOST);
} else if (protocol.equals("HTTP-draft-09/2.0")) {
result.add(ByteString.encodeUtf8(":authority"));
result.add(HEADER_AUTHORITY);
} else {
throw new AssertionError();
}
result.add(ByteString.encodeUtf8(HttpEngine.hostHeader(request.url())));
result.add(ByteString.encodeUtf8(":scheme"));
result.add(HEADER_SCHEME);
result.add(ByteString.encodeUtf8(request.url().getProtocol()));
Set<ByteString> names = new LinkedHashSet<ByteString>();
@@ -119,18 +127,19 @@ public final class SpdyTransport implements Transport {
|| name.equals(":scheme")) {
continue;
}
ByteString valueBytes = ByteString.encodeUtf8(value);
// If we haven't seen this name before, add the pair to the end of the list...
if (names.add(ByteString.encodeUtf8(name))) {
result.add(ByteString.encodeUtf8(name));
result.add(ByteString.encodeUtf8(value));
result.add(valueBytes);
continue;
}
// ...otherwise concatenate the existing values and this value.
for (int j = 0; j < result.size(); j += 2) {
if (result.get(j).utf8Equals(name)) {
result.set(j + 1, ByteString.encodeUtf8(result.get(j + 1).utf8() + "\0" + value));
result.set(j + 1, ByteString.concat(result.get(j + 1), NULL, valueBytes));
break;
}
}

View File

@@ -20,20 +20,19 @@ import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.ByteString;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.asByteStringList;
import static com.squareup.okhttp.internal.Util.byteStringList;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertEquals;
public final class HeadersTest {
@Test public void parseNameValueBlock() throws IOException {
List<ByteString> nameValueBlock = asByteStringList( //
"cache-control", "no-cache, no-store", //
"set-cookie", "Cookie1\u0000Cookie2", //
":status", "200 OK", //
List<ByteString> nameValueBlock = byteStringList(
"cache-control", "no-cache, no-store",
"set-cookie", "Cookie1\u0000Cookie2",
":status", "200 OK",
":version", "HTTP/1.1");
Request request = new Request.Builder().url("http://square.com/").build();
Response response =
@@ -57,9 +56,9 @@ public final class HeadersTest {
}
@Test public void readNameValueBlockDropsForbiddenHeadersSpdy3() throws IOException {
List<ByteString> nameValueBlock = asByteStringList( //
":status", "200 OK", //
":version", "HTTP/1.1", //
List<ByteString> nameValueBlock = byteStringList(
":status", "200 OK",
":version", "HTTP/1.1",
"connection", "close");
Request request = new Request.Builder().url("http://square.com/").build();
Response response =
@@ -71,9 +70,9 @@ public final class HeadersTest {
}
@Test public void readNameValueBlockDropsForbiddenHeadersHttp2() throws IOException {
List<ByteString> nameValueBlock = asByteStringList( //
":status", "200 OK", //
":version", "HTTP/1.1", //
List<ByteString> nameValueBlock = byteStringList(
":status", "200 OK",
":version", "HTTP/1.1",
"connection", "close");
Request request = new Request.Builder().url("http://square.com/").build();
Response response =
@@ -93,14 +92,14 @@ public final class HeadersTest {
.header(":status", "200 OK")
.build();
List<ByteString> nameValueBlock = SpdyTransport.writeNameValueBlock(request, "spdy/3", "HTTP/1.1");
List<ByteString> expected = asByteStringList( //
":method", "GET", //
":path", "/", //
":version", "HTTP/1.1", //
":host", "square.com", //
":scheme", "http", //
"cache-control", "no-cache, no-store", //
"set-cookie", "Cookie1\u0000Cookie2", //
List<ByteString> expected = byteStringList(
":method", "GET",
":path", "/",
":version", "HTTP/1.1",
":host", "square.com",
":scheme", "http",
"cache-control", "no-cache, no-store",
"set-cookie", "Cookie1\u0000Cookie2",
":status", "200 OK");
assertEquals(expected, nameValueBlock);
}
@@ -111,11 +110,11 @@ public final class HeadersTest {
.header("Connection", "close")
.header("Transfer-Encoding", "chunked")
.build();
List<ByteString> expected = asByteStringList( //
":method", "GET", //
":path", "/", //
":version", "HTTP/1.1", //
":host", "square.com", //
List<ByteString> expected = byteStringList(
":method", "GET",
":path", "/",
":version", "HTTP/1.1",
":host", "square.com",
":scheme", "http");
assertEquals(expected, SpdyTransport.writeNameValueBlock(request, "spdy/3", "HTTP/1.1"));
}
@@ -126,11 +125,11 @@ public final class HeadersTest {
.header("Connection", "upgrade")
.header("Upgrade", "websocket")
.build();
List<ByteString> expected = asByteStringList( //
":method", "GET", //
":path", "/", //
":version", "HTTP/1.1", //
":authority", "square.com", //
List<ByteString> expected = byteStringList(
":method", "GET",
":path", "/",
":version", "HTTP/1.1",
":authority", "square.com",
":scheme", "http");
assertEquals(expected,
SpdyTransport.writeNameValueBlock(request, "HTTP-draft-09/2.0", "HTTP/1.1"));