Upgrade OkHttp to newer code
This update includes changes up to
68affbd24d63620e1785ea847f1936760947b9ae / master /
14th March 2014.
Importantly, it contains a DiskLruCache fix which
broke several apps.
https://github.com/square/okhttp/issues/646
Change-Id: I8a489e6d0937a58fad10423154bad939ea4da868
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a34e2d2..1fc6899 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,23 @@
Change Log
==========
+## Version 1.5.1
+
+_2014-03-11_
+
+ * Fix 1.5.0 regression where connections should not have been recycled.
+ * Fix 1.5.0 regression where transparent Gzip was broken by attempting to
+ recover from another I/O failure.
+ * Fix problems where spdy/3.1 headers may not have been compressed properly.
+ * Fix problems with spdy/3.1 and http/2 where the wrong window size was being
+ used.
+ * Fix 1.5.0 regression where conditional cache responses could corrupt the
+ connection pool.
+
+
## Version 1.5.0
-_2014-03-05_
+_2014-03-07_
##### OkHttp no longer uses the default SSL context.
@@ -11,8 +25,8 @@
Applications that want to use the global SSL context with OkHttp should configure their
OkHttpClient instances with the following:
-```
- okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
+```java
+okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
```
A simpler solution is to avoid the shared default SSL socket factory. Instead, if you
@@ -39,6 +53,7 @@
* Fix: Don't do DNS lookups on invalid hosts.
* Fix: Exhaust the underlying stream when reading gzip streams.
* Fix: Support the `PATCH` method.
+ * Fix: Support request bodies on `DELETE` method.
* Fix: Drop the `okhttp-protocols` module.
* Internal: Replaced internal byte array buffers with pooled buffers ("OkBuffer").
diff --git a/README.md b/README.md
index 2e298ea..cd3bd02 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@
[1]: http://square.github.io/okhttp
- [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=okhttp&v=LATEST&c=jar-with-dependencies
+ [2]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=okhttp&v=LATEST
[3]: http://wiki.eclipse.org/Jetty/Feature/NPN
[4]: https://code.google.com/p/vogar/
[5]: http://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=com.squareup.okhttp&a=mockwebserver&v=LATEST
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/AsyncApiTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/AsyncApiTest.java
index a3dcf5c..bd9ed97 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/AsyncApiTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/AsyncApiTest.java
@@ -152,9 +152,11 @@
assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
}
- @Test public void cache() throws Exception {
+ @Test public void conditionalCacheHit() throws Exception {
server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+ server.enqueue(new MockResponse()
+ .clearHeaders()
+ .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
server.play();
client.setOkResponseCache(cache);
@@ -174,6 +176,28 @@
assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
}
+ @Test public void conditionalCacheMiss() throws Exception {
+ server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
+ server.enqueue(new MockResponse().setBody("B"));
+ server.play();
+
+ client.setOkResponseCache(cache);
+
+ Request request1 = new Request.Builder()
+ .url(server.getUrl("/"))
+ .build();
+ client.enqueue(request1, receiver);
+ receiver.await(request1.url()).assertCode(200).assertBody("A");
+ assertNull(server.takeRequest().getHeader("If-None-Match"));
+
+ Request request2 = new Request.Builder()
+ .url(server.getUrl("/"))
+ .build();
+ client.enqueue(request2, receiver);
+ receiver.await(request2.url()).assertCode(200).assertBody("B");
+ assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
+ }
+
@Test public void redirect() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(301)
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/RecordingReceiver.java b/okhttp-tests/src/test/java/com/squareup/okhttp/RecordingReceiver.java
index f5b54ee..9bc8475 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/RecordingReceiver.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/RecordingReceiver.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -73,8 +74,10 @@
public synchronized RecordedResponse await(URL url) throws Exception {
long timeoutMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime()) + TIMEOUT_MILLIS;
while (true) {
- for (RecordedResponse recordedResponse : responses) {
+ for (Iterator<RecordedResponse> i = responses.iterator(); i.hasNext(); ) {
+ RecordedResponse recordedResponse = i.next();
if (recordedResponse.request.url().equals(url)) {
+ i.remove();
return recordedResponse;
}
}
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/SyncApiTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/SyncApiTest.java
index ae3c743..1153299 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/SyncApiTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/SyncApiTest.java
@@ -138,9 +138,11 @@
assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
}
- @Test public void cache() throws Exception {
+ @Test public void conditionalCacheHit() throws Exception {
server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
- server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+ server.enqueue(new MockResponse()
+ .clearHeaders()
+ .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
server.play();
client.setOkResponseCache(cache);
@@ -154,6 +156,22 @@
assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
}
+ @Test public void conditionalCacheMiss() throws Exception {
+ server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
+ server.enqueue(new MockResponse().setBody("B"));
+ server.play();
+
+ client.setOkResponseCache(cache);
+
+ onSuccess(new Request.Builder().url(server.getUrl("/")).build())
+ .assertCode(200).assertBody("A");
+ assertNull(server.takeRequest().getHeader("If-None-Match"));
+
+ onSuccess(new Request.Builder().url(server.getUrl("/")).build())
+ .assertCode(200).assertBody("B");
+ assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
+ }
+
@Test public void redirect() throws Exception {
server.enqueue(new MockResponse()
.setResponseCode(301)
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
index 1380ff0..b6edaae 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
@@ -16,6 +16,7 @@
package com.squareup.okhttp.internal.http;
+import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.HttpResponseCache;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkResponseCache;
@@ -966,6 +967,22 @@
assertEquals("DEFDEFDEF", readAscii(openConnection(server.getUrl("/"))));
}
+ @Test public void conditionalCacheHitIsNotDoublePooled() throws Exception {
+ server.enqueue(new MockResponse().addHeader("ETag: v1").setBody("A"));
+ server.enqueue(new MockResponse()
+ .clearHeaders()
+ .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+ server.play();
+
+ ConnectionPool pool = ConnectionPool.getDefault();
+ pool.evictAll();
+ client.setConnectionPool(pool);
+
+ assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
+ assertEquals("A", readAscii(openConnection(server.getUrl("/"))));
+ assertEquals(1, client.getConnectionPool().getConnectionCount());
+ }
+
@Test public void expiresDateBeforeModifiedDate() throws Exception {
assertConditionallyCached(
new MockResponse().addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
index 4e791ad..5e86c8e 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java
@@ -16,6 +16,7 @@
package com.squareup.okhttp.internal.http;
+import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.HttpResponseCache;
import com.squareup.okhttp.OkAuthenticator.Credential;
import com.squareup.okhttp.OkHttpClient;
@@ -1172,6 +1173,41 @@
assertEquals(1, server.takeRequest().getSequenceNumber());
}
+ @Test public void transparentGzipWorksAfterExceptionRecovery() throws Exception {
+ server.enqueue(new MockResponse()
+ .setBody("a")
+ .setSocketPolicy(SHUTDOWN_INPUT_AT_END));
+ server.enqueue(new MockResponse()
+ .addHeader("Content-Encoding: gzip")
+ .setBody(gzip("b".getBytes(UTF_8))));
+ server.play();
+
+ // Seed the pool with a bad connection.
+ assertContent("a", client.open(server.getUrl("/")));
+
+ // This connection will need to be recovered. When it is, transparent gzip should still work!
+ assertContent("b", client.open(server.getUrl("/")));
+
+ assertEquals(0, server.takeRequest().getSequenceNumber());
+ assertEquals(0, server.takeRequest().getSequenceNumber()); // Connection is not pooled.
+ }
+
+ @Test public void endOfStreamResponseIsNotPooled() throws Exception {
+ server.enqueue(new MockResponse()
+ .setBody("{}")
+ .clearHeaders()
+ .setSocketPolicy(DISCONNECT_AT_END));
+ server.play();
+
+ ConnectionPool pool = ConnectionPool.getDefault();
+ pool.evictAll();
+ client.setConnectionPool(pool);
+
+ HttpURLConnection connection = client.open(server.getUrl("/"));
+ assertContent("{}", connection);
+ assertEquals(0, client.getConnectionPool().getConnectionCount());
+ }
+
@Test public void earlyDisconnectDoesntHarmPoolingWithChunkedEncoding() throws Exception {
testEarlyDisconnectDoesntHarmPooling(TransferKind.CHUNKED);
}
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
index a44af61..fd6007d 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java
@@ -45,7 +45,7 @@
private final List<OutFrame> outFrames = new ArrayList<OutFrame>();
private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
private int port;
- private final ExecutorService executor = Executors.newCachedThreadPool(
+ private final ExecutorService executor = Executors.newSingleThreadExecutor(
Util.threadFactory("MockSpdyPeer", false));
private ServerSocket serverSocket;
private Socket socket;
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
index 44459b8..294684f 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SettingsTest.java
@@ -17,6 +17,7 @@
import org.junit.Test;
+import static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_BANDWIDTH;
import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_RETRANS_RATE;
import static com.squareup.okhttp.internal.spdy.Settings.MAX_CONCURRENT_STREAMS;
@@ -68,9 +69,10 @@
settings.set(DOWNLOAD_RETRANS_RATE, 0, 97);
assertEquals(97, settings.getDownloadRetransRate(-3));
- assertEquals(-1, settings.getInitialWindowSize());
+ assertEquals(DEFAULT_INITIAL_WINDOW_SIZE,
+ settings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
settings.set(Settings.INITIAL_WINDOW_SIZE, 0, 108);
- assertEquals(108, settings.getInitialWindowSize());
+ assertEquals(108, settings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
assertEquals(-3, settings.getClientCertificateVectorSize(-3));
settings.set(Settings.CLIENT_CERTIFICATE_VECTOR_SIZE, 0, 117);
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
index fbfb2f5..2ef127e 100644
--- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
+++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java
@@ -39,6 +39,7 @@
import static com.squareup.okhttp.internal.spdy.ErrorCode.PROTOCOL_ERROR;
import static com.squareup.okhttp.internal.spdy.ErrorCode.REFUSED_STREAM;
import static com.squareup.okhttp.internal.spdy.ErrorCode.STREAM_IN_USE;
+import static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_DATA;
import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_GOAWAY;
@@ -47,7 +48,6 @@
import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_RST_STREAM;
import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_SETTINGS;
import static com.squareup.okhttp.internal.spdy.Spdy3.TYPE_WINDOW_UPDATE;
-import static com.squareup.okhttp.internal.spdy.SpdyConnection.INITIAL_WINDOW_SIZE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -301,7 +301,7 @@
// This stream was created *after* the connection settings were adjusted.
SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true);
- assertEquals(3368, connection.peerSettings.getInitialWindowSize());
+ assertEquals(3368, connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
assertEquals(1684, connection.bytesLeftInWriteWindow); // initial wasn't affected.
// New Stream is has the most recent initial window size.
assertEquals(3368, stream.bytesLeftInWriteWindow);
@@ -405,6 +405,35 @@
}
}
+ @Test public void clearSettingsBeforeMerge() throws Exception {
+ // write the mocking script
+ Settings settings1 = new Settings();
+ settings1.set(Settings.UPLOAD_BANDWIDTH, PERSIST_VALUE, 100);
+ settings1.set(Settings.DOWNLOAD_BANDWIDTH, PERSIST_VALUE, 200);
+ settings1.set(Settings.DOWNLOAD_RETRANS_RATE, 0, 300);
+ peer.sendFrame().settings(settings1);
+ peer.sendFrame().ping(false, 2, 0);
+ peer.acceptFrame();
+ peer.play();
+
+ // play it back
+ SpdyConnection connection = connection(peer, SPDY3);
+
+ peer.takeFrame(); // Guarantees that the Settings frame has been processed.
+
+ // fake a settings frame with clear flag set.
+ Settings settings2 = new Settings();
+ settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600);
+ connection.readerRunnable.settings(true, settings2);
+
+ synchronized (connection) {
+ assertEquals(-1, connection.peerSettings.getUploadBandwidth(-1));
+ assertEquals(-1, connection.peerSettings.getDownloadBandwidth(-1));
+ assertEquals(-1, connection.peerSettings.getDownloadRetransRate(-1));
+ assertEquals(600, connection.peerSettings.getMaxConcurrentStreams(-1));
+ }
+ }
+
@Test public void bogusDataFrameDoesNotDisruptConnection() throws Exception {
// write the mocking script
peer.sendFrame().data(true, 41, new OkBuffer().writeUtf8("bogus"));
@@ -1042,7 +1071,7 @@
throws IOException, InterruptedException {
peer.setVariantAndClient(variant, false);
- int windowUpdateThreshold = INITIAL_WINDOW_SIZE / 2;
+ int windowUpdateThreshold = DEFAULT_INITIAL_WINDOW_SIZE / 2;
// Write the mocking script.
peer.acceptFrame(); // SYN_STREAM
@@ -1061,7 +1090,6 @@
// Play it back.
SpdyConnection connection = connection(peer, variant);
- connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, INITIAL_WINDOW_SIZE);
SpdyStream stream = connection.newStream(headerEntries("b", "banana"), false, true);
assertEquals(0, stream.unacknowledgedBytesRead);
assertEquals(headerEntries("a", "android"), stream.getResponseHeaders());
@@ -1153,7 +1181,7 @@
}
@Test public void writeAwaitsWindowUpdate() throws Exception {
- int framesThatFillWindow = roundUp(INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize());
+ int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize());
// Write the mocking script. This accepts more data frames than necessary!
peer.acceptFrame(); // SYN_STREAM
@@ -1167,7 +1195,7 @@
SpdyConnection connection = connection(peer, SPDY3);
SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true);
BufferedSink out = Okio.buffer(stream.getSink());
- out.write(new byte[INITIAL_WINDOW_SIZE]);
+ out.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]);
out.flush();
// Check that we've filled the window for both the stream and also the connection.
@@ -1195,7 +1223,7 @@
}
@Test public void initialSettingsWithWindowSizeAdjustsConnection() throws Exception {
- int framesThatFillWindow = roundUp(INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize());
+ int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, HTTP_20_DRAFT_09.maxFrameSize());
// Write the mocking script. This accepts more data frames than necessary!
peer.acceptFrame(); // SYN_STREAM
@@ -1209,7 +1237,7 @@
SpdyConnection connection = connection(peer, SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "apple"), true, true);
BufferedSink out = Okio.buffer(stream.getSink());
- out.write(new byte[INITIAL_WINDOW_SIZE]);
+ out.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]);
out.flush();
// write 1 more than the window size
@@ -1222,7 +1250,7 @@
// Receiving a Settings with a larger window size will unblock the streams.
Settings initial = new Settings();
- initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, INITIAL_WINDOW_SIZE + 1);
+ initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, DEFAULT_INITIAL_WINDOW_SIZE + 1);
connection.readerRunnable.settings(false, initial);
assertEquals(1, connection.bytesLeftInWriteWindow);
@@ -1236,7 +1264,7 @@
// Settings after the initial do not affect the connection window size.
Settings next = new Settings();
- next.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, INITIAL_WINDOW_SIZE + 2);
+ next.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, DEFAULT_INITIAL_WINDOW_SIZE + 2);
connection.readerRunnable.settings(false, next);
assertEquals(0, connection.bytesLeftInWriteWindow); // connection wasn't affected.
@@ -1264,7 +1292,7 @@
}
@Test public void blockedStreamDoesntStarveNewStream() throws Exception {
- int framesThatFillWindow = roundUp(INITIAL_WINDOW_SIZE, SPDY3.maxFrameSize());
+ int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, SPDY3.maxFrameSize());
// Write the mocking script. This accepts more data frames than necessary!
peer.acceptFrame(); // SYN_STREAM on stream 1
@@ -1279,7 +1307,7 @@
SpdyConnection connection = connection(peer, SPDY3);
SpdyStream stream1 = connection.newStream(headerEntries("a", "apple"), true, true);
BufferedSink out1 = Okio.buffer(stream1.getSink());
- out1.write(new byte[INITIAL_WINDOW_SIZE]);
+ out1.write(new byte[DEFAULT_INITIAL_WINDOW_SIZE]);
out1.flush();
// Check that we've filled the window for both the stream and also the connection.
@@ -1300,7 +1328,7 @@
assertEquals(0, connection.bytesLeftInWriteWindow);
assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow);
- assertEquals(INITIAL_WINDOW_SIZE - 3, connection.getStream(3).bytesLeftInWriteWindow);
+ assertEquals(DEFAULT_INITIAL_WINDOW_SIZE - 3, connection.getStream(3).bytesLeftInWriteWindow);
}
@Test public void maxFrameSizeHonored() throws Exception {
diff --git a/okhttp/pom.xml b/okhttp/pom.xml
index 25e165f..c7e3ec4 100644
--- a/okhttp/pom.xml
+++ b/okhttp/pom.xml
@@ -18,11 +18,6 @@
<artifactId>okio</artifactId>
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>com.squareup.okhttp</groupId>
- <artifactId>okhttp-protocols</artifactId>
- <version>${project.version}</version>
- </dependency>
</dependencies>
<build>
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java
index d9115a9..55884dc 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java
@@ -25,16 +25,10 @@
import com.squareup.okhttp.internal.spdy.SpdyConnection;
import java.io.Closeable;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.net.Proxy;
import java.net.Socket;
-import java.net.SocketTimeoutException;
import javax.net.ssl.SSLSocket;
-import okio.BufferedSink;
-import okio.BufferedSource;
import okio.ByteString;
-import okio.Okio;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
@@ -70,10 +64,6 @@
private final Route route;
private Socket socket;
- private InputStream in;
- private OutputStream out;
- private BufferedSource source;
- private BufferedSink sink;
private boolean connected = false;
private HttpConnection httpConnection;
private SpdyConnection spdyConnection;
@@ -94,14 +84,11 @@
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
Platform.get().connectSocket(socket, route.inetSocketAddress, connectTimeout);
socket.setSoTimeout(readTimeout);
- in = socket.getInputStream();
- out = socket.getOutputStream();
if (route.address.sslSocketFactory != null) {
upgradeToTls(tunnelRequest);
} else {
- initSourceAndSink();
- httpConnection = new HttpConnection(pool, this, source, sink);
+ httpConnection = new HttpConnection(pool, this, socket);
}
connected = true;
}
@@ -128,19 +115,19 @@
platform.supportTlsIntolerantServer(sslSocket);
}
- boolean useNpn = route.modernTls && (// Contains a spdy variant.
- route.address.protocols.contains(Protocol.HTTP_2)
- || route.address.protocols.contains(Protocol.SPDY_3)
- );
-
- if (useNpn) {
- if (route.address.protocols.contains(Protocol.HTTP_2) // Contains both spdy variants.
- && route.address.protocols.contains(Protocol.SPDY_3)) {
+ boolean useNpn = false;
+ if (route.modernTls) {
+ boolean http2 = route.address.protocols.contains(Protocol.HTTP_2);
+ boolean spdy3 = route.address.protocols.contains(Protocol.SPDY_3);
+ if (http2 && spdy3) {
platform.setNpnProtocols(sslSocket, Protocol.HTTP2_SPDY3_AND_HTTP);
- } else if (route.address.protocols.contains(Protocol.HTTP_2)) {
+ useNpn = true;
+ } else if (http2) {
platform.setNpnProtocols(sslSocket, Protocol.HTTP2_AND_HTTP_11);
- } else {
+ useNpn = true;
+ } else if (spdy3) {
platform.setNpnProtocols(sslSocket, Protocol.SPDY3_AND_HTTP11);
+ useNpn = true;
}
}
@@ -152,10 +139,7 @@
throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
}
- out = sslSocket.getOutputStream();
- in = sslSocket.getInputStream();
handshake = Handshake.get(sslSocket.getSession());
- initSourceAndSink();
ByteString maybeProtocol;
Protocol selectedProtocol = Protocol.HTTP_11;
@@ -165,11 +149,11 @@
if (selectedProtocol.spdyVariant) {
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
- spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, source, sink)
+ spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, socket)
.protocol(selectedProtocol).build();
spdyConnection.sendConnectionHeader();
} else {
- httpConnection = new HttpConnection(pool, this, source, sink);
+ httpConnection = new HttpConnection(pool, this, socket);
}
}
@@ -206,28 +190,8 @@
* #isAlive()}; callers should check {@link #isAlive()} first.
*/
public boolean isReadable() {
- if (source == null) {
- return true; // Optimistic.
- }
- if (isSpdy()) {
- return true; // Optimistic. We can't test SPDY because its streams are in use.
- }
- try {
- int readTimeout = socket.getSoTimeout();
- try {
- socket.setSoTimeout(1);
- if (source.exhausted()) {
- return false; // Stream is exhausted; socket is closed.
- }
- return true;
- } finally {
- socket.setSoTimeout(readTimeout);
- }
- } catch (SocketTimeoutException ignored) {
- return true; // Read timed out; socket is good.
- } catch (IOException e) {
- return false; // Couldn't read; socket is closed.
- }
+ if (httpConnection != null) return httpConnection.isReadable();
+ return true; // SPDY connections, and connections before connect() are both optimistic.
}
public void resetIdleStartTime() {
@@ -320,9 +284,7 @@
* retried if the proxy requires authorization.
*/
private void makeTunnel(TunnelRequest tunnelRequest) throws IOException {
- BufferedSource tunnelSource = Okio.buffer(Okio.source(in));
- BufferedSink tunnelSink = Okio.buffer(Okio.sink(out));
- HttpConnection tunnelConnection = new HttpConnection(pool, this, tunnelSource, tunnelSink);
+ HttpConnection tunnelConnection = new HttpConnection(pool, this, socket);
Request request = tunnelRequest.getRequest();
String requestLine = tunnelRequest.requestLine();
while (true) {
@@ -335,7 +297,7 @@
case HTTP_OK:
// Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If that
// happens, then we will have buffered bytes that are needed by the SSLSocket!
- if (tunnelSource.buffer().size() > 0) {
+ if (tunnelConnection.bufferSize() > 0) {
throw new IOException("TLS tunnel buffered too many bytes!");
}
return;
@@ -352,9 +314,4 @@
}
}
}
-
- private void initSourceAndSink() throws IOException {
- source = Okio.buffer(Okio.source(in));
- sink = Okio.buffer(Okio.sink(out));
- }
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
index ad0a229..c6edf4b 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java
@@ -413,29 +413,29 @@
public Entry(InputStream in) throws IOException {
try {
BufferedSource source = Okio.buffer(Okio.source(in));
- url = source.readUtf8Line(true);
- requestMethod = source.readUtf8Line(true);
+ url = source.readUtf8LineStrict();
+ requestMethod = source.readUtf8LineStrict();
Headers.Builder varyHeadersBuilder = new Headers.Builder();
int varyRequestHeaderLineCount = readInt(source);
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
- varyHeadersBuilder.addLine(source.readUtf8Line(true));
+ varyHeadersBuilder.addLine(source.readUtf8LineStrict());
}
varyHeaders = varyHeadersBuilder.build();
- statusLine = source.readUtf8Line(true);
+ statusLine = source.readUtf8LineStrict();
Headers.Builder responseHeadersBuilder = new Headers.Builder();
int responseHeaderLineCount = readInt(source);
for (int i = 0; i < responseHeaderLineCount; i++) {
- responseHeadersBuilder.addLine(source.readUtf8Line(true));
+ responseHeadersBuilder.addLine(source.readUtf8LineStrict());
}
responseHeaders = responseHeadersBuilder.build();
if (isHttps()) {
- String blank = source.readUtf8Line(true);
+ String blank = source.readUtf8LineStrict();
if (blank.length() > 0) {
throw new IOException("expected \"\" but was \"" + blank + "\"");
}
- String cipherSuite = source.readUtf8Line(true);
+ String cipherSuite = source.readUtf8LineStrict();
List<Certificate> peerCertificates = readCertificateList(source);
List<Certificate> localCertificates = readCertificateList(source);
handshake = Handshake.get(cipherSuite, peerCertificates, localCertificates);
@@ -494,7 +494,7 @@
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
List<Certificate> result = new ArrayList<Certificate>(length);
for (int i = 0; i < length; i++) {
- String line = source.readUtf8Line(true);
+ String line = source.readUtf8LineStrict();
byte[] bytes = ByteString.decodeBase64(line).toByteArray();
result.add(certificateFactory.generateCertificate(new ByteArrayInputStream(bytes)));
}
@@ -537,7 +537,7 @@
}
private static int readInt(BufferedSource source) throws IOException {
- String line = source.readUtf8Line(true);
+ String line = source.readUtf8LineStrict();
try {
return Integer.parseInt(line);
} catch (NumberFormatException e) {
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Job.java b/okhttp/src/main/java/com/squareup/okhttp/Job.java
index 54dff42..64ce188 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Job.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Job.java
@@ -149,10 +149,7 @@
if (redirect == null) {
engine.releaseConnection();
return response.newBuilder()
- // Cache body includes original content-length and content-type data.
- .body(engine.responseSource().usesCache()
- ? engine.getResponse().body()
- : new RealResponseBody(response, engine.getResponseBody()))
+ .body(new RealResponseBody(response, engine.getResponseBody()))
.redirectedBy(redirectedBy)
.build();
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
index 9540608..bb4be68 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
@@ -40,7 +40,17 @@
import javax.net.ssl.SSLSocketFactory;
import okio.ByteString;
-/** Configures and creates HTTP connections. */
+/**
+ * Configures and creates HTTP connections. Most applications can use a single
+ * OkHttpClient for all of their HTTP requests - benefiting from a shared
+ * response cache, thread pool, connection re-use, etc.
+ *
+ * Instances of OkHttpClient are intended to be fully configured before they're
+ * shared - once shared they should be treated as immutable and can safely be used
+ * to concurrently open new connections. If required, threads can call
+ * {@link #clone()} to make a shallow copy of the OkHttpClient that can be
+ * safely modified with further configuration changes.
+ */
public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable {
private final RouteDatabase routeDatabase;
@@ -279,8 +289,9 @@
}
/**
- * @deprecated OkHttp 2 enforces an enumeration of {@link Protocol protocols}
- * that can be selected. Please switch to {@link #setProtocols(java.util.List)}.
+ * @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol
+ * protocols} that can be selected. Please switch to {@link
+ * #setProtocols(java.util.List)}.
*/
@Deprecated
public OkHttpClient setTransports(List<String> transports) {
@@ -337,8 +348,9 @@
}
/**
- * @deprecated OkHttp 2 enforces an enumeration of {@link Protocol protocols}
- * that can be selected. Please switch to {@link #getProtocols()}.
+ * @deprecated OkHttp 1.5 enforces an enumeration of {@link Protocol
+ * protocols} that can be selected. Please switch to {@link
+ * #getProtocols()}.
*/
@Deprecated
public List<String> getTransports() {
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java b/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
index 569da9b..7f4fa11 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/DiskLruCache.java
@@ -243,11 +243,11 @@
private void readJournal() throws IOException {
BufferedSource source = Okio.buffer(Okio.source(new FileInputStream(journalFile)));
try {
- String magic = source.readUtf8Line(true);
- String version = source.readUtf8Line(true);
- String appVersionString = source.readUtf8Line(true);
- String valueCountString = source.readUtf8Line(true);
- String blank = source.readUtf8Line(true);
+ String magic = source.readUtf8LineStrict();
+ String version = source.readUtf8LineStrict();
+ String appVersionString = source.readUtf8LineStrict();
+ String valueCountString = source.readUtf8LineStrict();
+ String blank = source.readUtf8LineStrict();
if (!MAGIC.equals(magic)
|| !VERSION_1.equals(version)
|| !Integer.toString(appVersion).equals(appVersionString)
@@ -260,7 +260,7 @@
int lineCount = 0;
while (true) {
try {
- readJournalLine(source.readUtf8Line(true));
+ readJournalLine(source.readUtf8LineStrict());
lineCount++;
} catch (EOFException endOfJournal) {
break;
@@ -376,8 +376,9 @@
}
private static void deleteIfExists(File file) throws IOException {
- if (file.exists() && !file.delete()) {
- throw new IOException();
+ // If delete() fails, make sure it's because the file didn't exist!
+ if (!file.delete() && file.exists()) {
+ throw new IOException("failed to delete " + file);
}
}
@@ -477,7 +478,7 @@
* Returns the maximum number of bytes that this cache should use to store
* its data.
*/
- public long getMaxSize() {
+ public synchronized long getMaxSize() {
return maxSize;
}
@@ -580,9 +581,7 @@
for (int i = 0; i < valueCount; i++) {
File file = entry.getCleanFile(i);
- if (!file.delete()) {
- throw new IOException("failed to delete " + file);
- }
+ deleteIfExists(file);
size -= entry.lengths[i];
entry.lengths[i] = 0;
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java b/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java
index d1bf250..51e04e8 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/Util.java
@@ -265,7 +265,7 @@
}
/** Returns an immutable list containing {@code elements}. */
- public static <T> List<T> immutableList(T[] elements) {
+ public static <T> List<T> immutableList(T... elements) {
return Collections.unmodifiableList(Arrays.asList(elements.clone()));
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java
index 7639bfa..b12b12d 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java
@@ -27,6 +27,7 @@
import java.net.CacheRequest;
import java.net.ProtocolException;
import java.net.Socket;
+import java.net.SocketTimeoutException;
import okio.BufferedSink;
import okio.BufferedSource;
import okio.Deadline;
@@ -72,18 +73,20 @@
private final ConnectionPool pool;
private final Connection connection;
+ private final Socket socket;
private final BufferedSource source;
private final BufferedSink sink;
private int state = STATE_IDLE;
private int onIdle = ON_IDLE_HOLD;
- public HttpConnection(ConnectionPool pool, Connection connection, BufferedSource source,
- BufferedSink sink) {
+ public HttpConnection(ConnectionPool pool, Connection connection, Socket socket)
+ throws IOException {
this.pool = pool;
this.connection = connection;
- this.source = source;
- this.sink = sink;
+ this.socket = socket;
+ this.source = Okio.buffer(Okio.source(socket.getInputStream()));
+ this.sink = Okio.buffer(Okio.sink(socket.getOutputStream()));
}
/**
@@ -123,6 +126,31 @@
sink.flush();
}
+ /** Returns the number of buffered bytes immediately readable. */
+ public long bufferSize() {
+ return source.buffer().size();
+ }
+
+ /** Test for a stale socket. */
+ public boolean isReadable() {
+ try {
+ int readTimeout = socket.getSoTimeout();
+ try {
+ socket.setSoTimeout(1);
+ if (source.exhausted()) {
+ return false; // Stream is exhausted; socket is closed.
+ }
+ return true;
+ } finally {
+ socket.setSoTimeout(readTimeout);
+ }
+ } catch (SocketTimeoutException ignored) {
+ return true; // Read timed out; socket is good.
+ } catch (IOException e) {
+ return false; // Couldn't read; socket is closed.
+ }
+ }
+
/** Returns bytes of a request header for sending on an HTTP transport. */
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
@@ -144,7 +172,7 @@
}
while (true) {
- String statusLineString = source.readUtf8Line(true);
+ String statusLineString = source.readUtf8LineStrict();
StatusLine statusLine = new StatusLine(statusLineString);
Response.Builder responseBuilder = new Response.Builder()
@@ -165,7 +193,7 @@
/** Reads headers or trailers into {@code builder}. */
public void readHeaders(Headers.Builder builder) throws IOException {
// parse the result headers until the first blank line
- for (String line; (line = source.readUtf8Line(true)).length() != 0; ) {
+ for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) {
builder.addLine(line);
}
}
@@ -177,7 +205,6 @@
* that may never occur.
*/
public boolean discard(Source in, int timeoutMillis) {
- Socket socket = connection.getSocket();
try {
int socketTimeout = socket.getSoTimeout();
socket.setSoTimeout(timeoutMillis);
@@ -356,7 +383,7 @@
* Closes the cache entry and makes the socket available for reuse. This
* should be invoked when the end of the body has been reached.
*/
- protected final void endOfInput() throws IOException {
+ protected final void endOfInput(boolean recyclable) throws IOException {
if (state != STATE_READING_RESPONSE_BODY) throw new IllegalStateException("state: " + state);
if (cacheRequest != null) {
@@ -364,7 +391,7 @@
}
state = STATE_IDLE;
- if (onIdle == ON_IDLE_POOL) {
+ if (recyclable && onIdle == ON_IDLE_POOL) {
onIdle = ON_IDLE_HOLD; // Set the on idle policy back to the default.
pool.recycle(connection);
} else if (onIdle == ON_IDLE_CLOSE) {
@@ -402,7 +429,7 @@
super(cacheRequest);
bytesRemaining = length;
if (bytesRemaining == 0) {
- endOfInput();
+ endOfInput(true);
}
}
@@ -421,7 +448,7 @@
bytesRemaining -= read;
cacheWrite(sink, read);
if (bytesRemaining == 0) {
- endOfInput();
+ endOfInput(true);
}
return read;
}
@@ -478,9 +505,9 @@
private void readChunkSize() throws IOException {
// read the suffix of the previous chunk
if (bytesRemainingInChunk != NO_CHUNK_YET) {
- source.readUtf8Line(true);
+ source.readUtf8LineStrict();
}
- String chunkSizeString = source.readUtf8Line(true);
+ String chunkSizeString = source.readUtf8LineStrict();
int index = chunkSizeString.indexOf(";");
if (index != -1) {
chunkSizeString = chunkSizeString.substring(0, index);
@@ -495,7 +522,7 @@
Headers.Builder trailersBuilder = new Headers.Builder();
readHeaders(trailersBuilder);
httpEngine.receiveHeaders(trailersBuilder.build());
- endOfInput();
+ endOfInput(true);
}
}
@@ -530,7 +557,7 @@
long read = source.read(sink, byteCount);
if (read == -1) {
inputExhausted = true;
- endOfInput();
+ endOfInput(false);
return -1;
}
cacheWrite(sink, read);
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
index 20bc3b7..1ce0f44 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -98,6 +98,7 @@
*/
public final boolean bufferRequestBody;
+ private Request originalRequest;
private Request request;
private Sink requestBodyOut;
private BufferedSink bufferedRequestBody;
@@ -135,6 +136,7 @@
public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
Connection connection, RouteSelector routeSelector, RetryableSink requestBodyOut) {
this.client = client;
+ this.originalRequest = request;
this.request = request;
this.bufferRequestBody = bufferRequestBody;
this.connection = connection;
@@ -328,7 +330,7 @@
Connection connection = close();
// For failure recovery, use the same route selector with a new connection.
- return new HttpEngine(client, request, bufferRequestBody, connection, routeSelector,
+ return new HttpEngine(client, originalRequest, bufferRequestBody, connection, routeSelector,
(RetryableSink) requestBodyOut);
}
@@ -369,7 +371,7 @@
* be released immediately.
*/
public final void releaseConnection() throws IOException {
- if (transport != null) {
+ if (transport != null && connection != null) {
transport.releaseConnectionOnIdle();
}
connection = null;
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
index f3afcac..e775d34 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java
@@ -50,6 +50,25 @@
import static com.squareup.okhttp.internal.spdy.Header.VERSION;
public final class SpdyTransport implements Transport {
+ /** See http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request. */
+ private static final List<ByteString> SPDY_3_PROHIBITED_HEADERS = Util.immutableList(
+ ByteString.encodeUtf8("connection"),
+ ByteString.encodeUtf8("host"),
+ ByteString.encodeUtf8("keep-alive"),
+ ByteString.encodeUtf8("proxy-connection"),
+ ByteString.encodeUtf8("transfer-encoding"));
+
+ /** See http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3. */
+ private static final List<ByteString> HTTP_2_PROHIBITED_HEADERS = Util.immutableList(
+ ByteString.encodeUtf8("connection"),
+ ByteString.encodeUtf8("host"),
+ ByteString.encodeUtf8("keep-alive"),
+ ByteString.encodeUtf8("proxy-connection"),
+ ByteString.encodeUtf8("te"),
+ ByteString.encodeUtf8("transfer-encoding"),
+ ByteString.encodeUtf8("encoding"),
+ ByteString.encodeUtf8("upgrade"));
+
private final HttpEngine httpEngine;
private final SpdyConnection spdyConnection;
private SpdyStream stream;
@@ -206,32 +225,13 @@
/** When true, this header should not be emitted or consumed. */
private static boolean isProhibitedHeader(Protocol protocol, ByteString name) {
- boolean prohibited = false;
if (protocol == Protocol.SPDY_3) {
- // http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1#TOC-3.2.1-Request
- if (name.equalsAscii("connection")
- || name.equalsAscii("host")
- || name.equalsAscii("keep-alive")
- || name.equalsAscii("proxy-connection")
- || name.equalsAscii("transfer-encoding")) {
- prohibited = true;
- }
+ return SPDY_3_PROHIBITED_HEADERS.contains(name);
} else if (protocol == Protocol.HTTP_2) {
- // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3
- if (name.equalsAscii("connection")
- || name.equalsAscii("host")
- || name.equalsAscii("keep-alive")
- || name.equalsAscii("proxy-connection")
- || name.equalsAscii("te")
- || name.equalsAscii("transfer-encoding")
- || name.equalsAscii("encoding")
- || name.equalsAscii("upgrade")) {
- prohibited = true;
- }
+ return HTTP_2_PROHIBITED_HEADERS.contains(name);
} else {
throw new AssertionError(protocol);
}
- return prohibited;
}
/** An HTTP message body terminated by the end of the underlying stream. */
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
index c05d6b1..bf43088 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/Settings.java
@@ -22,6 +22,12 @@
* Settings are {@link com.squareup.okhttp.internal.spdy.SpdyConnection connection} scoped.
*/
final class Settings {
+ /**
+ * From the SPDY/3 and HTTP/2 specs, the default initial window size for all
+ * streams is 64 KiB. (Chrome 25 uses 10 MiB).
+ */
+ static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
+
/** Peer request to clear durable settings. */
static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
@@ -171,11 +177,9 @@
return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
}
- // TODO: honor this setting in http/2.
- /** Returns -1 if unset. */
- int getInitialWindowSize() {
+ int getInitialWindowSize(int defaultValue) {
int bit = 1 << INITIAL_WINDOW_SIZE;
- return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : -1;
+ return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
}
/** spdy/3 only. */
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
index fab8698..da7c4e1 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
@@ -21,6 +21,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
@@ -38,6 +39,8 @@
import okio.OkBuffer;
import okio.Okio;
+import static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
/**
* A socket connection to a remote peer. A connection hosts streams which can
* send and receive data.
@@ -89,8 +92,6 @@
private final PushObserver pushObserver;
private int nextPingId;
- static final int INITIAL_WINDOW_SIZE = 65535;
-
/**
* The total number of bytes consumed by the application, but not yet
* acknowledged by sending a {@code WINDOW_UPDATE} frame on this connection.
@@ -107,15 +108,12 @@
/** Settings we communicate to the peer. */
// TODO: Do we want to dynamically adjust settings, or KISS and only set once?
- final Settings okHttpSettings = new Settings()
- .set(Settings.INITIAL_WINDOW_SIZE, 0, INITIAL_WINDOW_SIZE);
- // TODO: implement stream limit
+ final Settings okHttpSettings = new Settings();
// okHttpSettings.set(Settings.MAX_CONCURRENT_STREAMS, 0, max);
/** Settings we receive from the peer. */
// TODO: MWS will need to guard on this setting before attempting to push.
- final Settings peerSettings = new Settings()
- .set(Settings.INITIAL_WINDOW_SIZE, 0, INITIAL_WINDOW_SIZE);
+ final Settings peerSettings = new Settings();
private boolean receivedInitialPeerSettings = false;
final FrameReader frameReader;
@@ -151,7 +149,7 @@
} else {
throw new AssertionError(protocol);
}
- bytesLeftInWriteWindow = peerSettings.getInitialWindowSize();
+ bytesLeftInWriteWindow = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
frameReader = variant.newReader(builder.source, client);
frameWriter = variant.newWriter(builder.sink, client);
maxFrameSize = variant.maxFrameSize();
@@ -502,19 +500,18 @@
private boolean client;
public Builder(boolean client, Socket socket) throws IOException {
- this("", client, Okio.buffer(Okio.source(socket.getInputStream())),
- Okio.buffer(Okio.sink(socket.getOutputStream())));
+ this(((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(), client, socket);
}
/**
* @param client true if this peer initiated the connection; false if this
* peer accepted the connection.
*/
- public Builder(String hostName, boolean client, BufferedSource source, BufferedSink sink) {
+ public Builder(String hostName, boolean client, Socket socket) throws IOException {
this.hostName = hostName;
this.client = client;
- this.source = source;
- this.sink = sink;
+ this.source = Okio.buffer(Okio.source(socket.getInputStream()));
+ this.sink = Okio.buffer(Okio.sink(socket.getOutputStream()));
}
public Builder handler(IncomingStreamHandler handler) {
@@ -657,16 +654,13 @@
long delta = 0;
SpdyStream[] streamsToNotify = null;
synchronized (SpdyConnection.this) {
- int priorWriteWindowSize = peerSettings.getInitialWindowSize();
- if (clearPrevious) {
- peerSettings.clear();
- } else {
- peerSettings.merge(newSettings);
- }
+ int priorWriteWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ if (clearPrevious) peerSettings.clear();
+ peerSettings.merge(newSettings);
if (getProtocol() == Protocol.HTTP_2) {
ackSettingsLater();
}
- int peerInitialWindowSize = peerSettings.getInitialWindowSize();
+ int peerInitialWindowSize = peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
if (peerInitialWindowSize != -1 && peerInitialWindowSize != priorWriteWindowSize) {
delta = peerInitialWindowSize - priorWriteWindowSize;
if (!receivedInitialPeerSettings) {
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
index d48e3dc..0fcde2d 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
@@ -28,6 +28,8 @@
import okio.Sink;
import okio.Source;
+import static com.squareup.okhttp.internal.spdy.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
+
/** A logical bidirectional stream. */
public final class SpdyStream {
// Internal state is guarded by this. No long-running or potentially
@@ -76,8 +78,10 @@
if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
this.id = id;
this.connection = connection;
- this.bytesLeftInWriteWindow = connection.peerSettings.getInitialWindowSize();
- this.source = new SpdyDataSource(connection.okHttpSettings.getInitialWindowSize());
+ this.bytesLeftInWriteWindow =
+ connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE);
+ this.source = new SpdyDataSource(
+ connection.okHttpSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE));
this.sink = new SpdyDataSink();
this.source.finished = inFinished;
this.sink.finished = outFinished;
@@ -370,7 +374,8 @@
// Flow control: notify the peer that we're ready for more data!
unacknowledgedBytesRead += read;
- if (unacknowledgedBytesRead >= connection.okHttpSettings.getInitialWindowSize() / 2) {
+ if (unacknowledgedBytesRead
+ >= connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
connection.writeWindowUpdateLater(id, unacknowledgedBytesRead);
unacknowledgedBytesRead = 0;
}
@@ -380,7 +385,7 @@
synchronized (connection) { // Multiple application threads may hit this section.
connection.unacknowledgedBytesRead += read;
if (connection.unacknowledgedBytesRead
- >= connection.okHttpSettings.getInitialWindowSize() / 2) {
+ >= connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE) / 2) {
connection.writeWindowUpdateLater(0, connection.unacknowledgedBytesRead);
connection.unacknowledgedBytesRead = 0;
}
diff --git a/okio/src/main/java/okio/BufferedSink.java b/okio/src/main/java/okio/BufferedSink.java
index e5cc42d..3066011 100644
--- a/okio/src/main/java/okio/BufferedSink.java
+++ b/okio/src/main/java/okio/BufferedSink.java
@@ -43,16 +43,28 @@
/** Encodes {@code string} in UTF-8 and writes it to this sink. */
BufferedSink writeUtf8(String string) throws IOException;
- /** Writes byte to the end of this sink. */
+ /** Writes a byte to this sink. */
BufferedSink writeByte(int b) throws IOException;
- /** Writes a Big-Endian short to the end of this sink. */
+ /** Writes a big-endian short to this sink using two bytes. */
BufferedSink writeShort(int s) throws IOException;
- /** Writes a Big-Endian int to the end of this sink. */
+ /** Writes a little-endian short to this sink using two bytes. */
+ BufferedSink writeShortLe(int s) throws IOException;
+
+ /** Writes a big-endian int to this sink using four bytes. */
BufferedSink writeInt(int i) throws IOException;
- /** Writes complete segments to the sink. Like {@link #flush}, but weaker. */
+ /** Writes a little-endian int to this sink using four bytes. */
+ BufferedSink writeIntLe(int i) throws IOException;
+
+ /** Writes a big-endian long to this sink using eight bytes. */
+ BufferedSink writeLong(long v) throws IOException;
+
+ /** Writes a little-endian long to this sink using eight bytes. */
+ BufferedSink writeLongLe(long v) throws IOException;
+
+ /** Writes complete segments to this sink. Like {@link #flush}, but weaker. */
BufferedSink emitCompleteSegments() throws IOException;
/** Returns an output stream that writes to this sink. */
diff --git a/okio/src/main/java/okio/BufferedSource.java b/okio/src/main/java/okio/BufferedSource.java
index 678721b..2b48823 100644
--- a/okio/src/main/java/okio/BufferedSource.java
+++ b/okio/src/main/java/okio/BufferedSource.java
@@ -28,9 +28,8 @@
OkBuffer buffer();
/**
- * Returns true if there are no more bytes in the buffer or the source. This
- * will block until there are bytes to read or the source is definitely
- * exhausted.
+ * Returns true if there are no more bytes in this source. This will block
+ * until there are bytes to read or the source is definitely exhausted.
*/
boolean exhausted() throws IOException;
@@ -41,25 +40,31 @@
*/
void require(long byteCount) throws IOException;
- /** Removes a byte from the front of this buffer and returns it. */
+ /** Removes a byte from this source and returns it. */
byte readByte() throws IOException;
- /** Removes a Big-Endian short from the front of this buffer and returns it. */
+ /** Removes two bytes from this source and returns a big-endian short. */
short readShort() throws IOException;
- /** Removes a Little-Endian short from the front of this buffer and returns it. */
- int readShortLe() throws IOException;
+ /** Removes two bytes from this source and returns a little-endian short. */
+ short readShortLe() throws IOException;
- /** Removes a Big-Endian int from the front of this buffer and returns it. */
+ /** Removes four bytes from this source and returns a big-endian int. */
int readInt() throws IOException;
- /** Removes a Little-Endian int from the front of this buffer and returns it. */
+ /** Removes four bytes from this source and returns a little-endian int. */
int readIntLe() throws IOException;
+ /** Removes eight bytes from this source and returns a big-endian long. */
+ long readLong() throws IOException;
+
+ /** Removes eight bytes from this source and returns a little-endian long. */
+ long readLongLe() throws IOException;
+
/**
- * Reads and discards {@code byteCount} bytes from {@code source} using {@code
- * buffer} as a buffer. Throws an {@link java.io.EOFException} if the source
- * is exhausted before the requested bytes can be skipped.
+ * Reads and discards {@code byteCount} bytes from this source. Throws an
+ * {@link java.io.EOFException} if the source is exhausted before the
+ * requested bytes can be skipped.
*/
void skip(long byteCount) throws IOException;
@@ -77,28 +82,32 @@
* A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
* not included in the result.
*
- * <p>This method supports two ways to handle the end of the stream:
- * <ul>
- * <li><strong>Throw on EOF.</strong> Every call must consume either '\r\n'
- * or '\n'. If these characters are absent in the stream, an {@link
- * java.io.EOFException} is thrown. Use this for machine-generated data
- * where a missing line break implies truncated input.
- * <li><strong>Don't throw, just like BufferedReader.</strong> If the source
- * doesn't end with a line break then an implicit line break is assumed.
- * Null is returned once the source is exhausted. Use this for
- * human-generated data, where a trailing line breaks are optional.
- * </ul>
+ * <p><strong>On the end of the stream this method returns null,</strong> just
+ * like {@link java.io.BufferedReader}. If the source doesn't end with a line
+ * break then an implicit line break is assumed. Null is returned once the
+ * source is exhausted. Use this for human-generated data, where a trailing
+ * line break is optional.
*/
- String readUtf8Line(boolean throwOnEof) throws IOException;
+ String readUtf8Line() throws IOException;
+
+ /**
+ * Removes and returns characters up to but not including the next line break.
+ * A line break is either {@code "\n"} or {@code "\r\n"}; these characters are
+ * not included in the result.
+ *
+ * <p><strong>On the end of the stream this method throws.</strong> Every call
+ * must consume either '\r\n' or '\n'. If these characters are absent in the
+ * stream, an {@link java.io.EOFException} is thrown. Use this for
+ * machine-generated data where a missing line break implies truncated input.
+ */
+ String readUtf8LineStrict() throws IOException;
/**
* Returns the index of {@code b} in the buffer, refilling it if necessary
* until it is found. This reads an unbounded number of bytes into the buffer.
- *
- * @throws java.io.EOFException if the stream is exhausted before the
- * requested byte is found.
+ * Returns -1 if the stream is exhausted before the requested byte is found.
*/
- long seek(byte b) throws IOException;
+ long indexOf(byte b) throws IOException;
/** Returns an input stream that reads from this source. */
InputStream inputStream();
diff --git a/okio/src/main/java/okio/ByteString.java b/okio/src/main/java/okio/ByteString.java
index 608d652..6853adb 100644
--- a/okio/src/main/java/okio/ByteString.java
+++ b/okio/src/main/java/okio/ByteString.java
@@ -120,23 +120,6 @@
}
/**
- * Returns true when {@code ascii} is not null and equals the bytes wrapped
- * by this byte string.
- */
- public boolean equalsAscii(String ascii) {
- if (ascii == null || data.length != ascii.length()) {
- return false;
- }
- if (ascii == this.utf8) { // not using String.equals to avoid looping twice.
- return true;
- }
- for (int i = 0; i < data.length; i++) {
- if (data[i] != ascii.charAt(i)) return false;
- }
- return true;
- }
-
- /**
* Reads {@code count} bytes from {@code in} and returns the result.
*
* @throws java.io.EOFException if {@code in} has fewer than {@code count}
@@ -176,18 +159,9 @@
return this;
}
- 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 new ByteString(result);
+ /** Returns the byte at {@code pos}. */
+ public byte getByte(int pos) {
+ return data[pos];
}
/**
diff --git a/okio/src/main/java/okio/DeflaterSink.java b/okio/src/main/java/okio/DeflaterSink.java
index df9398f..7249f2d 100644
--- a/okio/src/main/java/okio/DeflaterSink.java
+++ b/okio/src/main/java/okio/DeflaterSink.java
@@ -37,6 +37,7 @@
public final class DeflaterSink implements Sink {
private final BufferedSink sink;
private final Deflater deflater;
+ private boolean closed;
public DeflaterSink(Sink sink, Deflater deflater) {
this.sink = Okio.buffer(sink);
@@ -80,10 +81,13 @@
? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH)
: deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit);
- if (deflated == 0) return;
- s.limit += deflated;
- buffer.size += deflated;
- sink.emitCompleteSegments();
+ if (deflated > 0) {
+ s.limit += deflated;
+ buffer.size += deflated;
+ sink.emitCompleteSegments();
+ } else if (deflater.needsInput()) {
+ return;
+ }
}
}
@@ -93,9 +97,32 @@
}
@Override public void close() throws IOException {
- deflater.finish();
- deflate(false);
- sink.close();
+ if (closed) return;
+
+ // Emit deflated data to the underlying sink. If this fails, we still need
+ // to close the deflater and the sink; otherwise we risk leaking resources.
+ Throwable thrown = null;
+ try {
+ deflater.finish();
+ deflate(false);
+ } catch (Throwable e) {
+ thrown = e;
+ }
+
+ try {
+ deflater.end();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+
+ try {
+ sink.close();
+ } catch (Throwable e) {
+ if (thrown == null) thrown = e;
+ }
+ closed = true;
+
+ if (thrown != null) Util.sneakyRethrow(thrown);
}
@Override public Sink deadline(Deadline deadline) {
diff --git a/okio/src/main/java/okio/GzipSource.java b/okio/src/main/java/okio/GzipSource.java
index fe73c08..eae3a16 100644
--- a/okio/src/main/java/okio/GzipSource.java
+++ b/okio/src/main/java/okio/GzipSource.java
@@ -15,6 +15,7 @@
*/
package okio;
+import java.io.EOFException;
import java.io.IOException;
import java.util.zip.CRC32;
import java.util.zip.Inflater;
@@ -121,7 +122,7 @@
if (((flags >> FEXTRA) & 1) == 1) {
source.require(2);
if (fhcrc) updateCrc(source.buffer(), 0, 2);
- int xlen = source.buffer().readShortLe() & 0xffff;
+ int xlen = source.buffer().readShortLe();
source.require(xlen);
if (fhcrc) updateCrc(source.buffer(), 0, xlen);
source.skip(xlen);
@@ -132,7 +133,8 @@
// |...original file name, zero-terminated...| (more-->)
// +=========================================+
if (((flags >> FNAME) & 1) == 1) {
- long index = source.seek((byte) 0);
+ long index = source.indexOf((byte) 0);
+ if (index == -1) throw new EOFException();
if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
source.skip(index + 1);
}
@@ -142,7 +144,8 @@
// |...file comment, zero-terminated...| (more-->)
// +===================================+
if (((flags >> FCOMMENT) & 1) == 1) {
- long index = source.seek((byte) 0);
+ long index = source.indexOf((byte) 0);
+ if (index == -1) throw new EOFException();
if (fhcrc) updateCrc(source.buffer(), 0, index + 1);
source.skip(index + 1);
}
diff --git a/okio/src/main/java/okio/OkBuffer.java b/okio/src/main/java/okio/OkBuffer.java
index a9dfa2f..8dc4290 100644
--- a/okio/src/main/java/okio/OkBuffer.java
+++ b/okio/src/main/java/okio/OkBuffer.java
@@ -16,6 +16,7 @@
package okio;
import java.io.EOFException;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
@@ -26,13 +27,14 @@
import static okio.Util.UTF_8;
import static okio.Util.checkOffsetAndCount;
+import static okio.Util.reverseBytesLong;
/**
* A collection of bytes in memory.
*
* <p><strong>Moving data from one OkBuffer to another is fast.</strong> Instead
* of copying bytes from one place in memory to another, this class just changes
- * ownership of the underlying bytes.
+ * ownership of the underlying byte arrays.
*
* <p><strong>This buffer grows with your data.</strong> Just like ArrayList,
* each OkBuffer starts small. It consumes only the memory it needs to.
@@ -92,12 +94,6 @@
if (this.size < byteCount) throw new EOFException();
}
- @Override public long seek(byte b) throws EOFException {
- long index = indexOf(b);
- if (index == -1) throw new EOFException();
- return index;
- }
-
@Override public InputStream inputStream() {
return new InputStream() {
@Override public int read() {
@@ -160,18 +156,18 @@
return b;
}
- /** Returns the byte at {@code i}. */
- public byte getByte(long i) {
- checkOffsetAndCount(size, i, 1);
+ /** Returns the byte at {@code pos}. */
+ public byte getByte(long pos) {
+ checkOffsetAndCount(size, pos, 1);
for (Segment s = head; true; s = s.next) {
int segmentByteCount = s.limit - s.pos;
- if (i < segmentByteCount) return s.data[s.pos + (int) i];
- i -= segmentByteCount;
+ if (pos < segmentByteCount) return s.data[s.pos + (int) pos];
+ pos -= segmentByteCount;
}
}
@Override public short readShort() {
- if (size < 2) throw new IllegalArgumentException("size < 2: " + size);
+ if (size < 2) throw new IllegalStateException("size < 2: " + size);
Segment segment = head;
int pos = segment.pos;
@@ -200,7 +196,7 @@
}
@Override public int readInt() {
- if (size < 4) throw new IllegalArgumentException("size < 4: " + size);
+ if (size < 4) throw new IllegalStateException("size < 4: " + size);
Segment segment = head;
int pos = segment.pos;
@@ -210,14 +206,14 @@
if (limit - pos < 4) {
return (readByte() & 0xff) << 24
| (readByte() & 0xff) << 16
- | (readByte() & 0xff) << 8
+ | (readByte() & 0xff) << 8
| (readByte() & 0xff);
}
byte[] data = segment.data;
int i = (data[pos++] & 0xff) << 24
| (data[pos++] & 0xff) << 16
- | (data[pos++] & 0xff) << 8
+ | (data[pos++] & 0xff) << 8
| (data[pos++] & 0xff);
size -= 4;
@@ -231,19 +227,57 @@
return i;
}
- public int readShortLe() {
+ @Override public long readLong() {
+ if (size < 8) throw new IllegalStateException("size < 8: " + size);
+
+ Segment segment = head;
+ int pos = segment.pos;
+ int limit = segment.limit;
+
+ // If the long is split across multiple segments, delegate to readInt().
+ if (limit - pos < 8) {
+ return (readInt() & 0xffffffffL) << 32
+ | (readInt() & 0xffffffffL);
+ }
+
+ byte[] data = segment.data;
+ long v = (data[pos++] & 0xffL) << 56
+ | (data[pos++] & 0xffL) << 48
+ | (data[pos++] & 0xffL) << 40
+ | (data[pos++] & 0xffL) << 32
+ | (data[pos++] & 0xffL) << 24
+ | (data[pos++] & 0xffL) << 16
+ | (data[pos++] & 0xffL) << 8
+ | (data[pos++] & 0xffL);
+ size -= 8;
+
+ if (pos == limit) {
+ head = segment.pop();
+ SegmentPool.INSTANCE.recycle(segment);
+ } else {
+ segment.pos = pos;
+ }
+
+ return v;
+ }
+
+ @Override public short readShortLe() {
return Util.reverseBytesShort(readShort());
}
- public int readIntLe() {
+ @Override public int readIntLe() {
return Util.reverseBytesInt(readInt());
}
- public ByteString readByteString(long byteCount) {
+ @Override public long readLongLe() {
+ return Util.reverseBytesLong(readLong());
+ }
+
+ @Override public ByteString readByteString(long byteCount) {
return new ByteString(readBytes(byteCount));
}
- public String readUtf8(long byteCount) {
+ @Override public String readUtf8(long byteCount) {
checkOffsetAndCount(this.size, 0, byteCount);
if (byteCount > Integer.MAX_VALUE) {
throw new IllegalArgumentException("byteCount > Integer.MAX_VALUE: " + byteCount);
@@ -268,14 +302,23 @@
return result;
}
- @Override public String readUtf8Line(boolean throwOnEof) throws EOFException {
+ @Override public String readUtf8Line() throws IOException {
long newline = indexOf((byte) '\n');
if (newline == -1) {
- if (throwOnEof) throw new EOFException();
return size != 0 ? readUtf8(size) : null;
}
+ return readUtf8Line(newline);
+ }
+
+ @Override public String readUtf8LineStrict() throws IOException {
+ long newline = indexOf((byte) '\n');
+ if (newline == -1) throw new EOFException();
+ return readUtf8Line(newline);
+ }
+
+ String readUtf8Line(long newline) {
if (newline > 0 && getByte(newline - 1) == '\r') {
// Read everything until '\r\n', then skip the '\r\n'.
String result = readUtf8((newline - 1));
@@ -319,9 +362,8 @@
/** Like {@link InputStream#read}. */
int read(byte[] sink, int offset, int byteCount) {
- if (byteCount == 0) return -1;
-
Segment s = this.head;
+ if (s == null) return -1;
int toCopy = Math.min(byteCount, s.limit - s.pos);
System.arraycopy(s.data, s.pos, sink, offset, toCopy);
@@ -345,7 +387,7 @@
}
/** Discards {@code byteCount} bytes from the head of this buffer. */
- public void skip(long byteCount) {
+ @Override public void skip(long byteCount) {
checkOffsetAndCount(this.size, 0, byteCount);
this.size -= byteCount;
@@ -362,13 +404,12 @@
}
}
- /** Appends {@code byteString} to this. */
@Override public OkBuffer write(ByteString byteString) {
return write(byteString.data, 0, byteString.data.length);
}
- /** Encodes {@code string} as UTF-8 and appends the bytes to this. */
@Override public OkBuffer writeUtf8(String string) {
+ // TODO: inline UTF-8 encoding to save allocating a byte[]?
byte[] data = string.getBytes(Util.UTF_8);
return write(data, 0, data.length);
}
@@ -393,7 +434,6 @@
return this;
}
- /** Appends a Big-Endian byte to the end of this buffer. */
@Override public OkBuffer writeByte(int b) {
Segment tail = writableSegment(1);
tail.data[tail.limit++] = (byte) b;
@@ -401,32 +441,59 @@
return this;
}
- /** Appends a Big-Endian short to the end of this buffer. */
@Override public OkBuffer writeShort(int s) {
Segment tail = writableSegment(2);
byte[] data = tail.data;
int limit = tail.limit;
- data[limit++] = (byte) ((s >> 8) & 0xff);
- data[limit++] = (byte) (s & 0xff);
+ data[limit++] = (byte) ((s >>> 8) & 0xff);
+ data[limit++] = (byte) (s & 0xff);
tail.limit = limit;
size += 2;
return this;
}
- /** Appends a Big-Endian int to the end of this buffer. */
+ @Override public BufferedSink writeShortLe(int s) {
+ return writeShort(Util.reverseBytesShort((short) s));
+ }
+
@Override public OkBuffer writeInt(int i) {
Segment tail = writableSegment(4);
byte[] data = tail.data;
int limit = tail.limit;
- data[limit++] = (byte) ((i >> 24) & 0xff);
- data[limit++] = (byte) ((i >> 16) & 0xff);
- data[limit++] = (byte) ((i >> 8) & 0xff);
- data[limit++] = (byte) (i & 0xff);
+ data[limit++] = (byte) ((i >>> 24) & 0xff);
+ data[limit++] = (byte) ((i >>> 16) & 0xff);
+ data[limit++] = (byte) ((i >>> 8) & 0xff);
+ data[limit++] = (byte) (i & 0xff);
tail.limit = limit;
size += 4;
return this;
}
+ @Override public BufferedSink writeIntLe(int i) {
+ return writeInt(Util.reverseBytesInt(i));
+ }
+
+ @Override public OkBuffer writeLong(long v) {
+ Segment tail = writableSegment(8);
+ byte[] data = tail.data;
+ int limit = tail.limit;
+ data[limit++] = (byte) ((v >>> 56L) & 0xff);
+ data[limit++] = (byte) ((v >>> 48L) & 0xff);
+ data[limit++] = (byte) ((v >>> 40L) & 0xff);
+ data[limit++] = (byte) ((v >>> 32L) & 0xff);
+ data[limit++] = (byte) ((v >>> 24L) & 0xff);
+ data[limit++] = (byte) ((v >>> 16L) & 0xff);
+ data[limit++] = (byte) ((v >>> 8L) & 0xff);
+ data[limit++] = (byte) (v & 0xff);
+ tail.limit = limit;
+ size += 8;
+ return this;
+ }
+
+ @Override public BufferedSink writeLongLe(long v) {
+ return writeLong(reverseBytesLong(v));
+ }
+
/**
* Returns a tail segment that we can write at least {@code minimumCapacity}
* bytes to, creating it if necessary.
@@ -549,11 +616,7 @@
return this;
}
- /**
- * Returns the index of {@code b} in this, or -1 if this buffer does not
- * contain {@code b}.
- */
- public long indexOf(byte b) {
+ @Override public long indexOf(byte b) {
return indexOf(b, 0);
}
diff --git a/okio/src/main/java/okio/RealBufferedSink.java b/okio/src/main/java/okio/RealBufferedSink.java
index 691c070..74454c6 100644
--- a/okio/src/main/java/okio/RealBufferedSink.java
+++ b/okio/src/main/java/okio/RealBufferedSink.java
@@ -39,55 +39,79 @@
@Override public void write(OkBuffer source, long byteCount)
throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.write(source, byteCount);
emitCompleteSegments();
}
@Override public BufferedSink write(ByteString byteString) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.write(byteString);
return emitCompleteSegments();
}
@Override public BufferedSink writeUtf8(String string) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.writeUtf8(string);
return emitCompleteSegments();
}
@Override public BufferedSink write(byte[] source) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.write(source);
return emitCompleteSegments();
}
@Override public BufferedSink write(byte[] source, int offset, int byteCount) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.write(source, offset, byteCount);
return emitCompleteSegments();
}
@Override public BufferedSink writeByte(int b) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.writeByte(b);
return emitCompleteSegments();
}
@Override public BufferedSink writeShort(int s) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.writeShort(s);
return emitCompleteSegments();
}
+ @Override public BufferedSink writeShortLe(int s) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeShortLe(s);
+ return emitCompleteSegments();
+ }
+
@Override public BufferedSink writeInt(int i) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
buffer.writeInt(i);
return emitCompleteSegments();
}
+ @Override public BufferedSink writeIntLe(int i) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeIntLe(i);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeLong(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeLong(v);
+ return emitCompleteSegments();
+ }
+
+ @Override public BufferedSink writeLongLe(long v) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
+ buffer.writeLongLe(v);
+ return emitCompleteSegments();
+ }
+
@Override public BufferedSink emitCompleteSegments() throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
long byteCount = buffer.completeSegmentByteCount();
if (byteCount > 0) sink.write(buffer, byteCount);
return this;
@@ -96,20 +120,20 @@
@Override public OutputStream outputStream() {
return new OutputStream() {
@Override public void write(int b) throws IOException {
- checkNotClosed();
+ if (closed) throw new IOException("closed");
buffer.writeByte((byte) b);
emitCompleteSegments();
}
@Override public void write(byte[] data, int offset, int byteCount) throws IOException {
- checkNotClosed();
+ if (closed) throw new IOException("closed");
buffer.write(data, offset, byteCount);
emitCompleteSegments();
}
@Override public void flush() throws IOException {
// For backwards compatibility, a flush() on a closed stream is a no-op.
- if (!RealBufferedSink.this.closed) {
+ if (!closed) {
RealBufferedSink.this.flush();
}
}
@@ -121,18 +145,11 @@
@Override public String toString() {
return RealBufferedSink.this + ".outputStream()";
}
-
- private void checkNotClosed() throws IOException {
- // By convention in java.io, IOException and not IllegalStateException is used.
- if (RealBufferedSink.this.closed) {
- throw new IOException("closed");
- }
- }
};
}
@Override public void flush() throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
if (buffer.size > 0) {
sink.write(buffer, buffer.size);
}
@@ -171,10 +188,4 @@
@Override public String toString() {
return "buffer(" + sink + ")";
}
-
- private void checkNotClosed() {
- if (closed) {
- throw new IllegalStateException("closed");
- }
- }
}
diff --git a/okio/src/main/java/okio/RealBufferedSource.java b/okio/src/main/java/okio/RealBufferedSource.java
index 9e9a34a..0189d0f 100644
--- a/okio/src/main/java/okio/RealBufferedSource.java
+++ b/okio/src/main/java/okio/RealBufferedSource.java
@@ -42,7 +42,7 @@
@Override public long read(OkBuffer sink, long byteCount) throws IOException {
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
if (buffer.size == 0) {
long read = source.read(buffer, Segment.SIZE);
@@ -54,12 +54,12 @@
}
@Override public boolean exhausted() throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
return buffer.exhausted() && source.read(buffer, Segment.SIZE) == -1;
}
@Override public void require(long byteCount) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
while (buffer.size < byteCount) {
if (source.read(buffer, Segment.SIZE) == -1) throw new EOFException();
}
@@ -80,30 +80,20 @@
return buffer.readUtf8(byteCount);
}
- @Override public String readUtf8Line(boolean throwOnEof) throws IOException {
- checkNotClosed();
- long start = 0;
- long newline;
- while ((newline = buffer.indexOf((byte) '\n', start)) == -1) {
- start = buffer.size;
- if (source.read(buffer, Segment.SIZE) == -1) {
- if (throwOnEof) throw new EOFException();
- return buffer.size != 0 ? readUtf8(buffer.size) : null;
- }
+ @Override public String readUtf8Line() throws IOException {
+ long newline = indexOf((byte) '\n');
+
+ if (newline == -1) {
+ return buffer.size != 0 ? readUtf8(buffer.size) : null;
}
- if (newline > 0 && buffer.getByte(newline - 1) == '\r') {
- // Read everything until '\r\n', then skip the '\r\n'.
- String result = readUtf8((newline - 1));
- skip(2);
- return result;
+ return buffer.readUtf8Line(newline);
+ }
- } else {
- // Read everything until '\n', then skip the '\n'.
- String result = readUtf8((newline));
- skip(1);
- return result;
- }
+ @Override public String readUtf8LineStrict() throws IOException {
+ long newline = indexOf((byte) '\n');
+ if (newline == -1L) throw new EOFException();
+ return buffer.readUtf8Line(newline);
}
@Override public short readShort() throws IOException {
@@ -111,7 +101,7 @@
return buffer.readShort();
}
- @Override public int readShortLe() throws IOException {
+ @Override public short readShortLe() throws IOException {
require(2);
return buffer.readShortLe();
}
@@ -126,8 +116,18 @@
return buffer.readIntLe();
}
+ @Override public long readLong() throws IOException {
+ require(8);
+ return buffer.readLong();
+ }
+
+ @Override public long readLongLe() throws IOException {
+ require(8);
+ return buffer.readLongLe();
+ }
+
@Override public void skip(long byteCount) throws IOException {
- checkNotClosed();
+ if (closed) throw new IllegalStateException("closed");
while (byteCount > 0) {
if (buffer.size == 0 && source.read(buffer, Segment.SIZE) == -1) {
throw new EOFException();
@@ -138,13 +138,13 @@
}
}
- @Override public long seek(byte b) throws IOException {
- checkNotClosed();
+ @Override public long indexOf(byte b) throws IOException {
+ if (closed) throw new IllegalStateException("closed");
long start = 0;
long index;
while ((index = buffer.indexOf(b, start)) == -1) {
start = buffer.size;
- if (source.read(buffer, Segment.SIZE) == -1) throw new EOFException();
+ if (source.read(buffer, Segment.SIZE) == -1) return -1L;
}
return index;
}
@@ -152,7 +152,7 @@
@Override public InputStream inputStream() {
return new InputStream() {
@Override public int read() throws IOException {
- checkNotClosed();
+ if (closed) throw new IOException("closed");
if (buffer.size == 0) {
long count = source.read(buffer, Segment.SIZE);
if (count == -1) return -1;
@@ -161,7 +161,7 @@
}
@Override public int read(byte[] data, int offset, int byteCount) throws IOException {
- checkNotClosed();
+ if (closed) throw new IOException("closed");
checkOffsetAndCount(data.length, offset, byteCount);
if (buffer.size == 0) {
@@ -173,7 +173,7 @@
}
@Override public int available() throws IOException {
- checkNotClosed();
+ if (closed) throw new IOException("closed");
return (int) Math.min(buffer.size, Integer.MAX_VALUE);
}
@@ -184,14 +184,6 @@
@Override public String toString() {
return RealBufferedSource.this + ".inputStream()";
}
-
- private void checkNotClosed() throws IOException {
- if (RealBufferedSource.this.closed) {
- // By convention in java.io, IOException and not IllegalStateException is used.
- throw new IOException("closed");
- }
- }
-
};
}
@@ -210,10 +202,4 @@
@Override public String toString() {
return "buffer(" + source + ")";
}
-
- private void checkNotClosed() {
- if (closed) {
- throw new IllegalStateException("closed");
- }
- }
}
diff --git a/okio/src/main/java/okio/Util.java b/okio/src/main/java/okio/Util.java
index e5c1ddc..4759488 100644
--- a/okio/src/main/java/okio/Util.java
+++ b/okio/src/main/java/okio/Util.java
@@ -30,17 +30,29 @@
}
}
- public static int reverseBytesShort(short s) {
+ public static short reverseBytesShort(short s) {
int i = s & 0xffff;
- return (i & 0xff00) >>> 8
- | (i & 0x00ff) << 8;
+ int reversed = (i & 0xff00) >>> 8
+ | (i & 0x00ff) << 8;
+ return (short) reversed;
}
public static int reverseBytesInt(int i) {
return (i & 0xff000000) >>> 24
- | (i & 0x00ff0000) >>> 8
- | (i & 0x0000ff00) << 8
- | (i & 0x000000ff) << 24;
+ | (i & 0x00ff0000) >>> 8
+ | (i & 0x0000ff00) << 8
+ | (i & 0x000000ff) << 24;
+ }
+
+ public static long reverseBytesLong(long v) {
+ return (v & 0xff00000000000000L) >>> 56
+ | (v & 0x00ff000000000000L) >>> 40
+ | (v & 0x0000ff0000000000L) >>> 24
+ | (v & 0x000000ff00000000L) >>> 8
+ | (v & 0x00000000ff000000L) << 8
+ | (v & 0x0000000000ff0000L) << 24
+ | (v & 0x000000000000ff00L) << 40
+ | (v & 0x00000000000000ffL) << 56;
}
/**
diff --git a/okio/src/test/java/okio/ByteStringTest.java b/okio/src/test/java/okio/ByteStringTest.java
index e714056..16b8e2d 100644
--- a/okio/src/test/java/okio/ByteStringTest.java
+++ b/okio/src/test/java/okio/ByteStringTest.java
@@ -29,6 +29,21 @@
public class ByteStringTest {
+ @Test public void getByte() throws Exception {
+ ByteString byteString = ByteString.decodeHex("ab12");
+ assertEquals(-85, byteString.getByte(0));
+ assertEquals(18, byteString.getByte(1));
+ }
+
+ @Test public void getByteOutOfBounds() throws Exception {
+ ByteString byteString = ByteString.decodeHex("ab12");
+ try {
+ byteString.getByte(2);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
@Test public void equals() throws Exception {
ByteString byteString = ByteString.decodeHex("000102");
assertTrue(byteString.equals(byteString));
@@ -48,15 +63,6 @@
assertEquals(byteString.utf8(), bronzeHorseman);
}
- @Test public void equalsAscii() throws Exception {
- ByteString byteString = ByteString.encodeUtf8("Content-Length");
- assertTrue(byteString.equalsAscii("Content-Length"));
- assertFalse(byteString.equalsAscii("content-length"));
- assertFalse(ByteString.of((byte) 0x63).equalsAscii(null));
- assertFalse(byteString.equalsAscii(bronzeHorseman));
- assertFalse(ByteString.encodeUtf8("Content-Length").equalsAscii("content-length"));
- }
-
@Test public void testHashCode() throws Exception {
ByteString byteString = ByteString.decodeHex("0102");
assertEquals(byteString.hashCode(), byteString.hashCode());
@@ -96,16 +102,6 @@
assertByteArraysEquals(new byte[] { 0x61, 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));
- }
-
@Test public void encodeBase64() {
assertEquals("", ByteString.encodeUtf8("").base64());
assertEquals("AA==", ByteString.encodeUtf8("\u0000").base64());
diff --git a/okio/src/test/java/okio/DeflaterSinkTest.java b/okio/src/test/java/okio/DeflaterSinkTest.java
index 025215b..0f6b8c2 100644
--- a/okio/src/test/java/okio/DeflaterSinkTest.java
+++ b/okio/src/test/java/okio/DeflaterSinkTest.java
@@ -25,6 +25,7 @@
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
public final class DeflaterSinkTest {
@Test public void deflateWithClose() throws Exception {
@@ -75,6 +76,39 @@
assertEquals(original, inflated.readByteString(inflated.size()));
}
+ @Test public void multipleSegmentsWithoutCompression() throws IOException {
+ OkBuffer buffer = new OkBuffer();
+ Deflater deflater = new Deflater();
+ deflater.setLevel(Deflater.NO_COMPRESSION);
+ DeflaterSink deflaterSink = new DeflaterSink(buffer, deflater);
+ int byteCount = Segment.SIZE * 4;
+ deflaterSink.write(new OkBuffer().writeUtf8(repeat('a', byteCount)), byteCount);
+ deflaterSink.close();
+ assertEquals(repeat('a', byteCount), inflate(buffer).readUtf8(byteCount));
+ }
+
+ /**
+ * This test deflates a single segment of without compression because that's
+ * the easiest way to force close() to emit a large amount of data to the
+ * underlying sink.
+ */
+ @Test public void closeWithExceptionWhenWritingAndClosing() throws IOException {
+ MockSink mockSink = new MockSink();
+ mockSink.scheduleThrow(0, new IOException("first"));
+ mockSink.scheduleThrow(1, new IOException("second"));
+ Deflater deflater = new Deflater();
+ deflater.setLevel(Deflater.NO_COMPRESSION);
+ DeflaterSink deflaterSink = new DeflaterSink(mockSink, deflater);
+ deflaterSink.write(new OkBuffer().writeUtf8(repeat('a', Segment.SIZE)), Segment.SIZE);
+ try {
+ deflaterSink.close();
+ fail();
+ } catch (IOException expected) {
+ assertEquals("first", expected.getMessage());
+ }
+ mockSink.assertLogContains("close()");
+ }
+
/**
* Uses streaming decompression to inflate {@code deflated}. The input must
* either be finished or have a trailing sync flush.
diff --git a/okio/src/test/java/okio/MockSink.java b/okio/src/test/java/okio/MockSink.java
new file mode 100644
index 0000000..bae3259
--- /dev/null
+++ b/okio/src/test/java/okio/MockSink.java
@@ -0,0 +1,70 @@
+/*
+ * 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 okio;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/** A scriptable sink. Like Mockito, but worse and requiring less configuration. */
+class MockSink implements Sink {
+ private final List<String> log = new ArrayList<String>();
+ private final Map<Integer, IOException> callThrows = new LinkedHashMap<Integer, IOException>();
+
+ public void assertLog(String... messages) {
+ assertEquals(Arrays.asList(messages), log);
+ }
+
+ public void assertLogContains(String message) {
+ assertTrue(log.contains(message));
+ }
+
+ public void scheduleThrow(int call, IOException e) {
+ callThrows.put(call, e);
+ }
+
+ private void throwIfScheduled() throws IOException {
+ IOException exception = callThrows.get(log.size() - 1);
+ if (exception != null) throw exception;
+ }
+
+ @Override public void write(OkBuffer source, long byteCount) throws IOException {
+ log.add("write(" + source + ", " + byteCount + ")");
+ source.skip(byteCount);
+ throwIfScheduled();
+ }
+
+ @Override public void flush() throws IOException {
+ log.add("flush()");
+ throwIfScheduled();
+ }
+
+ @Override public Sink deadline(Deadline deadline) {
+ log.add("deadline()");
+ return this;
+ }
+
+ @Override public void close() throws IOException {
+ log.add("close()");
+ throwIfScheduled();
+ }
+}
diff --git a/okio/src/test/java/okio/OkBufferTest.java b/okio/src/test/java/okio/OkBufferTest.java
index e4ba277..f69613a 100644
--- a/okio/src/test/java/okio/OkBufferTest.java
+++ b/okio/src/test/java/okio/OkBufferTest.java
@@ -365,6 +365,13 @@
assertEquals("OkBuffer[size=4 data=abcd4321]", data.toString());
}
+ @Test public void writeShortLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.writeShortLe(0xabcd);
+ data.writeShortLe(0x4321);
+ assertEquals("OkBuffer[size=4 data=cdab2143]", data.toString());
+ }
+
@Test public void writeInt() throws Exception {
OkBuffer data = new OkBuffer();
data.writeInt(0xabcdef01);
@@ -392,6 +399,27 @@
assertEquals("OkBuffer[size=8 data=abcdef0187654321]", data.toString());
}
+ @Test public void writeIntLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.writeIntLe(0xabcdef01);
+ data.writeIntLe(0x87654321);
+ assertEquals("OkBuffer[size=8 data=01efcdab21436587]", data.toString());
+ }
+
+ @Test public void writeLong() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.writeLong(0xabcdef0187654321L);
+ data.writeLong(0xcafebabeb0b15c00L);
+ assertEquals("OkBuffer[size=16 data=abcdef0187654321cafebabeb0b15c00]", data.toString());
+ }
+
+ @Test public void writeLongLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.writeLongLe(0xabcdef0187654321L);
+ data.writeLongLe(0xcafebabeb0b15c00L);
+ assertEquals("OkBuffer[size=16 data=2143658701efcdab005cb1b0bebafeca]", data.toString());
+ }
+
@Test public void readByte() throws Exception {
OkBuffer data = new OkBuffer();
data.write(new byte[] { (byte) 0xab, (byte) 0xcd });
@@ -410,6 +438,16 @@
assertEquals(0, data.size());
}
+ @Test public void readShortLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.write(new byte[] {
+ (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10
+ });
+ assertEquals((short) 0xcdab, data.readShortLe());
+ assertEquals((short) 0x10ef, data.readShortLe());
+ assertEquals(0, data.size());
+ }
+
@Test public void readShortSplitAcrossMultipleSegments() throws Exception {
OkBuffer data = new OkBuffer();
data.writeUtf8(repeat('a', Segment.SIZE - 1));
@@ -430,6 +468,17 @@
assertEquals(0, data.size());
}
+ @Test public void readIntLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.write(new byte[] {
+ (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10,
+ (byte) 0x87, (byte) 0x65, (byte) 0x43, (byte) 0x21
+ });
+ assertEquals(0x10efcdab, data.readIntLe());
+ assertEquals(0x21436587, data.readIntLe());
+ assertEquals(0, data.size());
+ }
+
@Test public void readIntSplitAcrossMultipleSegments() throws Exception {
OkBuffer data = new OkBuffer();
data.writeUtf8(repeat('a', Segment.SIZE - 3));
@@ -441,6 +490,44 @@
assertEquals(0, data.size());
}
+ @Test public void readLong() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.write(new byte[] {
+ (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10,
+ (byte) 0x87, (byte) 0x65, (byte) 0x43, (byte) 0x21,
+ (byte) 0x36, (byte) 0x47, (byte) 0x58, (byte) 0x69,
+ (byte) 0x12, (byte) 0x23, (byte) 0x34, (byte) 0x45
+ });
+ assertEquals(0xabcdef1087654321L, data.readLong());
+ assertEquals(0x3647586912233445L, data.readLong());
+ assertEquals(0, data.size());
+ }
+
+ @Test public void readLongLe() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.write(new byte[] {
+ (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x10,
+ (byte) 0x87, (byte) 0x65, (byte) 0x43, (byte) 0x21,
+ (byte) 0x36, (byte) 0x47, (byte) 0x58, (byte) 0x69,
+ (byte) 0x12, (byte) 0x23, (byte) 0x34, (byte) 0x45
+ });
+ assertEquals(0x2143658710efcdabL, data.readLongLe());
+ assertEquals(0x4534231269584736L, data.readLongLe());
+ assertEquals(0, data.size());
+ }
+
+ @Test public void readLongSplitAcrossMultipleSegments() throws Exception {
+ OkBuffer data = new OkBuffer();
+ data.writeUtf8(repeat('a', Segment.SIZE - 7));
+ data.write(new byte[] {
+ (byte) 0xab, (byte) 0xcd, (byte) 0xef, (byte) 0x01,
+ (byte) 0x87, (byte) 0x65, (byte) 0x43, (byte) 0x21,
+ });
+ data.readUtf8(Segment.SIZE - 7);
+ assertEquals(0xabcdef0187654321L, data.readLong());
+ assertEquals(0, data.size());
+ }
+
@Test public void byteAt() throws Exception {
OkBuffer buffer = new OkBuffer();
buffer.writeUtf8("a");
diff --git a/okio/src/test/java/okio/ReadUtf8LineTest.java b/okio/src/test/java/okio/ReadUtf8LineTest.java
index db36a79..79b4c8a 100644
--- a/okio/src/test/java/okio/ReadUtf8LineTest.java
+++ b/okio/src/test/java/okio/ReadUtf8LineTest.java
@@ -28,10 +28,10 @@
@Test public void readLines() throws IOException {
BufferedSource source = newSource("abc\ndef\n");
- assertEquals("abc", source.readUtf8Line(true));
- assertEquals("def", source.readUtf8Line(true));
+ assertEquals("abc", source.readUtf8LineStrict());
+ assertEquals("def", source.readUtf8LineStrict());
try {
- source.readUtf8Line(true);
+ source.readUtf8LineStrict();
fail();
} catch (EOFException expected) {
}
@@ -39,30 +39,30 @@
@Test public void emptyLines() throws IOException {
BufferedSource source = newSource("\n\n\n");
- assertEquals("", source.readUtf8Line(true));
- assertEquals("", source.readUtf8Line(true));
- assertEquals("", source.readUtf8Line(true));
+ assertEquals("", source.readUtf8LineStrict());
+ assertEquals("", source.readUtf8LineStrict());
+ assertEquals("", source.readUtf8LineStrict());
assertTrue(source.exhausted());
}
@Test public void crDroppedPrecedingLf() throws IOException {
BufferedSource source = newSource("abc\r\ndef\r\nghi\rjkl\r\n");
- assertEquals("abc", source.readUtf8Line(true));
- assertEquals("def", source.readUtf8Line(true));
- assertEquals("ghi\rjkl", source.readUtf8Line(true));
+ assertEquals("abc", source.readUtf8LineStrict());
+ assertEquals("def", source.readUtf8LineStrict());
+ assertEquals("ghi\rjkl", source.readUtf8LineStrict());
}
@Test public void bufferedReaderCompatible() throws IOException {
BufferedSource source = newSource("abc\ndef");
- assertEquals("abc", source.readUtf8Line(false));
- assertEquals("def", source.readUtf8Line(false));
- assertEquals(null, source.readUtf8Line(false));
+ assertEquals("abc", source.readUtf8Line());
+ assertEquals("def", source.readUtf8Line());
+ assertEquals(null, source.readUtf8Line());
}
@Test public void bufferedReaderCompatibleWithTrailingNewline() throws IOException {
BufferedSource source = newSource("abc\ndef\n");
- assertEquals("abc", source.readUtf8Line(false));
- assertEquals("def", source.readUtf8Line(false));
- assertEquals(null, source.readUtf8Line(false));
+ assertEquals("abc", source.readUtf8Line());
+ assertEquals("def", source.readUtf8Line());
+ assertEquals(null, source.readUtf8Line());
}
}
diff --git a/okio/src/test/java/okio/RealBufferedSinkTest.java b/okio/src/test/java/okio/RealBufferedSinkTest.java
index 67d3a80..80a1317 100644
--- a/okio/src/test/java/okio/RealBufferedSinkTest.java
+++ b/okio/src/test/java/okio/RealBufferedSinkTest.java
@@ -17,11 +17,7 @@
import java.io.IOException;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
import org.junit.Test;
import static okio.Util.UTF_8;
@@ -216,43 +212,4 @@
Arrays.fill(array, c);
return new String(array);
}
-
- /** A scriptable sink. Like Mockito, but worse and requiring less configuration. */
- private static class MockSink implements Sink {
- private final List<String> log = new ArrayList<String>();
- private final Map<Integer, IOException> callThrows = new LinkedHashMap<Integer, IOException>();
-
- public void assertLog(String... messages) {
- assertEquals(Arrays.asList(messages), log);
- }
-
- public void scheduleThrow(int call, IOException e) {
- callThrows.put(call, e);
- }
-
- private void throwIfScheduled() throws IOException {
- IOException exception = callThrows.get(log.size() - 1);
- if (exception != null) throw exception;
- }
-
- @Override public void write(OkBuffer source, long byteCount) throws IOException {
- log.add("write(" + source + ", " + byteCount + ")");
- throwIfScheduled();
- }
-
- @Override public void flush() throws IOException {
- log.add("flush()");
- throwIfScheduled();
- }
-
- @Override public Sink deadline(Deadline deadline) {
- log.add("deadline()");
- return this;
- }
-
- @Override public void close() throws IOException {
- log.add("close()");
- throwIfScheduled();
- }
- }
}
diff --git a/okio/src/test/java/okio/RealBufferedSourceTest.java b/okio/src/test/java/okio/RealBufferedSourceTest.java
index c191e1c..a77eaf2 100644
--- a/okio/src/test/java/okio/RealBufferedSourceTest.java
+++ b/okio/src/test/java/okio/RealBufferedSourceTest.java
@@ -18,7 +18,6 @@
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.util.Arrays;
import org.junit.Test;
@@ -163,7 +162,7 @@
// Test a sample set of methods.
try {
- bufferedSource.seek((byte) 1);
+ bufferedSource.indexOf((byte) 1);
fail();
} catch (IllegalStateException expected) {
}
diff --git a/pom.xml b/pom.xml
index 2216c73..c534a1d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,21 +138,28 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version>
</plugin>
-
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-release-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <autoVersionSubmodules>true</autoVersionSubmodules>
- </configuration>
- </plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.4.2</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.scm</groupId>
+ <artifactId>maven-scm-provider-gitexe</artifactId>
+ <version>1.9</version>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.10</version>
<configuration>
diff --git a/samples/crawler/pom.xml b/samples/crawler/pom.xml
new file mode 100644
index 0000000..0015f7c
--- /dev/null
+++ b/samples/crawler/pom.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>com.squareup.okhttp.sample</groupId>
+ <artifactId>sample-parent</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>crawler</artifactId>
+ <name>Sample: Crawler</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.squareup.okhttp</groupId>
+ <artifactId>okhttp</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jsoup</groupId>
+ <artifactId>jsoup</artifactId>
+ <version>1.7.3</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/samples/crawler/src/main/java/com/squareup/okhttp/sample/Crawler.java b/samples/crawler/src/main/java/com/squareup/okhttp/sample/Crawler.java
new file mode 100644
index 0000000..d80c13f
--- /dev/null
+++ b/samples/crawler/src/main/java/com/squareup/okhttp/sample/Crawler.java
@@ -0,0 +1,141 @@
+/*
+ * 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.sample;
+
+import com.squareup.okhttp.HttpResponseCache;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.internal.http.OkHeaders;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+
+/**
+ * Fetches HTML from a requested URL, follows the links, and repeats.
+ */
+public final class Crawler {
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ private final OkHttpClient client;
+ private final Set<URL> fetchedUrls = Collections.synchronizedSet(new LinkedHashSet<URL>());
+ private final LinkedBlockingQueue<URL> queue = new LinkedBlockingQueue<URL>();
+
+ public Crawler(OkHttpClient client) {
+ this.client = client;
+ }
+
+ private void parallelDrainQueue(int threadCount) {
+ ExecutorService executor = Executors.newFixedThreadPool(threadCount);
+ for (int i = 0; i < threadCount; i++) {
+ executor.execute(new Runnable() {
+ @Override public void run() {
+ try {
+ drainQueue();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ executor.shutdown();
+ }
+
+ private void drainQueue() throws Exception {
+ for (URL url; (url = queue.take()) != null; ) {
+ if (!fetchedUrls.add(url)) {
+ continue;
+ }
+
+ try {
+ fetch(url);
+ } catch (IOException e) {
+ System.out.printf("XXX: %s %s%n", url, e);
+ }
+ }
+ }
+
+ public void fetch(URL url) throws IOException {
+ HttpURLConnection connection = client.open(url);
+ String responseSource = connection.getHeaderField(OkHeaders.RESPONSE_SOURCE);
+ String contentType = connection.getHeaderField("Content-Type");
+ int responseCode = connection.getResponseCode();
+
+ System.out.printf("%03d: %s %s%n", responseCode, url, responseSource);
+
+ if (responseCode >= 400) {
+ connection.getErrorStream().close();
+ return;
+ }
+
+ InputStream in = connection.getInputStream();
+ if (responseCode != 200 || contentType == null) {
+ in.close();
+ return;
+ }
+
+ MediaType mediaType = MediaType.parse(contentType);
+ Document document = Jsoup.parse(in, mediaType.charset(UTF_8).name(), url.toString());
+ for (Element element : document.select("a[href]")) {
+ String href = element.attr("href");
+ URL link = parseUrl(url, href);
+ if (link != null) queue.add(link);
+ }
+
+ in.close();
+ }
+
+ private URL parseUrl(URL url, String href) {
+ try {
+ URL result = new URL(url, href);
+ return result.getProtocol().equals("http") || result.getProtocol().equals("https")
+ ? result
+ : null;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 2) {
+ System.out.println("Usage: Crawler <cache dir> <root>");
+ return;
+ }
+
+ int threadCount = 20;
+ long cacheByteCount = 1024L * 1024L * 100L;
+
+ OkHttpClient client = new OkHttpClient();
+ HttpResponseCache httpResponseCache = new HttpResponseCache(new File(args[0]), cacheByteCount);
+ client.setOkResponseCache(httpResponseCache);
+
+ Crawler crawler = new Crawler(client);
+ crawler.queue.add(new URL(args[1]));
+ crawler.parallelDrainQueue(threadCount);
+ }
+}
diff --git a/samples/pom.xml b/samples/pom.xml
index b212e99..62d1240 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -16,6 +16,7 @@
<modules>
<module>guide</module>
+ <module>crawler</module>
<module>simple-client</module>
<module>static-server</module>
</modules>
diff --git a/website/index.html b/website/index.html
index f2ed4e8..9ed87bb 100644
--- a/website/index.html
+++ b/website/index.html
@@ -210,8 +210,7 @@
// Look up the latest version of the library.
$.fn.artifactVersion({
'groupId': 'com.squareup.okhttp',
- 'artifactId': 'okhttp',
- 'classifier': 'jar-with-dependencies'
+ 'artifactId': 'okhttp'
}, function(version, url) {
$('.version').text(version);
$('.version-tag').text('v' + version);