diff options
16 files changed, 1887 insertions, 1510 deletions
@@ -16,6 +16,20 @@ package { default_visibility: ["//visibility:private"], + default_applicable_licenses: ["external_okhttp_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_okhttp_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE.txt", + ], } // The source files that contribute to Android's core library APIs. @@ -50,18 +64,19 @@ java_library { sdk_version: "none", system_modules: "core-all-system-modules", libs: [ - "conscrypt.module.intra.core.api.stubs", + "conscrypt.module.intra.core.api", ], java_version: "1.7", } -java_library { - name: "okhttp", - visibility: [ - "//art/build/apex", - "//external/robolectric-shadows", - "//libcore", - ], +// The source implementation files, used to build okhttp and core-all. It is +// used in the latter case to break the cycle where okhttp depends on core-all +// and core-all depends on okhttp. By including the source into core-all it +// ensures that the code can all build correctly and then that is used to build +// the separate parts. +filegroup { + name: "okhttp_impl_files", + visibility: ["//libcore"], srcs: [ // Although some of the classes in the android/ directory are already in the correct // package and do not need to be moved to another package they are transformed as they @@ -72,6 +87,21 @@ java_library { "repackaged/okhttp-android-support/src/main/java/**/*.java", "repackaged/okio/okio/src/main/java/**/*.java", ], +} + +java_library { + name: "okhttp", + visibility: [ + "//art/build/apex", + "//art/build/sdk", + "//external/grpc-grpc-java/okhttp", + "//external/robolectric-shadows", + "//libcore", + "//packages/modules/ArtPrebuilt", + ], + srcs: [ + ":okhttp_impl_files", + ], hostdex: true, installable: true, @@ -79,36 +109,82 @@ java_library { sdk_version: "none", system_modules: "core-all-system-modules", libs: [ - "conscrypt.module.intra.core.api.stubs", + "conscrypt.module.intra.core.api", ], java_version: "1.7", apex_available: [ + "com.android.art", "com.android.art.debug", - "com.android.art.release", ], + min_sdk_version: "31", +} + +// Java library for use on host, e.g. by robolectric. +java_library { + name: "okhttp-for-host", + visibility: [ + "//art/build/sdk", + "//external/robolectric-shadows", + ], + static_libs: [ + "okhttp", + ], + sdk_version: "none", + system_modules: "none", } -// A guaranteed unstripped version of okhttp. -// The build system may or may not strip the okhttp jar, but this one will -// not be stripped. See b/24535627. +// Java Library for both Host and Android that does not use the repackaged okhttp libraries +// (com.android.okhttp) and instead uses the original (com.squareup.okhttp) packages. +// This should not end up on the bootclasspath and instead should only be used to build +// third-party or unbundled applications or libraries that require OkHttp. java_library { - name: "okhttp-testdex", + name: "okhttp-norepackage", + host_supported: true, + visibility: [ - "//art:__subpackages__", + "//art/build/sdk", + "//external/grpc-grpc-java/okhttp", ], - static_libs: ["okhttp"], + + srcs: [ + "okhttp/src/main/java/**/*.java", + "okhttp-urlconnection/src/main/java/**/*.java", + "okio/okio/src/main/java/**/*.java", + ":okhttp_version.java", + ], + + target: { + host: { + libs: [ + "okhttp-android-util-log", + ], + }, + }, installable: true, + sdk_version: "current", +} - sdk_version: "none", - system_modules: "core-all-system-modules", - libs: [ - "conscrypt.module.intra.core.api.stubs", +// Generate Version.java based on the version number from pom.xml. +genrule { + name: "okhttp_version.java", + srcs: [ + "okhttp/src/main/java-templates/com/squareup/okhttp/internal/Version.java", + "okhttp/pom.xml", ], - dex_preopt: { - enabled: false, - }, - java_version: "1.7", + out: ["com/squareup/okhttp/internal/Version.java"], + cmd: "grep \"<version>\" $(location okhttp/pom.xml) | head -1 |" + + " sed -e \"s/\\s*<version>\\(.*\\)<\\/version>/\\1/\" > $(genDir)/version && " + + "sed -e \"s/\\$${project.version}/$$(cat $(genDir)/version)/\" " + + " $(location okhttp/src/main/java-templates/com/squareup/okhttp/internal/Version.java) " + + "> $(out)", +} + +// A library to provide a stub android.util.Log symbol for +// okhttp/src/main/java/com/squareup/okhttp/internal/Platform.java +java_library_host { + name: "okhttp-android-util-log", + srcs: ["okhttp-android-util-log/src/main/java/**/*.java"], } java_library { @@ -134,7 +210,7 @@ java_library { libs: [ "okhttp-nojarjar", "junit", - "conscrypt.module.intra.core.api.stubs", + "conscrypt.module.intra.core.api", "bouncycastle-unbundled", ], diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..d97975c --- /dev/null +++ b/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} @@ -1,3 +1,3 @@ # Bug component: 24949 -tobiast@google.com +ngeoffray@google.com include platform/libcore:/OWNERS diff --git a/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java b/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java index a7a5eba..0a25b88 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java @@ -16,7 +16,15 @@ package com.android.okhttp.internalandroidapi; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; + import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder; + +import libcore.util.NonNull; +import libcore.util.Nullable; + import com.squareup.okhttp.Cache; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; @@ -38,14 +46,21 @@ import java.util.Map; * classes appearing on method signatures. * @hide */ -@libcore.api.CorePlatformApi +@SystemApi(client = MODULE_LIBRARIES) +@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public final class AndroidResponseCacheAdapter { private final CacheHolder cacheHolder; private final Cache okHttpCache; - @libcore.api.CorePlatformApi - public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { + /** + * Creates an instance from {@link CacheHolder} + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public AndroidResponseCacheAdapter(@NonNull CacheHolder cacheHolder) { this.cacheHolder = cacheHolder; // Avoid one level of dereferencing by storing the reference to the OkHttp cache for later. this.okHttpCache = cacheHolder.getCache(); @@ -54,19 +69,25 @@ public final class AndroidResponseCacheAdapter { /** * Returns the {@link CacheHolder} associated with this instance and can be used by OkHttp * internal code to obtain the underlying OkHttp Cache object. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheHolder getCacheHolder() { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @NonNull CacheHolder getCacheHolder() { return cacheHolder; } /** * Used to implement {@link java.net.ResponseCache#get(URI, String, Map)}. See that method for * details. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheResponse get(URI uri, String requestMethod, - Map<String, List<String>> requestHeaders) throws IOException { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @Nullable CacheResponse get(@NonNull URI uri, @NonNull String requestMethod, + @Nullable Map<String, List<String>> requestHeaders) throws IOException { Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders); Response okResponse = okHttpCache.internalCache.get(okRequest); if (okResponse == null) { @@ -78,9 +99,13 @@ public final class AndroidResponseCacheAdapter { /** * Used to implement {@link java.net.ResponseCache#put(URI, URLConnection)}. See that method for * details. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @Nullable CacheRequest put(@NonNull URI uri, @NonNull URLConnection urlConnection) + throws IOException { Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection); if (okResponse == null) { // The URLConnection is not cacheable or could not be converted. Stop. @@ -98,8 +123,11 @@ public final class AndroidResponseCacheAdapter { * Returns the number of bytes currently being used to store the values in * this cache. This may be greater than the {@link #getMaxSize()} if a background * deletion is pending. IOException is thrown if the size cannot be determined. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getSize() throws IOException { return okHttpCache.getSize(); } @@ -107,8 +135,11 @@ public final class AndroidResponseCacheAdapter { /** * Returns the maximum number of bytes that this cache should use to store * its data. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getMaxSize() { return okHttpCache.getMaxSize(); } @@ -117,8 +148,11 @@ public final class AndroidResponseCacheAdapter { * Force buffered operations to the filesystem. This ensures that responses * written to the cache will be available the next time the cache is opened, * even if this process is killed. IOException is thrown if the flush fails. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void flush() throws IOException { okHttpCache.flush(); } @@ -126,8 +160,11 @@ public final class AndroidResponseCacheAdapter { /** * Returns the number of HTTP requests that required the network to either * supply a response or validate a locally cached response. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getNetworkCount() { return okHttpCache.getNetworkCount(); } @@ -136,8 +173,11 @@ public final class AndroidResponseCacheAdapter { * Returns the number of HTTP requests whose response was provided by the * cache. This may include conditional {@code GET} requests that were * validated over the network. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getHitCount() { return okHttpCache.getHitCount(); } @@ -146,14 +186,22 @@ public final class AndroidResponseCacheAdapter { * Returns the total number of HTTP requests that were made. This includes * both client requests and requests that were made on the client's behalf * to handle a redirects and retries. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getRequestCount() { return okHttpCache.getRequestCount(); } - /** Closes this cache. Stored values will remain on the filesystem. */ - @libcore.api.CorePlatformApi + /** + * Closes this cache. Stored values will remain on the filesystem. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void close() throws IOException { okHttpCache.close(); } @@ -162,8 +210,11 @@ public final class AndroidResponseCacheAdapter { * Closes the cache and deletes all of its stored values. This will delete * all files in the cache directory including files that weren't created by * the cache. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void delete() throws IOException { okHttpCache.delete(); } diff --git a/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java b/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java index 6558dfe..41c623f 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java @@ -24,12 +24,12 @@ import java.util.List; * A domain name service that resolves IP addresses for host names. * @hide */ -@libcore.api.CorePlatformApi public interface Dns { /** * Returns the IP addresses of {@code hostname}, in the order they should * be attempted. + * + * @hide */ - @libcore.api.CorePlatformApi List<InetAddress> lookup(String hostname) throws UnknownHostException; } diff --git a/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java b/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java index e990af4..6341b5f 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java @@ -16,6 +16,12 @@ package com.android.okhttp.internalandroidapi; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; + +import libcore.util.NonNull; + import com.squareup.okhttp.Cache; import java.io.File; @@ -24,21 +30,26 @@ import java.io.File; * An interface used to indicate a class can return a {@link CacheHolder} object. * @hide */ -@libcore.api.CorePlatformApi +@SystemApi(client = MODULE_LIBRARIES) +@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public interface HasCacheHolder { /** * Returns the {@link CacheHolder} object. + * + * @hide */ - @libcore.api.CorePlatformApi - CacheHolder getCacheHolder(); + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + @NonNull CacheHolder getCacheHolder(); /** * A holder for an OkHttp internal Cache object. This class exists as an opaque layer over * OkHttp internal classes. * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) final class CacheHolder { private final Cache okHttpCache; @@ -68,9 +79,12 @@ public interface HasCacheHolder { * * @param directory a writable directory * @param maxSizeBytes the maximum number of bytes this cache should use to store + * + * @hide */ - @libcore.api.CorePlatformApi - public static CacheHolder create(File directory, long maxSizeBytes) { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public static @NonNull CacheHolder create(@NonNull File directory, long maxSizeBytes) { Cache cache = new Cache(directory, maxSizeBytes); return new CacheHolder(cache); } @@ -78,9 +92,12 @@ public interface HasCacheHolder { /** * Returns true if the arguments supplied would result in an equivalent cache to this one * being created if they were passed to {@link #create(File, long)}. + * + * @hide */ - @libcore.api.CorePlatformApi - public boolean isEquivalent(File directory, long maxSizeBytes) { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public boolean isEquivalent(@NonNull File directory, long maxSizeBytes) { return (okHttpCache.getDirectory().equals(directory) && okHttpCache.getMaxSize() == maxSizeBytes && !okHttpCache.isClosed()); diff --git a/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java b/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java index a7d0bbf..eb223c4 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java @@ -51,27 +51,27 @@ import javax.net.SocketFactory; * * @hide */ -@libcore.api.CorePlatformApi public final class HttpURLConnectionFactory { private ConnectionPool connectionPool; private com.squareup.okhttp.Dns dns; - @libcore.api.CorePlatformApi + /** @hide */ public HttpURLConnectionFactory() { } /** * Sets a new ConnectionPool, specific to this URLFactory and not shared with * any other connections, with the given configuration. + * + * @hide */ - @libcore.api.CorePlatformApi public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } - @libcore.api.CorePlatformApi + /** @hide */ public void setDns(Dns dns) { Objects.requireNonNull(dns); this.dns = new DnsAdapter(dns); @@ -79,6 +79,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the system default proxy settings and SocketFactory. + * + * @hide */ public URLConnection openConnection(URL url) throws IOException { return internalOpenConnection(url, null /* socketFactory */, null /* proxy */); @@ -87,6 +89,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the system default SocketFactory and the specified * proxy settings. + * + * @hide */ public URLConnection openConnection(URL url, Proxy proxy) throws IOException { Objects.requireNonNull(proxy); @@ -96,6 +100,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the specified SocketFactory and the system default * proxy settings. + * + * @hide */ public URLConnection openConnection(URL url, SocketFactory socketFactory) throws IOException { Objects.requireNonNull(socketFactory); @@ -105,8 +111,9 @@ public final class HttpURLConnectionFactory { /** * Opens a connection using the specified SocketFactory and the specified proxy * settings, overriding any system wide configuration. + * + * @hide */ - @libcore.api.CorePlatformApi public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy) throws IOException { Objects.requireNonNull(socketFactory); @@ -148,6 +155,8 @@ public final class HttpURLConnectionFactory { /** * Adapts a {@link Dns} as a {@link com.squareup.okhttp.Dns}. + * + * @hide */ static final class DnsAdapter implements com.squareup.okhttp.Dns { private final Dns adaptee; diff --git a/okhttp-android-util-log/src/main/java/android/util/Log.java b/okhttp-android-util-log/src/main/java/android/util/Log.java new file mode 100644 index 0000000..d2d4f55 --- /dev/null +++ b/okhttp-android-util-log/src/main/java/android/util/Log.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * 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 android.util; + +public final class Log { + +private Log() { throw new RuntimeException("Stub!"); } + +public static int d(java.lang.String tag, java.lang.String msg) { throw new RuntimeException("Stub!"); } +} + diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/CallTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/CallTest.java index 3d48658..0b18783 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/CallTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/CallTest.java @@ -1650,9 +1650,16 @@ public final class CallTest { } @Test public void cancelInFlightBeforeResponseReadThrowsIOE() throws Exception { + final CountDownLatch cancelSignal = new CountDownLatch(1); + server.setDispatcher(new Dispatcher() { @Override public MockResponse dispatch(RecordedRequest request) { client.cancel("request"); + try { + cancelSignal.await(10L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + // Do nothing + } return new MockResponse().setBody("A"); } }); @@ -1662,6 +1669,7 @@ public final class CallTest { client.newCall(request).execute(); fail(); } catch (IOException expected) { + cancelSignal.countDown(); } } diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Http2ConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Http2ConnectionTest.java index 1e00b4b..607b0b5 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Http2ConnectionTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Http2ConnectionTest.java @@ -49,91 +49,92 @@ import static org.junit.Assert.fail; public final class Http2ConnectionTest { private static final Variant HTTP_2 = new Http2(); - private final MockSpdyPeer peer = new MockSpdyPeer(); - - @After public void tearDown() throws Exception { - peer.close(); - } @Test public void serverPingsClientHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.sendFrame().ping(false, 2, 3); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - connection(peer, HTTP_2); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(0, ping.streamId); - assertEquals(2, ping.payload1); - assertEquals(3, ping.payload2); - assertTrue(ping.ack); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.sendFrame().ping(false, 2, 3); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + connection(peer, HTTP_2); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(0, ping.streamId); + assertEquals(2, ping.payload1); + assertEquals(3, ping.payload2); + assertTrue(ping.ack); + } } @Test public void clientPingsServerHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 5); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, HTTP_2); - Ping ping = connection.ping(); - assertTrue(ping.roundTripTime() > 0); - assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); - - // verify the peer received what was expected - MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); - assertEquals(0, pingFrame.streamId); - assertEquals(1, pingFrame.payload1); - assertEquals(0x4f4b6f6b, pingFrame.payload2); // connection.ping() sets this. - assertFalse(pingFrame.ack); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 5); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, HTTP_2); + Ping ping = connection.ping(); + assertTrue(ping.roundTripTime() > 0); + assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); + + // verify the peer received what was expected + MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); + assertEquals(0, pingFrame.streamId); + assertEquals(1, pingFrame.payload1); + assertEquals(0x4f4b6f6b, pingFrame.payload2); // connection.ping() sets this. + assertFalse(pingFrame.ack); + } } @Test public void peerHttp2ServerLowersInitialWindowSize() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - Settings initial = new Settings(); - initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 1684); - Settings shouldntImpactConnection = new Settings(); - shouldntImpactConnection.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 3368); - - peer.sendFrame().settings(initial); - peer.acceptFrame(); // ACK - peer.sendFrame().settings(shouldntImpactConnection); - peer.acceptFrame(); // ACK 2 - peer.acceptFrame(); // HEADERS - peer.play(); - - FramedConnection connection = connection(peer, HTTP_2); - - // Default is 64KiB - 1. - assertEquals(65535, connection.peerSettings.getInitialWindowSize(-1)); - - // Verify the peer received the ACK. - MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); - assertEquals(TYPE_SETTINGS, ackFrame.type); - assertEquals(0, ackFrame.streamId); - assertTrue(ackFrame.ack); - ackFrame = peer.takeFrame(); - assertEquals(TYPE_SETTINGS, ackFrame.type); - assertEquals(0, ackFrame.streamId); - assertTrue(ackFrame.ack); - - // This stream was created *after* the connection settings were adjusted. - FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); - - 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); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + Settings initial = new Settings(); + initial.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 1684); + Settings shouldntImpactConnection = new Settings(); + shouldntImpactConnection.set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 3368); + + peer.sendFrame().settings(initial); + peer.acceptFrame(); // ACK + peer.sendFrame().settings(shouldntImpactConnection); + peer.acceptFrame(); // ACK 2 + peer.acceptFrame(); // HEADERS + peer.play(); + + FramedConnection connection = connection(peer, HTTP_2); + + // Default is 64KiB - 1. + assertEquals(65535, connection.peerSettings.getInitialWindowSize(-1)); + + // Verify the peer received the ACK. + MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); + assertEquals(TYPE_SETTINGS, ackFrame.type); + assertEquals(0, ackFrame.streamId); + assertTrue(ackFrame.ack); + ackFrame = peer.takeFrame(); + assertEquals(TYPE_SETTINGS, ackFrame.type); + assertEquals(0, ackFrame.streamId); + assertTrue(ackFrame.ack); + + // This stream was created *after* the connection settings were adjusted. + FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); + + 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); + } } @Test public void peerHttp2ServerZerosCompressionTable() throws Exception { @@ -174,101 +175,105 @@ public final class Http2ConnectionTest { } @Test public void receiveGoAwayHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.acceptFrame(); // SYN_STREAM 3 - peer.acceptFrame(); // SYN_STREAM 5 - peer.sendFrame().goAway(3, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.acceptFrame(); // DATA STREAM 3 - peer.play(); - - // play it back - FramedConnection connection = connection(peer, HTTP_2); - FramedStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); - FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); - connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received. - BufferedSink sink1 = Okio.buffer(stream1.getSink()); - BufferedSink sink2 = Okio.buffer(stream2.getSink()); - sink1.writeUtf8("abc"); - try { - sink2.writeUtf8("abc"); - sink2.flush(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); - } - sink1.writeUtf8("def"); - sink1.close(); - try { - connection.newStream(headerEntries("c", "cola"), true, true); - fail(); - } catch (IOException expected) { - assertEquals("shutdown", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.acceptFrame(); // SYN_STREAM 3 + peer.acceptFrame(); // SYN_STREAM 5 + peer.sendFrame().goAway(3, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.acceptFrame(); // DATA STREAM 3 + peer.play(); + + // play it back + FramedConnection connection = connection(peer, HTTP_2); + FramedStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); + FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received. + BufferedSink sink1 = Okio.buffer(stream1.getSink()); + BufferedSink sink2 = Okio.buffer(stream2.getSink()); + sink1.writeUtf8("abc"); + try { + sink2.writeUtf8("abc"); + sink2.flush(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + } + sink1.writeUtf8("def"); + sink1.close(); + try { + connection.newStream(headerEntries("c", "cola"), true, true); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + assertTrue(stream1.isOpen()); + assertFalse(stream2.isOpen()); + assertEquals(1, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream1.type); + MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream2.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame data1 = peer.takeFrame(); + assertEquals(TYPE_DATA, data1.type); + assertEquals(3, data1.streamId); + assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); } - assertTrue(stream1.isOpen()); - assertFalse(stream2.isOpen()); - assertEquals(1, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream1.type); - MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream2.type); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - MockSpdyPeer.InFrame data1 = peer.takeFrame(); - assertEquals(TYPE_DATA, data1.type); - assertEquals(3, data1.streamId); - assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); } @Test public void readSendsWindowUpdateHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - int windowSize = 100; - int windowUpdateThreshold = 50; - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - for (int i = 0; i < 3; i++) { - // Send frames of summing to size 50, which is windowUpdateThreshold. - peer.sendFrame().data(false, 3, data(24), 24); - peer.sendFrame().data(false, 3, data(25), 25); - peer.sendFrame().data(false, 3, data(1), 1); - peer.acceptFrame(); // connection WINDOW UPDATE - peer.acceptFrame(); // stream WINDOW UPDATE - } - peer.sendFrame().data(true, 3, data(0), 0); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, HTTP_2); - connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, windowSize); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(0, stream.unacknowledgedBytesRead); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - Source in = stream.getSource(); - Buffer buffer = new Buffer(); - buffer.writeAll(in); - assertEquals(-1, in.read(buffer, 1)); - assertEquals(150, buffer.size()); - - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - for (int i = 0; i < 3; i++) { - List<Integer> windowUpdateStreamIds = new ArrayList<>(2); - for (int j = 0; j < 2; j++) { - MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); - assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); - windowUpdateStreamIds.add(windowUpdate.streamId); - assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + int windowSize = 100; + int windowUpdateThreshold = 50; + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + for (int i = 0; i < 3; i++) { + // Send frames of summing to size 50, which is windowUpdateThreshold. + peer.sendFrame().data(false, 3, data(24), 24); + peer.sendFrame().data(false, 3, data(25), 25); + peer.sendFrame().data(false, 3, data(1), 1); + peer.acceptFrame(); // connection WINDOW UPDATE + peer.acceptFrame(); // stream WINDOW UPDATE + } + peer.sendFrame().data(true, 3, data(0), 0); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, HTTP_2); + connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, windowSize); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(0, stream.unacknowledgedBytesRead); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + Source in = stream.getSource(); + Buffer buffer = new Buffer(); + buffer.writeAll(in); + assertEquals(-1, in.read(buffer, 1)); + assertEquals(150, buffer.size()); + + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + for (int i = 0; i < 3; i++) { + List<Integer> windowUpdateStreamIds = new ArrayList<>(2); + for (int j = 0; j < 2; j++) { + MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); + assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); + windowUpdateStreamIds.add(windowUpdate.streamId); + assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); + } + assertTrue(windowUpdateStreamIds.contains(0)); // connection + assertTrue(windowUpdateStreamIds.contains(3)); // stream } - assertTrue(windowUpdateStreamIds.contains(0)); // connection - assertTrue(windowUpdateStreamIds.contains(3)); // stream } } @@ -277,156 +282,168 @@ public final class Http2ConnectionTest { } @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.sendFrame().data(true, 3, data(0), 0); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, HTTP_2); - FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(-1, client.getSource().read(new Buffer(), 1)); - - // Verify the peer received what was expected. - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(3, peer.frameCount()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.sendFrame().data(true, 3, data(0), 0); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, HTTP_2); + FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(-1, client.getSource().read(new Buffer(), 1)); + + // Verify the peer received what was expected. + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(3, peer.frameCount()); + } } @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // DATA - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, HTTP_2); - FramedStream client = connection.newStream(headerEntries("b", "banana"), true, true); - BufferedSink out = Okio.buffer(client.getSink()); - out.write(Util.EMPTY_BYTE_ARRAY); - out.flush(); - out.close(); - - // Verify the peer received what was expected. - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_DATA, peer.takeFrame().type); - assertEquals(3, peer.frameCount()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // DATA + peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, HTTP_2); + FramedStream client = connection.newStream(headerEntries("b", "banana"), true, true); + BufferedSink out = Okio.buffer(client.getSink()); + out.write(Util.EMPTY_BYTE_ARRAY); + out.flush(); + out.close(); + + // Verify the peer received what was expected. + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_DATA, peer.takeFrame().type); + assertEquals(3, peer.frameCount()); + } } @Test public void maxFrameSizeHonored() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - byte[] buff = new byte[peer.maxOutboundDataLength() + 1]; - Arrays.fill(buff, (byte) '*'); - - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - peer.acceptFrame(); // DATA - peer.acceptFrame(); // DATA - peer.play(); - - // play it back - FramedConnection connection = connection(peer, HTTP_2); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - BufferedSink out = Okio.buffer(stream.getSink()); - out.write(buff); - out.flush(); - out.close(); - - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(peer.maxOutboundDataLength(), data.data.length); - data = peer.takeFrame(); - assertEquals(1, data.data.length); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + byte[] buff = new byte[peer.maxOutboundDataLength() + 1]; + Arrays.fill(buff, (byte) '*'); + + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + peer.acceptFrame(); // DATA + peer.acceptFrame(); // DATA + peer.play(); + + // play it back + FramedConnection connection = connection(peer, HTTP_2); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + BufferedSink out = Okio.buffer(stream.getSink()); + out.write(buff); + out.flush(); + out.close(); + + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(peer.maxOutboundDataLength(), data.data.length); + data = peer.takeFrame(); + assertEquals(1, data.data.length); + } } @Test public void pushPromiseStream() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); - final List<Header> expectedRequestHeaders = Arrays.asList( - new Header(Header.TARGET_METHOD, "GET"), - new Header(Header.TARGET_SCHEME, "https"), - new Header(Header.TARGET_AUTHORITY, "squareup.com"), - new Header(Header.TARGET_PATH, "/cached") - ); - peer.sendFrame().pushPromise(3, 2, expectedRequestHeaders); - final List<Header> expectedResponseHeaders = Arrays.asList( - new Header(Header.RESPONSE_STATUS, "200") - ); - peer.sendFrame().synReply(true, 2, expectedResponseHeaders); - peer.sendFrame().data(true, 3, data(0), 0); - peer.play(); - - RecordingPushObserver observer = new RecordingPushObserver(); - - // play it back - FramedConnection connection = connectionBuilder(peer, HTTP_2) - .pushObserver(observer).build(); - FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(-1, client.getSource().read(new Buffer(), 1)); - - // verify the peer received what was expected - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - - assertEquals(expectedRequestHeaders, observer.takeEvent()); - assertEquals(expectedResponseHeaders, observer.takeEvent()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); + final List<Header> expectedRequestHeaders = Arrays.asList( + new Header(Header.TARGET_METHOD, "GET"), + new Header(Header.TARGET_SCHEME, "https"), + new Header(Header.TARGET_AUTHORITY, "squareup.com"), + new Header(Header.TARGET_PATH, "/cached") + ); + peer.sendFrame().pushPromise(3, 2, expectedRequestHeaders); + final List<Header> expectedResponseHeaders = Arrays.asList( + new Header(Header.RESPONSE_STATUS, "200") + ); + peer.sendFrame().synReply(true, 2, expectedResponseHeaders); + peer.sendFrame().data(true, 3, data(0), 0); + peer.play(); + + RecordingPushObserver observer = new RecordingPushObserver(); + + // play it back + FramedConnection connection = connectionBuilder(peer, HTTP_2) + .pushObserver(observer).build(); + FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(-1, client.getSource().read(new Buffer(), 1)); + + // verify the peer received what was expected + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + + assertEquals(expectedRequestHeaders, observer.takeEvent()); + assertEquals(expectedResponseHeaders, observer.takeEvent()); + } } @Test public void doublePushPromise() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.sendFrame().pushPromise(3, 2, headerEntries("a", "android")); - peer.acceptFrame(); // SYN_REPLY - peer.sendFrame().pushPromise(3, 2, headerEntries("b", "banana")); - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connectionBuilder(peer, HTTP_2).build(); - connection.newStream(headerEntries("b", "banana"), false, true); - - // verify the peer received what was expected - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(PROTOCOL_ERROR, peer.takeFrame().errorCode); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.sendFrame().pushPromise(3, 2, headerEntries("a", "android")); + peer.acceptFrame(); // SYN_REPLY + peer.sendFrame().pushPromise(3, 2, headerEntries("b", "banana")); + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connectionBuilder(peer, HTTP_2).build(); + connection.newStream(headerEntries("b", "banana"), false, true); + + // verify the peer received what was expected + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(PROTOCOL_ERROR, peer.takeFrame().errorCode); + } } @Test public void pushPromiseStreamsAutomaticallyCancel() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - - // write the mocking script - peer.sendFrame().pushPromise(3, 2, Arrays.asList( - new Header(Header.TARGET_METHOD, "GET"), - new Header(Header.TARGET_SCHEME, "https"), - new Header(Header.TARGET_AUTHORITY, "squareup.com"), - new Header(Header.TARGET_PATH, "/cached") - )); - peer.sendFrame().synReply(true, 2, Arrays.asList( - new Header(Header.RESPONSE_STATUS, "200") - )); - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - connectionBuilder(peer, HTTP_2) - .pushObserver(PushObserver.CANCEL).build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(2, rstStream.streamId); - assertEquals(CANCEL, rstStream.errorCode); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + + // write the mocking script + peer.sendFrame().pushPromise(3, 2, Arrays.asList( + new Header(Header.TARGET_METHOD, "GET"), + new Header(Header.TARGET_SCHEME, "https"), + new Header(Header.TARGET_AUTHORITY, "squareup.com"), + new Header(Header.TARGET_PATH, "/cached") + )); + peer.sendFrame().synReply(true, 2, Arrays.asList( + new Header(Header.RESPONSE_STATUS, "200") + )); + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + connectionBuilder(peer, HTTP_2) + .pushObserver(PushObserver.CANCEL).build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(2, rstStream.streamId); + assertEquals(CANCEL, rstStream.errorCode); + } } /** @@ -437,50 +454,54 @@ public final class Http2ConnectionTest { * <p>See https://github.com/square/okhttp/issues/1651 */ @Test public void socketExceptionWhileWritingHeaders() throws Exception { - peer.setVariantAndClient(HTTP_2, false); - peer.acceptFrame(); // SYN_STREAM. - peer.play(); - - String longString = repeat('a', Http2.INITIAL_MAX_FRAME_SIZE + 1); - Socket socket = peer.openSocket(); - FramedConnection connection = new FramedConnection.Builder(true) - .socket(socket) - .pushObserver(IGNORE) - .protocol(HTTP_2.getProtocol()) - .build(); - socket.shutdownOutput(); - try { - connection.newStream(headerEntries("a", longString), false, true); - fail(); - } catch (IOException expected) { - } - try { - connection.newStream(headerEntries("b", longString), false, true); - fail(); - } catch (IOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, false); + peer.acceptFrame(); // SYN_STREAM. + peer.play(); + + String longString = repeat('a', Http2.INITIAL_MAX_FRAME_SIZE + 1); + Socket socket = peer.openSocket(); + FramedConnection connection = new FramedConnection.Builder(true) + .socket(socket) + .pushObserver(IGNORE) + .protocol(HTTP_2.getProtocol()) + .build(); + socket.shutdownOutput(); + try { + connection.newStream(headerEntries("a", longString), false, true); + fail(); + } catch (IOException expected) { + } + try { + connection.newStream(headerEntries("b", longString), false, true); + fail(); + } catch (IOException expected) { + } } } private FramedConnection sendHttp2SettingsAndCheckForAck(boolean client, Settings settings) throws IOException, InterruptedException { - peer.setVariantAndClient(HTTP_2, client); - peer.sendFrame().settings(settings); - peer.acceptFrame(); // ACK - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, HTTP_2); - - // verify the peer received the ACK - MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); - assertEquals(TYPE_SETTINGS, ackFrame.type); - assertEquals(0, ackFrame.streamId); - assertTrue(ackFrame.ack); - - connection.ping().roundTripTime(); // Ensure that settings have been applied before returning. - return connection; + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(HTTP_2, client); + peer.sendFrame().settings(settings); + peer.acceptFrame(); // ACK + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, HTTP_2); + + // verify the peer received the ACK + MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); + assertEquals(TYPE_SETTINGS, ackFrame.type); + assertEquals(0, ackFrame.streamId); + assertTrue(ackFrame.ack); + + connection.ping().roundTripTime(); // Ensure that settings have been applied before returning. + return connection; + } } private FramedConnection connection(MockSpdyPeer peer, Variant variant) throws IOException { diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/MockSpdyPeer.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/MockSpdyPeer.java index f30d099..d402657 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/MockSpdyPeer.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/MockSpdyPeer.java @@ -27,9 +27,11 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import okio.Buffer; import okio.BufferedSource; @@ -116,11 +118,13 @@ public final class MockSpdyPeer implements Closeable { public void play() throws IOException { if (serverSocket != null) throw new IllegalStateException(); + final CountDownLatch startSignal = new CountDownLatch(1); serverSocket = new ServerSocket(0); serverSocket.setReuseAddress(true); port = serverSocket.getLocalPort(); executor.execute(new Runnable() { @Override public void run() { + startSignal.countDown(); try { readAndWriteFrames(); } catch (IOException e) { @@ -129,6 +133,11 @@ public final class MockSpdyPeer implements Closeable { } } }); + try { + startSignal.await(5L, TimeUnit.SECONDS); + } catch (InterruptedException e) { + // Do nothing + } } private void readAndWriteFrames() throws IOException { diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Spdy3ConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Spdy3ConnectionTest.java index 752e92b..e0f22d7 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Spdy3ConnectionTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/framed/Spdy3ConnectionTest.java @@ -58,416 +58,442 @@ import static org.junit.Assert.fail; public final class Spdy3ConnectionTest { private static final Variant SPDY3 = new Spdy3(); - private final MockSpdyPeer peer = new MockSpdyPeer(); - - @After public void tearDown() throws Exception { - peer.close(); - } @Test public void clientCreatesStreamAndServerReplies() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame() - .synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); - peer.acceptFrame(); // DATA - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - assertStreamData("robot", stream.getSource()); - BufferedSink out = Okio.buffer(stream.getSink()); - out.writeUtf8("c3po"); - out.close(); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertFalse(synStream.inFinished); - assertFalse(synStream.outFinished); - assertEquals(1, synStream.streamId); - assertEquals(0, synStream.associatedStreamId); - assertEquals(headerEntries("b", "banana"), synStream.headerBlock); - MockSpdyPeer.InFrame requestData = peer.takeFrame(); - assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame() + .synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); + peer.acceptFrame(); // DATA + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + assertStreamData("robot", stream.getSource()); + BufferedSink out = Okio.buffer(stream.getSink()); + out.writeUtf8("c3po"); + out.close(); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertFalse(synStream.inFinished); + assertFalse(synStream.outFinished); + assertEquals(1, synStream.streamId); + assertEquals(0, synStream.associatedStreamId); + assertEquals(headerEntries("b", "banana"), synStream.headerBlock); + MockSpdyPeer.InFrame requestData = peer.takeFrame(); + assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data)); + } } @Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception { - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), false, false); - assertEquals(1, connection.openStreamCount()); - assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); - connection.ping().roundTripTime(); // Ensure that inFinished has been received. - assertEquals(0, connection.openStreamCount()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), false, false); + // We cannot reliably check connection.openStreamCount as the stream gets closed once headers + // are exchanged and this can happen before newStream returns + assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); + connection.ping().roundTripTime(); // Ensure that inFinished has been received. + assertEquals(0, connection.openStreamCount()); + } } @Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // PING - peer.sendFrame().synReply(true, 1, headerEntries("a", "android")); - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(1, connection.openStreamCount()); - connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // PING + peer.sendFrame().synReply(true, 1, headerEntries("a", "android")); + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(1, connection.openStreamCount()); + connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + } } @Test public void serverCreatesStreamAndClientReplies() throws Exception { - final List<Header> pushHeaders = headerEntries( - ":scheme", "https", - ":host", "localhost:8888", - ":method", "GET", - ":path", "/index.html", - ":status", "200", - ":version", "HTTP/1.1", - "content-type", "text/html"); - // write the mocking script - peer.sendFrame().synStream(false, false, 2, 0, pushHeaders); - peer.acceptFrame(); // SYN_REPLY - peer.play(); - - // play it back - final AtomicInteger receiveCount = new AtomicInteger(); - FramedConnection.Listener handler = new FramedConnection.Listener() { - @Override public void onStream(FramedStream stream) throws IOException { - receiveCount.incrementAndGet(); - assertEquals(pushHeaders, stream.getRequestHeaders()); - assertEquals(null, stream.getErrorCode()); - stream.reply(headerEntries("b", "banana"), true); - } - }; - new FramedConnection.Builder(true) - .socket(peer.openSocket()) - .listener(handler) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame reply = peer.takeFrame(); - assertEquals(TYPE_HEADERS, reply.type); - assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); - assertFalse(reply.inFinished); - assertEquals(2, reply.streamId); - assertEquals(headerEntries("b", "banana"), reply.headerBlock); - assertEquals(1, receiveCount.get()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + final List<Header> pushHeaders = headerEntries( + ":scheme", "https", + ":host", "localhost:8888", + ":method", "GET", + ":path", "/index.html", + ":status", "200", + ":version", "HTTP/1.1", + "content-type", "text/html"); + // write the mocking script + peer.sendFrame().synStream(false, false, 2, 0, pushHeaders); + peer.acceptFrame(); // SYN_REPLY + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + FramedConnection.Listener handler = new FramedConnection.Listener() { + @Override public void onStream(FramedStream stream) throws IOException { + receiveCount.incrementAndGet(); + assertEquals(pushHeaders, stream.getRequestHeaders()); + assertEquals(null, stream.getErrorCode()); + stream.reply(headerEntries("b", "banana"), true); + } + }; + new FramedConnection.Builder(true) + .socket(peer.openSocket()) + .listener(handler) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_HEADERS, reply.type); + assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); + assertFalse(reply.inFinished); + assertEquals(2, reply.streamId); + assertEquals(headerEntries("b", "banana"), reply.headerBlock); + assertEquals(1, receiveCount.get()); + } } @Test public void replyWithNoData() throws Exception { - // write the mocking script - peer.sendFrame().synStream(false, false, 2, 0, headerEntries("a", "android")); - peer.acceptFrame(); // SYN_REPLY - peer.play(); - - // play it back - final AtomicInteger receiveCount = new AtomicInteger(); - FramedConnection.Listener listener = new FramedConnection.Listener() { - @Override public void onStream(FramedStream stream) throws IOException { - stream.reply(headerEntries("b", "banana"), false); - receiveCount.incrementAndGet(); - } - }; + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().synStream(false, false, 2, 0, headerEntries("a", "android")); + peer.acceptFrame(); // SYN_REPLY + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + FramedConnection.Listener listener = new FramedConnection.Listener() { + @Override public void onStream(FramedStream stream) throws IOException { + receiveCount.incrementAndGet(); + stream.reply(headerEntries("b", "banana"), false); + } + }; - connectionBuilder(peer, SPDY3).listener(listener).build(); + connectionBuilder(peer, SPDY3).listener(listener).build(); - // verify the peer received what was expected - MockSpdyPeer.InFrame reply = peer.takeFrame(); - assertEquals(TYPE_HEADERS, reply.type); - assertTrue(reply.inFinished); - assertEquals(headerEntries("b", "banana"), reply.headerBlock); - assertEquals(1, receiveCount.get()); - assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_HEADERS, reply.type); + assertTrue(reply.inFinished); + assertEquals(headerEntries("b", "banana"), reply.headerBlock); + assertEquals(1, receiveCount.get()); + assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); + } } @Test public void serverPingsClient() throws Exception { - // write the mocking script - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - connection(peer, SPDY3); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(0, ping.streamId); - assertEquals(2, ping.payload1); - assertEquals(0, ping.payload2); // ignored in spdy! - assertTrue(ping.ack); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + connection(peer, SPDY3); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(0, ping.streamId); + assertEquals(2, ping.payload1); + assertEquals(0, ping.payload2); // ignored in spdy! + assertTrue(ping.ack); + } } @Test public void clientPingsServer() throws Exception { - // write the mocking script - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 5); // payload2 ignored in spdy! - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - Ping ping = connection.ping(); - assertTrue(ping.roundTripTime() > 0); - assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); - - // verify the peer received what was expected - MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); - assertEquals(TYPE_PING, pingFrame.type); - assertEquals(1, pingFrame.payload1); - assertEquals(0, pingFrame.payload2); - assertFalse(pingFrame.ack); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 5); // payload2 ignored in spdy! + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + Ping ping = connection.ping(); + assertTrue(ping.roundTripTime() > 0); + assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); + + // verify the peer received what was expected + MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); + assertEquals(TYPE_PING, pingFrame.type); + assertEquals(1, pingFrame.payload1); + assertEquals(0, pingFrame.payload2); + assertFalse(pingFrame.ack); + } } @Test public void unexpectedPingIsNotReturned() throws Exception { - // write the mocking script - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 3, 0); // This ping will not be returned. - peer.sendFrame().ping(false, 4, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - connection(peer, SPDY3); - - // verify the peer received what was expected - MockSpdyPeer.InFrame ping2 = peer.takeFrame(); - assertEquals(2, ping2.payload1); - MockSpdyPeer.InFrame ping4 = peer.takeFrame(); - assertEquals(4, ping4.payload1); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 3, 0); // This ping will not be returned. + peer.sendFrame().ping(false, 4, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + connection(peer, SPDY3); + + // verify the peer received what was expected + MockSpdyPeer.InFrame ping2 = peer.takeFrame(); + assertEquals(2, ping2.payload1); + MockSpdyPeer.InFrame ping4 = peer.takeFrame(); + assertEquals(4, ping4.payload1); + } } @Test public void serverSendsSettingsToClient() throws Exception { - // write the mocking script - final Settings settings = new Settings(); - settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); - peer.sendFrame().settings(settings); - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - final AtomicInteger maxConcurrentStreams = new AtomicInteger(); - FramedConnection.Listener listener = new FramedConnection.Listener() { - @Override public void onStream(FramedStream stream) throws IOException { - throw new AssertionError(); - } - @Override public void onSettings(FramedConnection connection) { - maxConcurrentStreams.set(connection.maxConcurrentStreams()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + final Settings settings = new Settings(); + settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10); + peer.sendFrame().settings(settings); + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + final AtomicInteger maxConcurrentStreams = new AtomicInteger(); + FramedConnection.Listener listener = new FramedConnection.Listener() { + @Override public void onStream(FramedStream stream) throws IOException { + throw new AssertionError(); + } + @Override public void onSettings(FramedConnection connection) { + maxConcurrentStreams.set(connection.maxConcurrentStreams()); + } + }; + FramedConnection connection = connectionBuilder(peer, SPDY3) + .listener(listener) + .build(); + + peer.takeFrame(); // Guarantees that the peer Settings frame has been processed. + synchronized (connection) { + assertEquals(10, connection.peerSettings.getMaxConcurrentStreams(-1)); } - }; - FramedConnection connection = connectionBuilder(peer, SPDY3) - .listener(listener) - .build(); - - peer.takeFrame(); // Guarantees that the peer Settings frame has been processed. - synchronized (connection) { - assertEquals(10, connection.peerSettings.getMaxConcurrentStreams(-1)); + assertEquals(10, maxConcurrentStreams.get()); } - assertEquals(10, maxConcurrentStreams.get()); } @Test public void multipleSettingsFramesAreMerged() 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); - Settings settings2 = new Settings(); - settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); - settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); - settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); - peer.sendFrame().settings(settings2); - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - - peer.takeFrame(); // Guarantees that the Settings frame has been processed. - synchronized (connection) { - assertEquals(100, connection.peerSettings.getUploadBandwidth(-1)); - assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.UPLOAD_BANDWIDTH)); - assertEquals(400, connection.peerSettings.getDownloadBandwidth(-1)); - assertEquals(0, connection.peerSettings.flags(Settings.DOWNLOAD_BANDWIDTH)); - assertEquals(500, connection.peerSettings.getDownloadRetransRate(-1)); - assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.DOWNLOAD_RETRANS_RATE)); - assertEquals(600, connection.peerSettings.getMaxConcurrentStreams(-1)); - assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.MAX_CONCURRENT_STREAMS)); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // 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); + Settings settings2 = new Settings(); + settings2.set(Settings.DOWNLOAD_BANDWIDTH, 0, 400); + settings2.set(Settings.DOWNLOAD_RETRANS_RATE, PERSIST_VALUE, 500); + settings2.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 600); + peer.sendFrame().settings(settings2); + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + + peer.takeFrame(); // Guarantees that the Settings frame has been processed. + synchronized (connection) { + assertEquals(100, connection.peerSettings.getUploadBandwidth(-1)); + assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.UPLOAD_BANDWIDTH)); + assertEquals(400, connection.peerSettings.getDownloadBandwidth(-1)); + assertEquals(0, connection.peerSettings.flags(Settings.DOWNLOAD_BANDWIDTH)); + assertEquals(500, connection.peerSettings.getDownloadRetransRate(-1)); + assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.DOWNLOAD_RETRANS_RATE)); + assertEquals(600, connection.peerSettings.getMaxConcurrentStreams(-1)); + assertEquals(PERSIST_VALUE, connection.peerSettings.flags(Settings.MAX_CONCURRENT_STREAMS)); + } } } @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 - FramedConnection 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)); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // 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 + FramedConnection 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 Buffer().writeUtf8("bogus"), 5); - peer.acceptFrame(); // RST_STREAM - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - connection(peer, SPDY3); - - // verify the peer received what was expected - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(41, rstStream.streamId); - assertEquals(INVALID_STREAM, rstStream.errorCode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(2, ping.payload1); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().data(true, 41, new Buffer().writeUtf8("bogus"), 5); + peer.acceptFrame(); // RST_STREAM + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + connection(peer, SPDY3); + + // verify the peer received what was expected + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(41, rstStream.streamId); + assertEquals(INVALID_STREAM, rstStream.errorCode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(2, ping.payload1); + } } @Test public void bogusReplyFrameDoesNotDisruptConnection() throws Exception { - // write the mocking script - peer.sendFrame().synReply(false, 41, headerEntries("a", "android")); - peer.acceptFrame(); // RST_STREAM - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - connection(peer, SPDY3); - - // verify the peer received what was expected - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(41, rstStream.streamId); - assertEquals(INVALID_STREAM, rstStream.errorCode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(2, ping.payload1); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().synReply(false, 41, headerEntries("a", "android")); + peer.acceptFrame(); // RST_STREAM + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + connection(peer, SPDY3); + + // verify the peer received what was expected + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(41, rstStream.streamId); + assertEquals(INVALID_STREAM, rstStream.errorCode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(2, ping.payload1); + } } @Test public void clientClosesClientOutputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); - peer.acceptFrame(); // TYPE_DATA - peer.acceptFrame(); // TYPE_DATA with FLAG_FIN - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, false); - BufferedSink out = Okio.buffer(stream.getSink()); - out.writeUtf8("square"); - out.flush(); - assertEquals(1, connection.openStreamCount()); - out.close(); - try { - out.writeUtf8("round"); - fail(); - } catch (Exception expected) { - assertEquals("closed", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); + peer.acceptFrame(); // TYPE_DATA + peer.acceptFrame(); // TYPE_DATA with FLAG_FIN + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, false); + BufferedSink out = Okio.buffer(stream.getSink()); + out.writeUtf8("square"); + out.flush(); + assertEquals(1, connection.openStreamCount()); + out.close(); + try { + out.writeUtf8("round"); + fail(); + } catch (Exception expected) { + assertEquals("closed", expected.getMessage()); + } + connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertFalse(synStream.inFinished); + assertTrue(synStream.outFinished); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(TYPE_DATA, data.type); + assertFalse(data.inFinished); + assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); + MockSpdyPeer.InFrame fin = peer.takeFrame(); + assertEquals(TYPE_DATA, fin.type); + assertTrue(fin.inFinished); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(1, ping.payload1); } - connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received. - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertFalse(synStream.inFinished); - assertTrue(synStream.outFinished); - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(TYPE_DATA, data.type); - assertFalse(data.inFinished); - assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); - MockSpdyPeer.InFrame fin = peer.takeFrame(); - assertEquals(TYPE_DATA, fin.type); - assertTrue(fin.inFinished); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(1, ping.payload1); } @Test public void serverClosesClientOutputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().rstStream(1, CANCEL); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); - BufferedSink out = Okio.buffer(stream.getSink()); - connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. - try { - out.writeUtf8("square"); - out.flush(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: CANCEL", expected.getMessage()); - } - try { - out.close(); - fail(); - } catch (IOException expected) { - // Close throws because buffered data wasn't flushed. + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().rstStream(1, CANCEL); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); + BufferedSink out = Okio.buffer(stream.getSink()); + connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received. + try { + out.writeUtf8("square"); + out.flush(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } + try { + out.close(); + fail(); + } catch (IOException expected) { + // Close throws because buffered data wasn't flushed. + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertFalse(synStream.inFinished); + assertFalse(synStream.outFinished); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(1, ping.payload1); } - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertFalse(synStream.inFinished); - assertFalse(synStream.outFinished); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(1, ping.payload1); } /** @@ -475,41 +501,43 @@ public final class Spdy3ConnectionTest { * output stream. */ @Test public void clientClosesClientInputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); - Source in = stream.getSource(); - BufferedSink out = Okio.buffer(stream.getSink()); - in.close(); - try { - in.read(new Buffer(), 1); - fail(); - } catch (IOException expected) { - assertEquals("stream closed", expected.getMessage()); - } - try { - out.writeUtf8("a"); - out.flush(); - fail(); - } catch (IOException expected) { - assertEquals("stream finished", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); + Source in = stream.getSource(); + BufferedSink out = Okio.buffer(stream.getSink()); + in.close(); + try { + in.read(new Buffer(), 1); + fail(); + } catch (IOException expected) { + assertEquals("stream closed", expected.getMessage()); + } + try { + out.writeUtf8("a"); + out.flush(); + fail(); + } catch (IOException expected) { + assertEquals("stream finished", expected.getMessage()); + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertTrue(synStream.inFinished); + assertFalse(synStream.outFinished); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(CANCEL, rstStream.errorCode); } - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertTrue(synStream.inFinished); - assertFalse(synStream.outFinished); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(CANCEL, rstStream.errorCode); } /** @@ -517,635 +545,675 @@ public final class Spdy3ConnectionTest { * the output stream. */ @Test public void clientClosesClientInputStreamIfOutputStreamIsClosed() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // DATA - peer.acceptFrame(); // DATA with FLAG_FIN - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); - Source source = stream.getSource(); - BufferedSink out = Okio.buffer(stream.getSink()); - source.close(); - try { - source.read(new Buffer(), 1); - fail(); - } catch (IOException expected) { - assertEquals("stream closed", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // DATA + peer.acceptFrame(); // DATA with FLAG_FIN + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); + Source source = stream.getSource(); + BufferedSink out = Okio.buffer(stream.getSink()); + source.close(); + try { + source.read(new Buffer(), 1); + fail(); + } catch (IOException expected) { + assertEquals("stream closed", expected.getMessage()); + } + out.writeUtf8("square"); + out.flush(); + out.close(); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertFalse(synStream.inFinished); + assertFalse(synStream.outFinished); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(TYPE_DATA, data.type); + assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); + MockSpdyPeer.InFrame fin = peer.takeFrame(); + assertEquals(TYPE_DATA, fin.type); + assertTrue(fin.inFinished); + assertFalse(fin.outFinished); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(CANCEL, rstStream.errorCode); } - out.writeUtf8("square"); - out.flush(); - out.close(); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertFalse(synStream.inFinished); - assertFalse(synStream.outFinished); - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(TYPE_DATA, data.type); - assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data)); - MockSpdyPeer.InFrame fin = peer.takeFrame(); - assertEquals(TYPE_DATA, fin.type); - assertTrue(fin.inFinished); - assertFalse(fin.outFinished); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(CANCEL, rstStream.errorCode); } @Test public void serverClosesClientInputStream() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); - peer.sendFrame().data(true, 1, new Buffer().writeUtf8("square"), 6); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); - Source source = stream.getSource(); - assertStreamData("square", source); - connection.ping().roundTripTime(); // Ensure that inFinished has been received. - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - assertTrue(synStream.inFinished); - assertFalse(synStream.outFinished); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); + peer.sendFrame().data(true, 1, new Buffer().writeUtf8("square"), 6); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); + Source source = stream.getSource(); + assertStreamData("square", source); + connection.ping().roundTripTime(); // Ensure that inFinished has been received. + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + assertTrue(synStream.inFinished); + assertFalse(synStream.outFinished); + } } @Test public void remoteDoubleSynReply() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.acceptFrame(); // PING - peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); - peer.sendFrame().ping(true, 1, 0); - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("c", "cola"), true, true); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received. - try { - stream.getSource().read(new Buffer(), 1); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: STREAM_IN_USE", expected.getMessage()); - } + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.acceptFrame(); // PING + peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); + peer.sendFrame().ping(true, 1, 0); + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("c", "cola"), true, true); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + connection.ping().roundTripTime(); // Ensure that the 2nd SYN REPLY has been received. + try { + stream.getSource().read(new Buffer(), 1); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: STREAM_IN_USE", expected.getMessage()); + } - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(1, rstStream.streamId); - assertEquals(STREAM_IN_USE, rstStream.errorCode); + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(1, rstStream.streamId); + assertEquals(STREAM_IN_USE, rstStream.errorCode); + } } @Test public void remoteDoubleSynStream() throws Exception { - // write the mocking script - peer.sendFrame().synStream(false, false, 2, 0, headerEntries("a", "android")); - peer.acceptFrame(); // SYN_REPLY - peer.sendFrame().synStream(false, false, 2, 0, headerEntries("b", "banana")); - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - final AtomicInteger receiveCount = new AtomicInteger(); - FramedConnection.Listener listener = new FramedConnection.Listener() { - @Override public void onStream(FramedStream stream) throws IOException { - receiveCount.incrementAndGet(); - assertEquals(headerEntries("a", "android"), stream.getRequestHeaders()); - assertEquals(null, stream.getErrorCode()); - stream.reply(headerEntries("c", "cola"), true); - } - }; - new FramedConnection.Builder(true) - .socket(peer.openSocket()) - .listener(listener) - .build(); - - // verify the peer received what was expected - MockSpdyPeer.InFrame reply = peer.takeFrame(); - assertEquals(TYPE_HEADERS, reply.type); - assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(2, rstStream.streamId); - assertEquals(PROTOCOL_ERROR, rstStream.errorCode); - assertEquals(1, receiveCount.intValue()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.sendFrame().synStream(false, false, 2, 0, headerEntries("a", "android")); + peer.acceptFrame(); // SYN_REPLY + peer.sendFrame().synStream(false, false, 2, 0, headerEntries("b", "banana")); + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + final AtomicInteger receiveCount = new AtomicInteger(); + FramedConnection.Listener listener = new FramedConnection.Listener() { + @Override public void onStream(FramedStream stream) throws IOException { + receiveCount.incrementAndGet(); + assertEquals(headerEntries("a", "android"), stream.getRequestHeaders()); + assertEquals(null, stream.getErrorCode()); + stream.reply(headerEntries("c", "cola"), true); + } + }; + new FramedConnection.Builder(true) + .socket(peer.openSocket()) + .listener(listener) + .build(); + + // verify the peer received what was expected + MockSpdyPeer.InFrame reply = peer.takeFrame(); + assertEquals(TYPE_HEADERS, reply.type); + assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(2, rstStream.streamId); + assertEquals(PROTOCOL_ERROR, rstStream.errorCode); + assertEquals(1, receiveCount.intValue()); + } } @Test public void remoteSendsDataAfterInFinished() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); - peer.sendFrame().data(true, 1, new Buffer().writeUtf8("c3po"), 4); // Ignored. - peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. - peer.acceptFrame(); // PING - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - assertStreamData("robot", stream.getSource()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(2, ping.payload1); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); + peer.sendFrame().data(true, 1, new Buffer().writeUtf8("c3po"), 4); // Ignored. + peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. + peer.acceptFrame(); // PING + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + assertStreamData("robot", stream.getSource()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.payload1); + } } @Test public void clientDoesNotLimitFlowControl() throws Exception { - int dataLength = 64 * 1024 + 1; - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); - peer.sendFrame().data(false, 1, new Buffer().write(new byte[dataLength]), dataLength); - peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. - peer.acceptFrame(); // PING - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); - assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(2, ping.payload1); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + int dataLength = 64 * 1024 + 1; + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("b", "banana")); + peer.sendFrame().data(false, 1, new Buffer().write(new byte[dataLength]), dataLength); + peer.sendFrame().ping(false, 2, 0); // Ping just to make sure the stream was fastforwarded. + peer.acceptFrame(); // PING + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); + assertEquals(headerEntries("b", "banana"), stream.getResponseHeaders()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.payload1); + } } @Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().rstStream(1, REFUSED_STREAM); - peer.sendFrame().ping(false, 2, 0); - peer.acceptFrame(); // PING - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); - try { - stream.getResponseHeaders(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().rstStream(1, REFUSED_STREAM); + peer.sendFrame().ping(false, 2, 0); + peer.acceptFrame(); // PING + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); + try { + stream.getResponseHeaders(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + } + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + assertEquals(2, ping.payload1); } - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - assertEquals(2, ping.payload1); } @Test public void receiveGoAway() throws Exception { - peer.setVariantAndClient(SPDY3, false); - - // write the mocking script - peer.acceptFrame(); // SYN_STREAM 1 - peer.acceptFrame(); // SYN_STREAM 3 - peer.acceptFrame(); // PING. - peer.sendFrame().goAway(1, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY); - peer.sendFrame().ping(true, 1, 0); - peer.acceptFrame(); // DATA STREAM 1 - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); - FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); - connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received. - BufferedSink sink1 = Okio.buffer(stream1.getSink()); - BufferedSink sink2 = Okio.buffer(stream2.getSink()); - sink1.writeUtf8("abc"); - try { - sink2.writeUtf8("abc"); - sink2.flush(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); - } - sink1.writeUtf8("def"); - sink1.close(); - try { - connection.newStream(headerEntries("c", "cola"), true, true); - fail(); - } catch (IOException expected) { - assertEquals("shutdown", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(SPDY3, false); + + // write the mocking script + peer.acceptFrame(); // SYN_STREAM 1 + peer.acceptFrame(); // SYN_STREAM 3 + peer.acceptFrame(); // PING. + peer.sendFrame().goAway(1, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY); + peer.sendFrame().ping(true, 1, 0); + peer.acceptFrame(); // DATA STREAM 1 + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); + FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received. + BufferedSink sink1 = Okio.buffer(stream1.getSink()); + BufferedSink sink2 = Okio.buffer(stream2.getSink()); + sink1.writeUtf8("abc"); + try { + sink2.writeUtf8("abc"); + sink2.flush(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); + } + sink1.writeUtf8("def"); + sink1.close(); + try { + connection.newStream(headerEntries("c", "cola"), true, true); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + assertTrue(stream1.isOpen()); + assertFalse(stream2.isOpen()); + assertEquals(1, connection.openStreamCount()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream1.type); + MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream2.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame data1 = peer.takeFrame(); + assertEquals(TYPE_DATA, data1.type); + assertEquals(1, data1.streamId); + assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); } - assertTrue(stream1.isOpen()); - assertFalse(stream2.isOpen()); - assertEquals(1, connection.openStreamCount()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream1.type); - MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream2.type); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - MockSpdyPeer.InFrame data1 = peer.takeFrame(); - assertEquals(TYPE_DATA, data1.type); - assertEquals(1, data1.streamId); - assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); } @Test public void sendGoAway() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM 1 - peer.acceptFrame(); // GOAWAY - peer.acceptFrame(); // PING - peer.sendFrame().synStream(false, false, 2, 0, headerEntries("b", "b")); // Should be ignored! - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - connection.newStream(headerEntries("a", "android"), true, true); - Ping ping = connection.ping(); - connection.shutdown(PROTOCOL_ERROR); - assertEquals(1, connection.openStreamCount()); - ping.roundTripTime(); // Prevent the peer from exiting prematurely. - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream1.type); - MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); - assertEquals(TYPE_PING, pingFrame.type); - MockSpdyPeer.InFrame goaway = peer.takeFrame(); - assertEquals(TYPE_GOAWAY, goaway.type); - assertEquals(0, goaway.streamId); - assertEquals(PROTOCOL_ERROR, goaway.errorCode); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM 1 + peer.acceptFrame(); // GOAWAY + peer.acceptFrame(); // PING + peer.sendFrame().synStream(false, false, 2, 0, headerEntries("b", "b")); // Should be ignored! + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + connection.newStream(headerEntries("a", "android"), true, true); + Ping ping = connection.ping(); + connection.shutdown(PROTOCOL_ERROR); + assertEquals(1, connection.openStreamCount()); + ping.roundTripTime(); // Prevent the peer from exiting prematurely. + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream1.type); + MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); + assertEquals(TYPE_PING, pingFrame.type); + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + assertEquals(0, goaway.streamId); + assertEquals(PROTOCOL_ERROR, goaway.errorCode); + } } @Test public void noPingsAfterShutdown() throws Exception { - // write the mocking script - peer.acceptFrame(); // GOAWAY - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - connection.shutdown(INTERNAL_ERROR); - try { - connection.ping(); - fail(); - } catch (IOException expected) { - assertEquals("shutdown", expected.getMessage()); - } + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // GOAWAY + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + connection.shutdown(INTERNAL_ERROR); + try { + connection.ping(); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } - // verify the peer received what was expected - MockSpdyPeer.InFrame goaway = peer.takeFrame(); - assertEquals(TYPE_GOAWAY, goaway.type); - assertEquals(INTERNAL_ERROR, goaway.errorCode); + // verify the peer received what was expected + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + assertEquals(INTERNAL_ERROR, goaway.errorCode); + } } @Test public void close() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // GOAWAY - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); - assertEquals(1, connection.openStreamCount()); - connection.close(); - assertEquals(0, connection.openStreamCount()); - try { - connection.newStream(headerEntries("b", "banana"), true, true); - fail(); - } catch (IOException expected) { - assertEquals("shutdown", expected.getMessage()); - } - BufferedSink sink = Okio.buffer(stream.getSink()); - try { - sink.writeByte(0); - sink.flush(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: CANCEL", expected.getMessage()); - } - try { - stream.getSource().read(new Buffer(), 1); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: CANCEL", expected.getMessage()); - } + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // GOAWAY + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("a", "android"), true, true); + assertEquals(1, connection.openStreamCount()); + connection.close(); + assertEquals(0, connection.openStreamCount()); + try { + connection.newStream(headerEntries("b", "banana"), true, true); + fail(); + } catch (IOException expected) { + assertEquals("shutdown", expected.getMessage()); + } + BufferedSink sink = Okio.buffer(stream.getSink()); + try { + sink.writeByte(0); + sink.flush(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } + try { + stream.getSource().read(new Buffer(), 1); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: CANCEL", expected.getMessage()); + } - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame goaway = peer.takeFrame(); - assertEquals(TYPE_GOAWAY, goaway.type); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(1, rstStream.streamId); + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame goaway = peer.takeFrame(); + assertEquals(TYPE_GOAWAY, goaway.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(1, rstStream.streamId); + } } @Test public void closeCancelsPings() throws Exception { - // write the mocking script - peer.acceptFrame(); // PING - peer.acceptFrame(); // GOAWAY - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - Ping ping = connection.ping(); - connection.close(); - assertEquals(-1, ping.roundTripTime()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // PING + peer.acceptFrame(); // GOAWAY + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + Ping ping = connection.ping(); + connection.close(); + assertEquals(-1, ping.roundTripTime()); + } } @Test public void getResponseHeadersTimesOut() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS); - long startNanos = System.nanoTime(); - try { - stream.getResponseHeaders(); - fail(); - } catch (InterruptedIOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS); + long startNanos = System.nanoTime(); + try { + stream.getResponseHeaders(); + fail(); + } catch (InterruptedIOException expected) { + } + long elapsedNanos = System.nanoTime() - startNanos; + awaitWatchdogIdle(); + assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } - long elapsedNanos = System.nanoTime() - startNanos; - awaitWatchdogIdle(); - assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } @Test public void readTimesOut() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS); - Source source = stream.getSource(); - long startNanos = System.nanoTime(); - try { - source.read(new Buffer(), 1); - fail(); - } catch (InterruptedIOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + stream.readTimeout().timeout(500, TimeUnit.MILLISECONDS); + Source source = stream.getSource(); + long startNanos = System.nanoTime(); + try { + source.read(new Buffer(), 1); + fail(); + } catch (InterruptedIOException expected) { + } + long elapsedNanos = System.nanoTime() - startNanos; + awaitWatchdogIdle(); + assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } - long elapsedNanos = System.nanoTime() - startNanos; - awaitWatchdogIdle(); - assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } @Test public void writeTimesOutAwaitingStreamWindow() throws Exception { - // Set the peer's receive window to 5 bytes! - Settings peerSettings = new Settings().set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 5); - - // write the mocking script - peer.sendFrame().settings(peerSettings); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.acceptFrame(); // DATA - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - connection.ping().roundTripTime(); // Make sure settings have been received. - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - Sink sink = stream.getSink(); - sink.write(new Buffer().writeUtf8("abcde"), 5); - stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS); - long startNanos = System.nanoTime(); - sink.write(new Buffer().writeUtf8("f"), 1); - try { - sink.flush(); // This will time out waiting on the write window. - fail(); - } catch (InterruptedIOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // Set the peer's receive window to 5 bytes! + Settings peerSettings = new Settings().set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 5); + + // write the mocking script + peer.sendFrame().settings(peerSettings); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.acceptFrame(); // DATA + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + connection.ping().roundTripTime(); // Make sure settings have been received. + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + Sink sink = stream.getSink(); + sink.write(new Buffer().writeUtf8("abcde"), 5); + stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS); + long startNanos = System.nanoTime(); + sink.write(new Buffer().writeUtf8("f"), 1); + try { + sink.flush(); // This will time out waiting on the write window. + fail(); + } catch (InterruptedIOException expected) { + } + long elapsedNanos = System.nanoTime() - startNanos; + awaitWatchdogIdle(); + assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + assertEquals(TYPE_PING, peer.takeFrame().type); + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_DATA, peer.takeFrame().type); + assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } - long elapsedNanos = System.nanoTime() - startNanos; - awaitWatchdogIdle(); - assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - assertEquals(TYPE_PING, peer.takeFrame().type); - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_DATA, peer.takeFrame().type); - assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } @Test public void writeTimesOutAwaitingConnectionWindow() throws Exception { - // Set the peer's receive window to 5 bytes. Give the stream 5 bytes back, so only the - // connection-level window is applicable. - Settings peerSettings = new Settings().set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 5); - - // write the mocking script - peer.sendFrame().settings(peerSettings); - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().windowUpdate(1, 5); - peer.acceptFrame(); // PING - peer.sendFrame().ping(true, 1, 0); - peer.acceptFrame(); // DATA - peer.acceptFrame(); // RST_STREAM - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - connection.ping().roundTripTime(); // Make sure the window update has been received. - Sink sink = stream.getSink(); - stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS); - sink.write(new Buffer().writeUtf8("abcdef"), 6); - long startNanos = System.nanoTime(); - try { - sink.flush(); // This will time out waiting on the write window. - fail(); - } catch (InterruptedIOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // Set the peer's receive window to 5 bytes. Give the stream 5 bytes back, so only the + // connection-level window is applicable. + Settings peerSettings = new Settings().set(Settings.INITIAL_WINDOW_SIZE, PERSIST_VALUE, 5); + + // write the mocking script + peer.sendFrame().settings(peerSettings); + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().windowUpdate(1, 5); + peer.acceptFrame(); // PING + peer.sendFrame().ping(true, 1, 0); + peer.acceptFrame(); // DATA + peer.acceptFrame(); // RST_STREAM + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + connection.ping().roundTripTime(); // Make sure the window update has been received. + Sink sink = stream.getSink(); + stream.writeTimeout().timeout(500, TimeUnit.MILLISECONDS); + sink.write(new Buffer().writeUtf8("abcdef"), 6); + long startNanos = System.nanoTime(); + try { + sink.flush(); // This will time out waiting on the write window. + fail(); + } catch (InterruptedIOException expected) { + } + long elapsedNanos = System.nanoTime() - startNanos; + awaitWatchdogIdle(); + assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); + assertEquals(0, connection.openStreamCount()); + + // verify the peer received what was expected + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_PING, peer.takeFrame().type); + assertEquals(TYPE_DATA, peer.takeFrame().type); + assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } - long elapsedNanos = System.nanoTime() - startNanos; - awaitWatchdogIdle(); - assertEquals(500d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */); - assertEquals(0, connection.openStreamCount()); - - // verify the peer received what was expected - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_PING, peer.takeFrame().type); - assertEquals(TYPE_DATA, peer.takeFrame().type); - assertEquals(TYPE_RST_STREAM, peer.takeFrame().type); } @Test public void outgoingWritesAreBatched() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.acceptFrame(); // DATA - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - - // two outgoing writes - Sink sink = stream.getSink(); - sink.write(new Buffer().writeUtf8("abcde"), 5); - sink.write(new Buffer().writeUtf8("fghij"), 5); - sink.close(); - - // verify the peer received one incoming frame - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - MockSpdyPeer.InFrame data = peer.takeFrame(); - assertEquals(TYPE_DATA, data.type); - assertTrue(Arrays.equals("abcdefghij".getBytes("UTF-8"), data.data)); - assertTrue(data.inFinished); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.acceptFrame(); // DATA + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + + // two outgoing writes + Sink sink = stream.getSink(); + sink.write(new Buffer().writeUtf8("abcde"), 5); + sink.write(new Buffer().writeUtf8("fghij"), 5); + sink.close(); + + // verify the peer received one incoming frame + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + MockSpdyPeer.InFrame data = peer.takeFrame(); + assertEquals(TYPE_DATA, data.type); + assertTrue(Arrays.equals("abcdefghij".getBytes("UTF-8"), data.data)); + assertTrue(data.inFinished); + } } @Test public void headers() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // PING - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().headers(1, headerEntries("c", "c3po")); - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. - assertEquals(headerEntries("a", "android", "c", "c3po"), stream.getResponseHeaders()); - - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // PING + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().headers(1, headerEntries("c", "c3po")); + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + assertEquals(headerEntries("a", "android", "c", "c3po"), stream.getResponseHeaders()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + } } @Test public void headersBeforeReply() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // PING - peer.sendFrame().headers(1, headerEntries("c", "c3po")); - peer.acceptFrame(); // RST_STREAM - peer.sendFrame().ping(true, 1, 0); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. - try { - stream.getResponseHeaders(); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); - } + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // PING + peer.sendFrame().headers(1, headerEntries("c", "c3po")); + peer.acceptFrame(); // RST_STREAM + peer.sendFrame().ping(true, 1, 0); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + try { + stream.getResponseHeaders(); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); + } - // verify the peer received what was expected - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); - MockSpdyPeer.InFrame ping = peer.takeFrame(); - assertEquals(TYPE_PING, ping.type); - MockSpdyPeer.InFrame rstStream = peer.takeFrame(); - assertEquals(TYPE_RST_STREAM, rstStream.type); - assertEquals(PROTOCOL_ERROR, rstStream.errorCode); + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(HeadersMode.SPDY_SYN_STREAM, synStream.headersMode); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(PROTOCOL_ERROR, rstStream.errorCode); + } } @Test public void readSendsWindowUpdate() throws Exception { - peer.setVariantAndClient(SPDY3, false); - - int windowSize = 100; - int windowUpdateThreshold = 50; - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - for (int i = 0; i < 3; i++) { - // Send frames of summing to size 50, which is windowUpdateThreshold. - peer.sendFrame().data(false, 1, data(24), 24); - peer.sendFrame().data(false, 1, data(25), 25); - peer.sendFrame().data(false, 1, data(1), 1); - peer.acceptFrame(); // connection WINDOW UPDATE - peer.acceptFrame(); // stream WINDOW UPDATE - } - peer.sendFrame().data(true, 1, data(0), 0); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, SPDY3); - connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, windowSize); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(0, stream.unacknowledgedBytesRead); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - Source in = stream.getSource(); - Buffer buffer = new Buffer(); - buffer.writeAll(in); - assertEquals(-1, in.read(buffer, 1)); - assertEquals(150, buffer.size()); - - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - for (int i = 0; i < 3; i++) { - List<Integer> windowUpdateStreamIds = new ArrayList<>(2); - for (int j = 0; j < 2; j++) { - MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); - assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); - windowUpdateStreamIds.add(windowUpdate.streamId); - assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(SPDY3, false); + + int windowSize = 100; + int windowUpdateThreshold = 50; + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + for (int i = 0; i < 3; i++) { + // Send frames of summing to size 50, which is windowUpdateThreshold. + peer.sendFrame().data(false, 1, data(24), 24); + peer.sendFrame().data(false, 1, data(25), 25); + peer.sendFrame().data(false, 1, data(1), 1); + peer.acceptFrame(); // connection WINDOW UPDATE + peer.acceptFrame(); // stream WINDOW UPDATE + } + peer.sendFrame().data(true, 1, data(0), 0); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, SPDY3); + connection.okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, 0, windowSize); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(0, stream.unacknowledgedBytesRead); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + Source in = stream.getSource(); + Buffer buffer = new Buffer(); + buffer.writeAll(in); + assertEquals(-1, in.read(buffer, 1)); + assertEquals(150, buffer.size()); + + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + for (int i = 0; i < 3; i++) { + List<Integer> windowUpdateStreamIds = new ArrayList<>(2); + for (int j = 0; j < 2; j++) { + MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); + assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); + windowUpdateStreamIds.add(windowUpdate.streamId); + assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); + } + assertTrue(windowUpdateStreamIds.contains(0)); // connection + assertTrue(windowUpdateStreamIds.contains(1)); // stream } - assertTrue(windowUpdateStreamIds.contains(0)); // connection - assertTrue(windowUpdateStreamIds.contains(1)); // stream } } @@ -1154,107 +1222,115 @@ public final class Spdy3ConnectionTest { } @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdate() throws Exception { - peer.setVariantAndClient(SPDY3, false); - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().data(true, 1, data(0), 0); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, SPDY3); - FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); - assertEquals(-1, client.getSource().read(new Buffer(), 1)); - - // Verify the peer received what was expected. - MockSpdyPeer.InFrame synStream = peer.takeFrame(); - assertEquals(TYPE_HEADERS, synStream.type); - assertEquals(3, peer.frameCount()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(SPDY3, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().data(true, 1, data(0), 0); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, SPDY3); + FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(-1, client.getSource().read(new Buffer(), 1)); + + // Verify the peer received what was expected. + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(3, peer.frameCount()); + } } @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdate() throws Exception { - peer.setVariantAndClient(SPDY3, false); - - // Write the mocking script. - peer.acceptFrame(); // SYN_STREAM - peer.acceptFrame(); // DATA - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, SPDY3); - FramedStream client = connection.newStream(headerEntries("b", "banana"), true, true); - BufferedSink out = Okio.buffer(client.getSink()); - out.write(Util.EMPTY_BYTE_ARRAY); - out.flush(); - out.close(); - - // Verify the peer received what was expected. - assertEquals(TYPE_HEADERS, peer.takeFrame().type); - assertEquals(TYPE_DATA, peer.takeFrame().type); - assertEquals(3, peer.frameCount()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.setVariantAndClient(SPDY3, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.acceptFrame(); // DATA + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, SPDY3); + FramedStream client = connection.newStream(headerEntries("b", "banana"), true, true); + BufferedSink out = Okio.buffer(client.getSink()); + out.write(Util.EMPTY_BYTE_ARRAY); + out.flush(); + out.close(); + + // Verify the peer received what was expected. + assertEquals(TYPE_HEADERS, peer.takeFrame().type); + assertEquals(TYPE_DATA, peer.takeFrame().type); + assertEquals(3, peer.frameCount()); + } } @Test public void testTruncatedDataFrame() throws Exception { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); - peer.sendFrame().data(false, 1, data(1024), 1024); - peer.truncateLastFrame(8 + 100); - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); - Source in = stream.getSource(); - try { - Okio.buffer(in).readByteString(101); - fail(); - } catch (IOException expected) { - assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().data(false, 1, data(1024), 1024); + peer.truncateLastFrame(8 + 100); + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); + Source in = stream.getSource(); + try { + Okio.buffer(in).readByteString(101); + fail(); + } catch (IOException expected) { + assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage()); + } } } @Test public void blockedStreamDoesntStarveNewStream() throws Exception { - int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, peer.maxOutboundDataLength()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + int framesThatFillWindow = roundUp(DEFAULT_INITIAL_WINDOW_SIZE, peer.maxOutboundDataLength()); - // Write the mocking script. This accepts more data frames than necessary! - peer.acceptFrame(); // SYN_STREAM on stream 1 - for (int i = 0; i < framesThatFillWindow; i++) { - peer.acceptFrame(); // DATA on stream 1 + // Write the mocking script. This accepts more data frames than necessary! + peer.acceptFrame(); // SYN_STREAM on stream 1 + for (int i = 0; i < framesThatFillWindow; i++) { + peer.acceptFrame(); // DATA on stream 1 + } + peer.acceptFrame(); // SYN_STREAM on stream 2 + peer.acceptFrame(); // DATA on stream 2 + peer.play(); + + // Play it back. + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream1 = connection.newStream(headerEntries("a", "apple"), true, true); + BufferedSink out1 = Okio.buffer(stream1.getSink()); + 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. + assertEquals(0, connection.bytesLeftInWriteWindow); + assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); + + // receiving a window update on the the connection will unblock new streams. + connection.readerRunnable.windowUpdate(0, 3); + + assertEquals(3, connection.bytesLeftInWriteWindow); + assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); + + // Another stream should be able to send data even though 1 is blocked. + FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); + BufferedSink out2 = Okio.buffer(stream2.getSink()); + out2.writeUtf8("foo"); + out2.flush(); + + assertEquals(0, connection.bytesLeftInWriteWindow); + assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); + assertEquals(DEFAULT_INITIAL_WINDOW_SIZE - 3, connection.getStream(3).bytesLeftInWriteWindow); } - peer.acceptFrame(); // SYN_STREAM on stream 2 - peer.acceptFrame(); // DATA on stream 2 - peer.play(); - - // Play it back. - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream1 = connection.newStream(headerEntries("a", "apple"), true, true); - BufferedSink out1 = Okio.buffer(stream1.getSink()); - 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. - assertEquals(0, connection.bytesLeftInWriteWindow); - assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); - - // receiving a window update on the the connection will unblock new streams. - connection.readerRunnable.windowUpdate(0, 3); - - assertEquals(3, connection.bytesLeftInWriteWindow); - assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); - - // Another stream should be able to send data even though 1 is blocked. - FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); - BufferedSink out2 = Okio.buffer(stream2.getSink()); - out2.writeUtf8("foo"); - out2.flush(); - - assertEquals(0, connection.bytesLeftInWriteWindow); - assertEquals(0, connection.getStream(1).bytesLeftInWriteWindow); - assertEquals(DEFAULT_INITIAL_WINDOW_SIZE - 3, connection.getStream(3).bytesLeftInWriteWindow); } /** https://github.com/square/okhttp/issues/333 */ @@ -1310,43 +1386,47 @@ public final class Spdy3ConnectionTest { } private void headerBlockHasTrailingCompressedBytes(String frame, int length) throws IOException { - // write the mocking script - peer.acceptFrame(); // SYN_STREAM - byte[] trailingCompressedBytes = ByteString.decodeBase64(frame).toByteArray(); - trailingCompressedBytes[11] = 1; // Set SPDY/3 stream ID to 3. - peer.sendFrame(trailingCompressedBytes); - peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); - peer.acceptFrame(); // DATA - peer.play(); - - // play it back - FramedConnection connection = connection(peer, SPDY3); - FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); - assertEquals("a", stream.getResponseHeaders().get(0).name.utf8()); - assertEquals(length, stream.getResponseHeaders().get(0).value.size()); - assertStreamData("robot", stream.getSource()); + try (MockSpdyPeer peer = new MockSpdyPeer()) { + // write the mocking script + peer.acceptFrame(); // SYN_STREAM + byte[] trailingCompressedBytes = ByteString.decodeBase64(frame).toByteArray(); + trailingCompressedBytes[11] = 1; // Set SPDY/3 stream ID to 3. + peer.sendFrame(trailingCompressedBytes); + peer.sendFrame().data(true, 1, new Buffer().writeUtf8("robot"), 5); + peer.acceptFrame(); // DATA + peer.play(); + + // play it back + FramedConnection connection = connection(peer, SPDY3); + FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); + assertEquals("a", stream.getResponseHeaders().get(0).name.utf8()); + assertEquals(length, stream.getResponseHeaders().get(0).value.size()); + assertStreamData("robot", stream.getSource()); + } } @Test public void socketExceptionWhileWritingHeaders() throws Exception { - peer.acceptFrame(); // SYN_STREAM. - peer.play(); - - String longString = ByteString.of(randomBytes(2048)).base64(); - Socket socket = peer.openSocket(); - FramedConnection connection = new FramedConnection.Builder(true) - .socket(socket) - .protocol(SPDY3.getProtocol()) - .build(); - socket.shutdownOutput(); - try { - connection.newStream(headerEntries("a", longString), false, true); - fail(); - } catch (IOException expected) { - } - try { - connection.newStream(headerEntries("b", longString), false, true); - fail(); - } catch (IOException expected) { + try (MockSpdyPeer peer = new MockSpdyPeer()) { + peer.acceptFrame(); // SYN_STREAM. + peer.play(); + + String longString = ByteString.of(randomBytes(2048)).base64(); + Socket socket = peer.openSocket(); + FramedConnection connection = new FramedConnection.Builder(true) + .socket(socket) + .protocol(SPDY3.getProtocol()) + .build(); + socket.shutdownOutput(); + try { + connection.newStream(headerEntries("a", longString), false, true); + fail(); + } catch (IOException expected) { + } + try { + connection.newStream(headerEntries("b", longString), false, true); + fail(); + } catch (IOException expected) { + } } } diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java index b7f5002..1c5c6e6 100644 --- a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java +++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java @@ -17,7 +17,15 @@ package com.android.okhttp.internalandroidapi; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; + import com.android.okhttp.internalandroidapi.HasCacheHolder.CacheHolder; + +import libcore.util.NonNull; +import libcore.util.Nullable; + import com.android.okhttp.Cache; import com.android.okhttp.Request; import com.android.okhttp.Response; @@ -40,14 +48,21 @@ import java.util.Map; * @hide * @hide This class is not part of the Android public SDK API */ -@libcore.api.CorePlatformApi +@SystemApi(client = MODULE_LIBRARIES) +@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public final class AndroidResponseCacheAdapter { private final CacheHolder cacheHolder; private final Cache okHttpCache; - @libcore.api.CorePlatformApi - public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { + /** + * Creates an instance from {@link CacheHolder} + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public AndroidResponseCacheAdapter(@NonNull CacheHolder cacheHolder) { this.cacheHolder = cacheHolder; // Avoid one level of dereferencing by storing the reference to the OkHttp cache for later. this.okHttpCache = cacheHolder.getCache(); @@ -56,19 +71,25 @@ public final class AndroidResponseCacheAdapter { /** * Returns the {@link CacheHolder} associated with this instance and can be used by OkHttp * internal code to obtain the underlying OkHttp Cache object. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheHolder getCacheHolder() { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @NonNull CacheHolder getCacheHolder() { return cacheHolder; } /** * Used to implement {@link java.net.ResponseCache#get(URI, String, Map)}. See that method for * details. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheResponse get(URI uri, String requestMethod, - Map<String, List<String>> requestHeaders) throws IOException { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @Nullable CacheResponse get(@NonNull URI uri, @NonNull String requestMethod, + @Nullable Map<String, List<String>> requestHeaders) throws IOException { Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders); Response okResponse = okHttpCache.internalCache.get(okRequest); if (okResponse == null) { @@ -80,9 +101,13 @@ public final class AndroidResponseCacheAdapter { /** * Used to implement {@link java.net.ResponseCache#put(URI, URLConnection)}. See that method for * details. + * + * @hide */ - @libcore.api.CorePlatformApi - public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public @Nullable CacheRequest put(@NonNull URI uri, @NonNull URLConnection urlConnection) + throws IOException { Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection); if (okResponse == null) { // The URLConnection is not cacheable or could not be converted. Stop. @@ -100,8 +125,11 @@ public final class AndroidResponseCacheAdapter { * Returns the number of bytes currently being used to store the values in * this cache. This may be greater than the {@link #getMaxSize()} if a background * deletion is pending. IOException is thrown if the size cannot be determined. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getSize() throws IOException { return okHttpCache.getSize(); } @@ -109,8 +137,11 @@ public final class AndroidResponseCacheAdapter { /** * Returns the maximum number of bytes that this cache should use to store * its data. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getMaxSize() { return okHttpCache.getMaxSize(); } @@ -119,8 +150,11 @@ public final class AndroidResponseCacheAdapter { * Force buffered operations to the filesystem. This ensures that responses * written to the cache will be available the next time the cache is opened, * even if this process is killed. IOException is thrown if the flush fails. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void flush() throws IOException { okHttpCache.flush(); } @@ -128,8 +162,11 @@ public final class AndroidResponseCacheAdapter { /** * Returns the number of HTTP requests that required the network to either * supply a response or validate a locally cached response. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getNetworkCount() { return okHttpCache.getNetworkCount(); } @@ -138,8 +175,11 @@ public final class AndroidResponseCacheAdapter { * Returns the number of HTTP requests whose response was provided by the * cache. This may include conditional {@code GET} requests that were * validated over the network. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getHitCount() { return okHttpCache.getHitCount(); } @@ -148,14 +188,22 @@ public final class AndroidResponseCacheAdapter { * Returns the total number of HTTP requests that were made. This includes * both client requests and requests that were made on the client's behalf * to handle a redirects and retries. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getRequestCount() { return okHttpCache.getRequestCount(); } - /** Closes this cache. Stored values will remain on the filesystem. */ - @libcore.api.CorePlatformApi + /** + * Closes this cache. Stored values will remain on the filesystem. + * + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void close() throws IOException { okHttpCache.close(); } @@ -164,8 +212,11 @@ public final class AndroidResponseCacheAdapter { * Closes the cache and deletes all of its stored values. This will delete * all files in the cache directory including files that weren't created by * the cache. + * + * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void delete() throws IOException { okHttpCache.delete(); } diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java index 859ea80..4da9d8f 100644 --- a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java +++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java @@ -26,12 +26,12 @@ import java.util.List; * @hide * @hide This class is not part of the Android public SDK API */ -@libcore.api.CorePlatformApi public interface Dns { /** * Returns the IP addresses of {@code hostname}, in the order they should * be attempted. + * + * @hide */ - @libcore.api.CorePlatformApi List<InetAddress> lookup(String hostname) throws UnknownHostException; } diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java index aa90bf3..67ae1ca 100644 --- a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java +++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java @@ -17,6 +17,12 @@ package com.android.okhttp.internalandroidapi; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.SystemApi; + +import libcore.util.NonNull; + import com.android.okhttp.Cache; import java.io.File; @@ -26,21 +32,26 @@ import java.io.File; * @hide * @hide This class is not part of the Android public SDK API */ -@libcore.api.CorePlatformApi +@SystemApi(client = MODULE_LIBRARIES) +@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public interface HasCacheHolder { /** * Returns the {@link CacheHolder} object. + * + * @hide */ - @libcore.api.CorePlatformApi - CacheHolder getCacheHolder(); + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + @NonNull CacheHolder getCacheHolder(); /** * A holder for an OkHttp internal Cache object. This class exists as an opaque layer over * OkHttp internal classes. * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) final class CacheHolder { private final Cache okHttpCache; @@ -70,9 +81,12 @@ public interface HasCacheHolder { * * @param directory a writable directory * @param maxSizeBytes the maximum number of bytes this cache should use to store + * + * @hide */ - @libcore.api.CorePlatformApi - public static CacheHolder create(File directory, long maxSizeBytes) { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public static @NonNull CacheHolder create(@NonNull File directory, long maxSizeBytes) { Cache cache = new Cache(directory, maxSizeBytes); return new CacheHolder(cache); } @@ -80,9 +94,12 @@ public interface HasCacheHolder { /** * Returns true if the arguments supplied would result in an equivalent cache to this one * being created if they were passed to {@link #create(File, long)}. + * + * @hide */ - @libcore.api.CorePlatformApi - public boolean isEquivalent(File directory, long maxSizeBytes) { + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) + public boolean isEquivalent(@NonNull File directory, long maxSizeBytes) { return (okHttpCache.getDirectory().equals(directory) && okHttpCache.getMaxSize() == maxSizeBytes && !okHttpCache.isClosed()); diff --git a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java index 7f83ee5..f4403b8 100644 --- a/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java +++ b/repackaged/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java @@ -53,27 +53,27 @@ import javax.net.SocketFactory; * @hide * @hide This class is not part of the Android public SDK API */ -@libcore.api.CorePlatformApi public final class HttpURLConnectionFactory { private ConnectionPool connectionPool; private com.android.okhttp.Dns dns; - @libcore.api.CorePlatformApi + /** @hide */ public HttpURLConnectionFactory() { } /** * Sets a new ConnectionPool, specific to this URLFactory and not shared with * any other connections, with the given configuration. + * + * @hide */ - @libcore.api.CorePlatformApi public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } - @libcore.api.CorePlatformApi + /** @hide */ public void setDns(Dns dns) { Objects.requireNonNull(dns); this.dns = new DnsAdapter(dns); @@ -81,6 +81,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the system default proxy settings and SocketFactory. + * + * @hide */ public URLConnection openConnection(URL url) throws IOException { return internalOpenConnection(url, null /* socketFactory */, null /* proxy */); @@ -89,6 +91,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the system default SocketFactory and the specified * proxy settings. + * + * @hide */ public URLConnection openConnection(URL url, Proxy proxy) throws IOException { Objects.requireNonNull(proxy); @@ -98,6 +102,8 @@ public final class HttpURLConnectionFactory { /** * Opens a connection that uses the specified SocketFactory and the system default * proxy settings. + * + * @hide */ public URLConnection openConnection(URL url, SocketFactory socketFactory) throws IOException { Objects.requireNonNull(socketFactory); @@ -107,8 +113,9 @@ public final class HttpURLConnectionFactory { /** * Opens a connection using the specified SocketFactory and the specified proxy * settings, overriding any system wide configuration. + * + * @hide */ - @libcore.api.CorePlatformApi public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy) throws IOException { Objects.requireNonNull(socketFactory); @@ -150,6 +157,8 @@ public final class HttpURLConnectionFactory { /** * Adapts a {@link Dns} as a {@link com.android.okhttp.Dns}. + * + * @hide */ static final class DnsAdapter implements com.android.okhttp.Dns { private final Dns adaptee; |