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