From 4be57c04a85daaed7e595e3b77967b32ade2c3de Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 23 Jun 2021 14:08:16 +0100 Subject: Document and @hide okhttp APIs Bug: 154796679 Test: m droid Merged-In: I051ec677d0d70de61b6d58a4f37ec1e341250452 Change-Id: I051ec677d0d70de61b6d58a4f37ec1e341250452 --- .../AndroidResponseCacheAdapter.java | 31 +++++++++++++++++++++- .../com/android/okhttp/internalandroidapi/Dns.java | 2 ++ .../okhttp/internalandroidapi/HasCacheHolder.java | 6 +++++ .../HttpURLConnectionFactory.java | 14 ++++++++++ .../AndroidResponseCacheAdapter.java | 31 +++++++++++++++++++++- .../com/android/okhttp/internalandroidapi/Dns.java | 2 ++ .../okhttp/internalandroidapi/HasCacheHolder.java | 6 +++++ .../HttpURLConnectionFactory.java | 14 ++++++++++ 8 files changed, 104 insertions(+), 2 deletions(-) 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..bf7e9a9 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/AndroidResponseCacheAdapter.java @@ -44,6 +44,11 @@ public final class AndroidResponseCacheAdapter { private final CacheHolder cacheHolder; private final Cache okHttpCache; + /** + * Creates an instance from {@link CacheHolder} + * + * @hide + */ @libcore.api.CorePlatformApi public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { this.cacheHolder = cacheHolder; @@ -54,6 +59,8 @@ 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() { @@ -63,6 +70,8 @@ public final class AndroidResponseCacheAdapter { /** * 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, @@ -78,6 +87,8 @@ 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 { @@ -98,6 +109,8 @@ 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 public long getSize() throws IOException { @@ -107,6 +120,8 @@ public final class AndroidResponseCacheAdapter { /** * Returns the maximum number of bytes that this cache should use to store * its data. + * + * @hide */ @libcore.api.CorePlatformApi public long getMaxSize() { @@ -117,6 +132,8 @@ 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 public void flush() throws IOException { @@ -126,6 +143,8 @@ 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 public int getNetworkCount() { @@ -136,6 +155,8 @@ 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 public int getHitCount() { @@ -146,13 +167,19 @@ 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 public int getRequestCount() { return okHttpCache.getRequestCount(); } - /** Closes this cache. Stored values will remain on the filesystem. */ + /** + * Closes this cache. Stored values will remain on the filesystem. + * + * @hide + */ @libcore.api.CorePlatformApi public void close() throws IOException { okHttpCache.close(); @@ -162,6 +189,8 @@ 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 public void delete() throws IOException { 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..20f84da 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/Dns.java @@ -29,6 +29,8 @@ public interface Dns { /** * Returns the IP addresses of {@code hostname}, in the order they should * be attempted. + * + * @hide */ @libcore.api.CorePlatformApi List 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..895b576 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/HasCacheHolder.java @@ -29,6 +29,8 @@ public interface HasCacheHolder { /** * Returns the {@link CacheHolder} object. + * + * @hide */ @libcore.api.CorePlatformApi CacheHolder getCacheHolder(); @@ -68,6 +70,8 @@ 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) { @@ -78,6 +82,8 @@ 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) { 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..d6a7402 100644 --- a/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java +++ b/android/src/main/java/com/android/okhttp/internalandroidapi/HttpURLConnectionFactory.java @@ -57,6 +57,7 @@ public final class HttpURLConnectionFactory { private ConnectionPool connectionPool; private com.squareup.okhttp.Dns dns; + /** @hide */ @libcore.api.CorePlatformApi public HttpURLConnectionFactory() { } @@ -64,6 +65,8 @@ public final class 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, @@ -71,6 +74,7 @@ public final class HttpURLConnectionFactory { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } + /** @hide */ @libcore.api.CorePlatformApi public void setDns(Dns dns) { Objects.requireNonNull(dns); @@ -79,6 +83,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 +93,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 +104,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,6 +115,8 @@ 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) @@ -148,6 +160,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/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..20f8359 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 @@ -46,6 +46,11 @@ public final class AndroidResponseCacheAdapter { private final CacheHolder cacheHolder; private final Cache okHttpCache; + /** + * Creates an instance from {@link CacheHolder} + * + * @hide + */ @libcore.api.CorePlatformApi public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { this.cacheHolder = cacheHolder; @@ -56,6 +61,8 @@ 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() { @@ -65,6 +72,8 @@ public final class AndroidResponseCacheAdapter { /** * 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, @@ -80,6 +89,8 @@ 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 { @@ -100,6 +111,8 @@ 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 public long getSize() throws IOException { @@ -109,6 +122,8 @@ public final class AndroidResponseCacheAdapter { /** * Returns the maximum number of bytes that this cache should use to store * its data. + * + * @hide */ @libcore.api.CorePlatformApi public long getMaxSize() { @@ -119,6 +134,8 @@ 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 public void flush() throws IOException { @@ -128,6 +145,8 @@ 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 public int getNetworkCount() { @@ -138,6 +157,8 @@ 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 public int getHitCount() { @@ -148,13 +169,19 @@ 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 public int getRequestCount() { return okHttpCache.getRequestCount(); } - /** Closes this cache. Stored values will remain on the filesystem. */ + /** + * Closes this cache. Stored values will remain on the filesystem. + * + * @hide + */ @libcore.api.CorePlatformApi public void close() throws IOException { okHttpCache.close(); @@ -164,6 +191,8 @@ 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 public void delete() throws IOException { 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..104f23f 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 @@ -31,6 +31,8 @@ public interface Dns { /** * Returns the IP addresses of {@code hostname}, in the order they should * be attempted. + * + * @hide */ @libcore.api.CorePlatformApi List 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..757c006 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 @@ -31,6 +31,8 @@ public interface HasCacheHolder { /** * Returns the {@link CacheHolder} object. + * + * @hide */ @libcore.api.CorePlatformApi CacheHolder getCacheHolder(); @@ -70,6 +72,8 @@ 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) { @@ -80,6 +84,8 @@ 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) { 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..91266ab 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 @@ -59,6 +59,7 @@ public final class HttpURLConnectionFactory { private ConnectionPool connectionPool; private com.android.okhttp.Dns dns; + /** @hide */ @libcore.api.CorePlatformApi public HttpURLConnectionFactory() { } @@ -66,6 +67,8 @@ public final class 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, @@ -73,6 +76,7 @@ public final class HttpURLConnectionFactory { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } + /** @hide */ @libcore.api.CorePlatformApi public void setDns(Dns dns) { Objects.requireNonNull(dns); @@ -81,6 +85,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 +95,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 +106,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,6 +117,8 @@ 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) @@ -150,6 +162,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; -- cgit v1.2.3 From 381a66cc396e4402ff2cb3415a6ef116e1413c3f Mon Sep 17 00:00:00 2001 From: Victor Chang Date: Wed, 23 Jun 2021 14:39:47 +0100 Subject: Remove unused @CorePlatformApi and mark APIs in okhttp as @SystemApi Bug: 157639992 Bug: 154796679 Test: m update-api Merged-In: I9d570f00044ee0d0abbf1d867397f122afb22bdd Change-Id: I9d570f00044ee0d0abbf1d867397f122afb22bdd (cherry picked from commit 04fa37675e6192b75e8c404915205d9af24ddee8) --- .../AndroidResponseCacheAdapter.java | 58 +++++++++++++++------- .../com/android/okhttp/internalandroidapi/Dns.java | 2 - .../okhttp/internalandroidapi/HasCacheHolder.java | 27 +++++++--- .../HttpURLConnectionFactory.java | 5 -- .../AndroidResponseCacheAdapter.java | 58 +++++++++++++++------- .../com/android/okhttp/internalandroidapi/Dns.java | 2 - .../okhttp/internalandroidapi/HasCacheHolder.java | 27 +++++++--- .../HttpURLConnectionFactory.java | 5 -- 8 files changed, 118 insertions(+), 66 deletions(-) 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 bf7e9a9..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,7 +46,8 @@ 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; @@ -49,8 +58,9 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi - public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { + @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(); @@ -62,8 +72,9 @@ public final class AndroidResponseCacheAdapter { * * @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; } @@ -73,9 +84,10 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi - public CacheResponse get(URI uri, String requestMethod, - Map> 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> requestHeaders) throws IOException { Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders); Response okResponse = okHttpCache.internalCache.get(okRequest); if (okResponse == null) { @@ -90,8 +102,10 @@ public final class AndroidResponseCacheAdapter { * * @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. @@ -112,7 +126,8 @@ public final class AndroidResponseCacheAdapter { * * @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(); } @@ -123,7 +138,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getMaxSize() { return okHttpCache.getMaxSize(); } @@ -135,7 +151,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void flush() throws IOException { okHttpCache.flush(); } @@ -146,7 +163,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getNetworkCount() { return okHttpCache.getNetworkCount(); } @@ -158,7 +176,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getHitCount() { return okHttpCache.getHitCount(); } @@ -170,7 +189,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getRequestCount() { return okHttpCache.getRequestCount(); } @@ -180,7 +200,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void close() throws IOException { okHttpCache.close(); } @@ -192,7 +213,8 @@ public final class AndroidResponseCacheAdapter { * * @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 20f84da..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,7 +24,6 @@ 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 @@ -32,6 +31,5 @@ public interface Dns { * * @hide */ - @libcore.api.CorePlatformApi List 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 895b576..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,7 +30,8 @@ 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 { /** @@ -32,15 +39,17 @@ public interface HasCacheHolder { * * @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; @@ -73,8 +82,9 @@ public interface HasCacheHolder { * * @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); } @@ -85,8 +95,9 @@ public interface HasCacheHolder { * * @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 d6a7402..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,14 +51,12 @@ import javax.net.SocketFactory; * * @hide */ -@libcore.api.CorePlatformApi public final class HttpURLConnectionFactory { private ConnectionPool connectionPool; private com.squareup.okhttp.Dns dns; /** @hide */ - @libcore.api.CorePlatformApi public HttpURLConnectionFactory() { } @@ -68,14 +66,12 @@ public final class HttpURLConnectionFactory { * * @hide */ - @libcore.api.CorePlatformApi public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } /** @hide */ - @libcore.api.CorePlatformApi public void setDns(Dns dns) { Objects.requireNonNull(dns); this.dns = new DnsAdapter(dns); @@ -118,7 +114,6 @@ public final class HttpURLConnectionFactory { * * @hide */ - @libcore.api.CorePlatformApi public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy) throws IOException { Objects.requireNonNull(socketFactory); 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 20f8359..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,7 +48,8 @@ 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; @@ -51,8 +60,9 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi - public AndroidResponseCacheAdapter(CacheHolder cacheHolder) { + @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(); @@ -64,8 +74,9 @@ public final class AndroidResponseCacheAdapter { * * @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; } @@ -75,9 +86,10 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi - public CacheResponse get(URI uri, String requestMethod, - Map> 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> requestHeaders) throws IOException { Request okRequest = JavaApiConverter.createOkRequest(uri, requestMethod, requestHeaders); Response okResponse = okHttpCache.internalCache.get(okRequest); if (okResponse == null) { @@ -92,8 +104,10 @@ public final class AndroidResponseCacheAdapter { * * @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. @@ -114,7 +128,8 @@ public final class AndroidResponseCacheAdapter { * * @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(); } @@ -125,7 +140,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public long getMaxSize() { return okHttpCache.getMaxSize(); } @@ -137,7 +153,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void flush() throws IOException { okHttpCache.flush(); } @@ -148,7 +165,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getNetworkCount() { return okHttpCache.getNetworkCount(); } @@ -160,7 +178,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getHitCount() { return okHttpCache.getHitCount(); } @@ -172,7 +191,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public int getRequestCount() { return okHttpCache.getRequestCount(); } @@ -182,7 +202,8 @@ public final class AndroidResponseCacheAdapter { * * @hide */ - @libcore.api.CorePlatformApi + @SystemApi(client = MODULE_LIBRARIES) + @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) public void close() throws IOException { okHttpCache.close(); } @@ -194,7 +215,8 @@ public final class AndroidResponseCacheAdapter { * * @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 104f23f..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,7 +26,6 @@ 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 @@ -34,6 +33,5 @@ public interface Dns { * * @hide */ - @libcore.api.CorePlatformApi List 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 757c006..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,7 +32,8 @@ 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 { /** @@ -34,15 +41,17 @@ public interface HasCacheHolder { * * @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; @@ -75,8 +84,9 @@ public interface HasCacheHolder { * * @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); } @@ -87,8 +97,9 @@ public interface HasCacheHolder { * * @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 91266ab..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,14 +53,12 @@ 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; /** @hide */ - @libcore.api.CorePlatformApi public HttpURLConnectionFactory() { } @@ -70,14 +68,12 @@ public final class HttpURLConnectionFactory { * * @hide */ - @libcore.api.CorePlatformApi public void setNewConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) { this.connectionPool = new ConnectionPool(maxIdleConnections, keepAliveDuration, timeUnit); } /** @hide */ - @libcore.api.CorePlatformApi public void setDns(Dns dns) { Objects.requireNonNull(dns); this.dns = new DnsAdapter(dns); @@ -120,7 +116,6 @@ public final class HttpURLConnectionFactory { * * @hide */ - @libcore.api.CorePlatformApi public URLConnection openConnection(URL url, SocketFactory socketFactory, Proxy proxy) throws IOException { Objects.requireNonNull(socketFactory); -- cgit v1.2.3 From a40903c6d4b947e0c4503e0ed753746f2ae5d41a Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Thu, 24 Jun 2021 06:14:00 +0000 Subject: Making the OkHttp internal framed tests more stable The Spdy3ConnectionTest experienced some flakyness and inspecting it things could be improved. First of all the MockSpdyPeer.play() function was returning before the peer task executes, allowing room for race conditions. This has been fixed with the use of a CountDownLatch, so play() now waits for the task to start handling HTTP requests. Another potential problem is that tests would share the instance of MockSpdyPeer and rely on tearDown to clear any data, or resources. Tests have been updated to have their own instance of the class which gets auto-closed on test end. A last issue found is one where a listener would first provide an HTTP response before marking that the callback was received. This could cause a race condition as the main thread may start handling the response and checking if the callback was indeed received. It got fixed by first marking the callback and then providing the response. (cherry picked from commit 4291869755a4daebb5ec558e1b51f8d6080bb956) Bug: 191097565 Test: atest CtsLibcoreOkHttpTestCases --iterations 300 Test: atest CtsLibcoreOkHttpTestCases:com.squareup.okhttp.internal.framed.Spdy3ConnectionTest --iterations 300 Test: atest CtsLibcoreOkHttpTestCases:com.squareup.okhttp.internal.framed.Http2ConnectionTest --iterations 300 Merged-In: I88954f470d67299ff3c865a951e1ad91c7c8d1dd Change-Id: I88954f470d67299ff3c865a951e1ad91c7c8d1dd --- .../internal/framed/Http2ConnectionTest.java | 703 +++--- .../okhttp/internal/framed/MockSpdyPeer.java | 9 + .../internal/framed/Spdy3ConnectionTest.java | 2229 ++++++++++---------- 3 files changed, 1525 insertions(+), 1416 deletions(-) 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 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 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
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
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
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
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 { *

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..38961ef 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,441 @@ 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); + assertEquals(1, connection.openStreamCount()); + 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

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
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 +500,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 +544,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 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 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 +1221,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 +1385,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) { + } } } -- cgit v1.2.3 From 541335a9306579edf064d1be862192891697050d Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Wed, 14 Jul 2021 11:21:08 +0000 Subject: Fixing stability of headersOnlyStreamIsClosedAfterReplyHeaders Spdy3ConnectionTest.headersOnlyStreamIsClosedAfterReplyHeaders was occasionally failing. The error seems to be due to the fact that when a new stream is created, if only headers are exchanged and the exchange happens too quickly, then the stream is not marked as open, which causes the test to fail, as it expects an open stream after creation. This was fixed in upstream by removing the check that fails due to race conditions. See https://github.com/square/okhttp/commit/49dfda04ae9df1b915ccea17ca1e70ff48e06e6c (cherry picked from commit 9a853ef51ae582db4c7062d0410222379fb6e411) Bug: 191097565 Test: atest CtsLibcoreOkHttpTestCases: com.squareup.okhttp.internal.framed.Spdy3ConnectionTest --iterations 1000 Merged-In: Ic73d98407c617ea86bf4a6689109d55240043670 Change-Id: Ic73d98407c617ea86bf4a6689109d55240043670 --- .../java/com/squareup/okhttp/internal/framed/Spdy3ConnectionTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 38961ef..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 @@ -103,7 +103,8 @@ public final class Spdy3ConnectionTest { FramedConnection connection = connection(peer, SPDY3); FramedStream stream = connection.newStream(headerEntries("a", "android"), false, false); - assertEquals(1, connection.openStreamCount()); + // 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()); -- cgit v1.2.3 From 35aef1ba398a2b1dd2ff0bc20fa6203a9e32ba85 Mon Sep 17 00:00:00 2001 From: Sorin Basca Date: Mon, 19 Jul 2021 14:16:33 +0000 Subject: Fixing stability of CallTest.cancelInFlightBeforeResponseReadThrowsIOE The CallTest.cancelInFlightBeforeResponseReadThrowsIOE tests fail occasionally. It looks like a race condition between the cancel going through and the response message being handled from the client side. The test code has been updated to make the server-side hold off the response until the cancel is seen on the client-side. (cherry picked from commit 6bb2abab50a48217af33c9872ead82ba507f0ffa) Bug: 191957489 Test: atest CtsLibcoreOkHttpTestCases:com.squareup.okhttp.CallTest --iterations 1000 Merged-In: Ic9a8a0ef59723baa08b44ad9db0dc5c695ba9578 Change-Id: Ic9a8a0ef59723baa08b44ad9db0dc5c695ba9578 --- okhttp-tests/src/test/java/com/squareup/okhttp/CallTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) 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(); } } -- cgit v1.2.3