1 /* 2 * Copyright (C) 2015 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.squareup.okhttp; 17 18 import com.squareup.okhttp.internal.SslContextBuilder; 19 import com.squareup.okhttp.mockwebserver.MockResponse; 20 import com.squareup.okhttp.mockwebserver.MockWebServer; 21 import com.squareup.okhttp.mockwebserver.SocketPolicy; 22 import com.squareup.okhttp.mockwebserver.SocketShutdownListener; 23 import com.squareup.okhttp.testing.RecordingHostnameVerifier; 24 import java.util.Arrays; 25 import java.util.concurrent.TimeUnit; 26 import javax.net.ssl.SSLContext; 27 import org.junit.Rule; 28 import org.junit.Test; 29 import org.junit.rules.TestRule; 30 import org.junit.rules.Timeout; 31 32 import static org.junit.Assert.assertEquals; 33 34 public final class ConnectionReuseTest { 35 @Rule public final TestRule timeout = new Timeout(30_000); 36 @Rule public final MockWebServer server = new MockWebServer(); 37 38 private SSLContext sslContext = SslContextBuilder.localhost(); 39 private OkHttpClient client = new OkHttpClient(); 40 connectionsAreReused()41 @Test public void connectionsAreReused() throws Exception { 42 server.enqueue(new MockResponse().setBody("a")); 43 server.enqueue(new MockResponse().setBody("b")); 44 45 Request request = new Request.Builder() 46 .url(server.url("/")) 47 .build(); 48 assertConnectionReused(request, request); 49 } 50 connectionsAreReusedWithHttp2()51 @Test public void connectionsAreReusedWithHttp2() throws Exception { 52 enableHttp2(); 53 server.enqueue(new MockResponse().setBody("a")); 54 server.enqueue(new MockResponse().setBody("b")); 55 56 Request request = new Request.Builder() 57 .url(server.url("/")) 58 .build(); 59 assertConnectionReused(request, request); 60 } 61 connectionsAreNotReusedWithRequestConnectionClose()62 @Test public void connectionsAreNotReusedWithRequestConnectionClose() throws Exception { 63 server.enqueue(new MockResponse().setBody("a")); 64 server.enqueue(new MockResponse().setBody("b")); 65 66 Request requestA = new Request.Builder() 67 .url(server.url("/")) 68 .header("Connection", "close") 69 .build(); 70 Request requestB = new Request.Builder() 71 .url(server.url("/")) 72 .build(); 73 assertConnectionNotReused(requestA, requestB); 74 } 75 connectionsAreNotReusedWithResponseConnectionClose()76 @Test public void connectionsAreNotReusedWithResponseConnectionClose() throws Exception { 77 server.enqueue(new MockResponse() 78 .addHeader("Connection", "close") 79 .setBody("a")); 80 server.enqueue(new MockResponse().setBody("b")); 81 82 Request requestA = new Request.Builder() 83 .url(server.url("/")) 84 .build(); 85 Request requestB = new Request.Builder() 86 .url(server.url("/")) 87 .build(); 88 assertConnectionNotReused(requestA, requestB); 89 } 90 connectionsAreNotReusedWithUnknownLengthResponseBody()91 @Test public void connectionsAreNotReusedWithUnknownLengthResponseBody() throws Exception { 92 server.enqueue(new MockResponse() 93 .setBody("a") 94 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END) 95 .clearHeaders()); 96 server.enqueue(new MockResponse().setBody("b")); 97 98 Request request = new Request.Builder() 99 .url(server.url("/")) 100 .build(); 101 assertConnectionNotReused(request, request); 102 } 103 connectionsAreNotReusedIfPoolIsSizeZero()104 @Test public void connectionsAreNotReusedIfPoolIsSizeZero() throws Exception { 105 client.setConnectionPool(new ConnectionPool(0, 5000)); 106 server.enqueue(new MockResponse().setBody("a")); 107 server.enqueue(new MockResponse().setBody("b")); 108 109 Request request = new Request.Builder() 110 .url(server.url("/")) 111 .build(); 112 assertConnectionNotReused(request, request); 113 } 114 connectionsReusedWithRedirectEvenIfPoolIsSizeZero()115 @Test public void connectionsReusedWithRedirectEvenIfPoolIsSizeZero() throws Exception { 116 client.setConnectionPool(new ConnectionPool(0, 5000)); 117 server.enqueue(new MockResponse() 118 .setResponseCode(301) 119 .addHeader("Location: /b") 120 .setBody("a")); 121 server.enqueue(new MockResponse().setBody("b")); 122 123 Request request = new Request.Builder() 124 .url(server.url("/")) 125 .build(); 126 Response response = client.newCall(request).execute(); 127 assertEquals("b", response.body().string()); 128 assertEquals(0, server.takeRequest().getSequenceNumber()); 129 assertEquals(1, server.takeRequest().getSequenceNumber()); 130 } 131 connectionsNotReusedWithRedirectIfDiscardingResponseIsSlow()132 @Test public void connectionsNotReusedWithRedirectIfDiscardingResponseIsSlow() throws Exception { 133 client.setConnectionPool(new ConnectionPool(0, 5000)); 134 server.enqueue(new MockResponse() 135 .setResponseCode(301) 136 .addHeader("Location: /b") 137 .setBodyDelay(1, TimeUnit.SECONDS) 138 .setBody("a")); 139 server.enqueue(new MockResponse().setBody("b")); 140 141 Request request = new Request.Builder() 142 .url(server.url("/")) 143 .build(); 144 Response response = client.newCall(request).execute(); 145 assertEquals("b", response.body().string()); 146 assertEquals(0, server.takeRequest().getSequenceNumber()); 147 assertEquals(0, server.takeRequest().getSequenceNumber()); 148 } 149 silentRetryWhenIdempotentRequestFailsOnReusedConnection()150 @Test public void silentRetryWhenIdempotentRequestFailsOnReusedConnection() throws Exception { 151 server.enqueue(new MockResponse().setBody("a")); 152 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST)); 153 server.enqueue(new MockResponse().setBody("b")); 154 155 Request request = new Request.Builder() 156 .url(server.url("/")) 157 .build(); 158 159 Response responseA = client.newCall(request).execute(); 160 assertEquals("a", responseA.body().string()); 161 assertEquals(0, server.takeRequest().getSequenceNumber()); 162 163 Response responseB = client.newCall(request).execute(); 164 assertEquals("b", responseB.body().string()); 165 assertEquals(1, server.takeRequest().getSequenceNumber()); 166 assertEquals(0, server.takeRequest().getSequenceNumber()); 167 } 168 staleConnectionNotReusedForNonIdempotentRequest()169 @Test public void staleConnectionNotReusedForNonIdempotentRequest() throws Exception { 170 SocketShutdownListener shutdownListener = new SocketShutdownListener(); 171 server.enqueue(new MockResponse().setBody("a") 172 .setSocketPolicy(SocketPolicy.SHUTDOWN_OUTPUT_AT_END) 173 .setSocketShutdownListener(shutdownListener)); 174 server.enqueue(new MockResponse().setBody("b")); 175 176 Request requestA = new Request.Builder() 177 .url(server.url("/")) 178 .build(); 179 Response responseA = client.newCall(requestA).execute(); 180 assertEquals("a", responseA.body().string()); 181 assertEquals(0, server.takeRequest().getSequenceNumber()); 182 183 shutdownListener.waitForSocketShutdown(); 184 185 Request requestB = new Request.Builder() 186 .url(server.url("/")) 187 .post(RequestBody.create(MediaType.parse("text/plain"), "b")) 188 .build(); 189 Response responseB = client.newCall(requestB).execute(); 190 assertEquals("b", responseB.body().string()); 191 assertEquals(0, server.takeRequest().getSequenceNumber()); 192 } 193 http2ConnectionsAreSharedBeforeResponseIsConsumed()194 @Test public void http2ConnectionsAreSharedBeforeResponseIsConsumed() throws Exception { 195 enableHttp2(); 196 server.enqueue(new MockResponse().setBody("a")); 197 server.enqueue(new MockResponse().setBody("b")); 198 199 Request request = new Request.Builder() 200 .url(server.url("/")) 201 .build(); 202 Response response1 = client.newCall(request).execute(); 203 Response response2 = client.newCall(request).execute(); 204 response1.body().string(); // Discard the response body. 205 response2.body().string(); // Discard the response body. 206 assertEquals(0, server.takeRequest().getSequenceNumber()); 207 assertEquals(1, server.takeRequest().getSequenceNumber()); 208 } 209 connectionsAreEvicted()210 @Test public void connectionsAreEvicted() throws Exception { 211 server.enqueue(new MockResponse().setBody("a")); 212 server.enqueue(new MockResponse().setBody("b")); 213 214 client.setConnectionPool(new ConnectionPool(5, 250, TimeUnit.MILLISECONDS)); 215 Request request = new Request.Builder() 216 .url(server.url("/")) 217 .build(); 218 219 Response response1 = client.newCall(request).execute(); 220 assertEquals("a", response1.body().string()); 221 222 // Give the thread pool a chance to evict. 223 Thread.sleep(500); 224 225 Response response2 = client.newCall(request).execute(); 226 assertEquals("b", response2.body().string()); 227 228 assertEquals(0, server.takeRequest().getSequenceNumber()); 229 assertEquals(0, server.takeRequest().getSequenceNumber()); 230 } 231 enableHttp2()232 private void enableHttp2() { 233 client.setSslSocketFactory(sslContext.getSocketFactory()); 234 client.setHostnameVerifier(new RecordingHostnameVerifier()); 235 client.setProtocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)); 236 server.useHttps(sslContext.getSocketFactory(), false); 237 server.setProtocols(client.getProtocols()); 238 } 239 assertConnectionReused(Request... requests)240 private void assertConnectionReused(Request... requests) throws Exception { 241 for (int i = 0; i < requests.length; i++) { 242 Response response = client.newCall(requests[i]).execute(); 243 response.body().string(); // Discard the response body. 244 assertEquals(i, server.takeRequest().getSequenceNumber()); 245 } 246 } 247 assertConnectionNotReused(Request... requests)248 private void assertConnectionNotReused(Request... requests) throws Exception { 249 for (Request request : requests) { 250 Response response = client.newCall(request).execute(); 251 response.body().string(); // Discard the response body. 252 assertEquals(0, server.takeRequest().getSequenceNumber()); 253 } 254 } 255 } 256