1/*
2 * Copyright (C) 2013 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.RecordingHostnameVerifier;
19import com.squareup.okhttp.internal.SslContextBuilder;
20import com.squareup.okhttp.mockwebserver.Dispatcher;
21import com.squareup.okhttp.mockwebserver.MockResponse;
22import com.squareup.okhttp.mockwebserver.MockWebServer;
23import com.squareup.okhttp.mockwebserver.RecordedRequest;
24import com.squareup.okhttp.mockwebserver.SocketPolicy;
25import java.io.File;
26import java.io.IOException;
27import java.io.InputStream;
28import java.net.HttpURLConnection;
29import java.util.UUID;
30import java.util.concurrent.CountDownLatch;
31import java.util.concurrent.atomic.AtomicReference;
32import javax.net.ssl.SSLContext;
33import org.junit.After;
34import org.junit.Before;
35import org.junit.Test;
36
37import static org.junit.Assert.assertEquals;
38import static org.junit.Assert.assertNull;
39import static org.junit.Assert.assertTrue;
40
41public final class AsyncApiTest {
42  private MockWebServer server = new MockWebServer();
43  private OkHttpClient client = new OkHttpClient();
44  private RecordingReceiver receiver = new RecordingReceiver();
45
46  private static final SSLContext sslContext = SslContextBuilder.localhost();
47  private HttpResponseCache cache;
48
49  @Before public void setUp() throws Exception {
50    String tmp = System.getProperty("java.io.tmpdir");
51    File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
52    cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
53  }
54
55  @After public void tearDown() throws Exception {
56    server.shutdown();
57    cache.delete();
58  }
59
60  @Test public void get() throws Exception {
61    server.enqueue(new MockResponse()
62        .setBody("abc")
63        .addHeader("Content-Type: text/plain"));
64    server.play();
65
66    Request request = new Request.Builder()
67        .url(server.getUrl("/"))
68        .header("User-Agent", "AsyncApiTest")
69        .build();
70    client.enqueue(request, receiver);
71
72    receiver.await(request.url())
73        .assertCode(200)
74        .assertContainsHeaders("Content-Type: text/plain")
75        .assertBody("abc");
76
77    assertTrue(server.takeRequest().getHeaders().contains("User-Agent: AsyncApiTest"));
78  }
79
80  @Test public void connectionPooling() throws Exception {
81    server.enqueue(new MockResponse().setBody("abc"));
82    server.enqueue(new MockResponse().setBody("def"));
83    server.enqueue(new MockResponse().setBody("ghi"));
84    server.play();
85
86    client.enqueue(new Request.Builder().url(server.getUrl("/a")).build(), receiver);
87    receiver.await(server.getUrl("/a")).assertBody("abc");
88
89    client.enqueue(new Request.Builder().url(server.getUrl("/b")).build(), receiver);
90    receiver.await(server.getUrl("/b")).assertBody("def");
91
92    client.enqueue(new Request.Builder().url(server.getUrl("/c")).build(), receiver);
93    receiver.await(server.getUrl("/c")).assertBody("ghi");
94
95    assertEquals(0, server.takeRequest().getSequenceNumber());
96    assertEquals(1, server.takeRequest().getSequenceNumber());
97    assertEquals(2, server.takeRequest().getSequenceNumber());
98  }
99
100  @Test public void tls() throws Exception {
101    server.useHttps(sslContext.getSocketFactory(), false);
102    server.enqueue(new MockResponse()
103        .setBody("abc")
104        .addHeader("Content-Type: text/plain"));
105    server.play();
106
107    client.setSslSocketFactory(sslContext.getSocketFactory());
108    client.setHostnameVerifier(new RecordingHostnameVerifier());
109
110    Request request = new Request.Builder()
111        .url(server.getUrl("/"))
112        .build();
113    client.enqueue(request, receiver);
114
115    receiver.await(request.url()).assertHandshake();
116  }
117
118  @Test public void recoverFromTlsHandshakeFailure() throws Exception {
119    server.useHttps(sslContext.getSocketFactory(), false);
120    server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
121    server.enqueue(new MockResponse().setBody("abc"));
122    server.play();
123
124    client.setSslSocketFactory(sslContext.getSocketFactory());
125    client.setHostnameVerifier(new RecordingHostnameVerifier());
126
127    Request request = new Request.Builder()
128        .url(server.getUrl("/"))
129        .build();
130    client.enqueue(request, receiver);
131
132    receiver.await(request.url()).assertBody("abc");
133  }
134
135  @Test public void post() throws Exception {
136    server.enqueue(new MockResponse().setBody("abc"));
137    server.play();
138
139    Request request = new Request.Builder()
140        .url(server.getUrl("/"))
141        .post(Request.Body.create(MediaType.parse("text/plain"), "def"))
142        .build();
143    client.enqueue(request, receiver);
144
145    receiver.await(request.url())
146        .assertCode(200)
147        .assertBody("abc");
148
149    RecordedRequest recordedRequest = server.takeRequest();
150    assertEquals("def", recordedRequest.getUtf8Body());
151    assertEquals("3", recordedRequest.getHeader("Content-Length"));
152    assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
153  }
154
155  @Test public void conditionalCacheHit() throws Exception {
156    server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
157    server.enqueue(new MockResponse()
158        .clearHeaders()
159        .setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
160    server.play();
161
162    client.setOkResponseCache(cache);
163
164    Request request1 = new Request.Builder()
165        .url(server.getUrl("/"))
166        .build();
167    client.enqueue(request1, receiver);
168    receiver.await(request1.url()).assertCode(200).assertBody("A");
169    assertNull(server.takeRequest().getHeader("If-None-Match"));
170
171    Request request2 = new Request.Builder()
172        .url(server.getUrl("/"))
173        .build();
174    client.enqueue(request2, receiver);
175    receiver.await(request2.url()).assertCode(200).assertBody("A");
176    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
177  }
178
179  @Test public void conditionalCacheMiss() throws Exception {
180    server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
181    server.enqueue(new MockResponse().setBody("B"));
182    server.play();
183
184    client.setOkResponseCache(cache);
185
186    Request request1 = new Request.Builder()
187        .url(server.getUrl("/"))
188        .build();
189    client.enqueue(request1, receiver);
190    receiver.await(request1.url()).assertCode(200).assertBody("A");
191    assertNull(server.takeRequest().getHeader("If-None-Match"));
192
193    Request request2 = new Request.Builder()
194        .url(server.getUrl("/"))
195        .build();
196    client.enqueue(request2, receiver);
197    receiver.await(request2.url()).assertCode(200).assertBody("B");
198    assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
199  }
200
201  @Test public void redirect() throws Exception {
202    server.enqueue(new MockResponse()
203        .setResponseCode(301)
204        .addHeader("Location: /b")
205        .addHeader("Test", "Redirect from /a to /b")
206        .setBody("/a has moved!"));
207    server.enqueue(new MockResponse()
208        .setResponseCode(302)
209        .addHeader("Location: /c")
210        .addHeader("Test", "Redirect from /b to /c")
211        .setBody("/b has moved!"));
212    server.enqueue(new MockResponse().setBody("C"));
213    server.play();
214
215    Request request = new Request.Builder().url(server.getUrl("/a")).build();
216    client.enqueue(request, receiver);
217
218    receiver.await(server.getUrl("/c"))
219        .assertCode(200)
220        .assertBody("C")
221        .redirectedBy()
222        .assertCode(302)
223        .assertContainsHeaders("Test: Redirect from /b to /c")
224        .redirectedBy()
225        .assertCode(301)
226        .assertContainsHeaders("Test: Redirect from /a to /b");
227
228    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
229    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reused.
230    assertEquals(2, server.takeRequest().getSequenceNumber()); // Connection reused again!
231  }
232
233  @Test public void redirectWithRedirectsDisabled() throws Exception {
234    client.setFollowProtocolRedirects(false);
235    server.enqueue(new MockResponse()
236        .setResponseCode(301)
237        .addHeader("Location: /b")
238        .addHeader("Test", "Redirect from /a to /b")
239        .setBody("/a has moved!"));
240    server.play();
241
242    Request request = new Request.Builder().url(server.getUrl("/a")).build();
243    client.enqueue(request, receiver);
244
245    receiver.await(server.getUrl("/a"))
246        .assertCode(301)
247        .assertBody("/a has moved!")
248        .assertContainsHeaders("Location: /b");
249  }
250
251  @Test public void follow20Redirects() throws Exception {
252    for (int i = 0; i < 20; i++) {
253      server.enqueue(new MockResponse()
254          .setResponseCode(301)
255          .addHeader("Location: /" + (i + 1))
256          .setBody("Redirecting to /" + (i + 1)));
257    }
258    server.enqueue(new MockResponse().setBody("Success!"));
259    server.play();
260
261    Request request = new Request.Builder().url(server.getUrl("/0")).build();
262    client.enqueue(request, receiver);
263    receiver.await(server.getUrl("/20"))
264        .assertCode(200)
265        .assertBody("Success!");
266  }
267
268  @Test public void doesNotFollow21Redirects() throws Exception {
269    for (int i = 0; i < 21; i++) {
270      server.enqueue(new MockResponse()
271          .setResponseCode(301)
272          .addHeader("Location: /" + (i + 1))
273          .setBody("Redirecting to /" + (i + 1)));
274    }
275    server.play();
276
277    Request request = new Request.Builder().url(server.getUrl("/0")).build();
278    client.enqueue(request, receiver);
279    receiver.await(server.getUrl("/20")).assertFailure("Too many redirects: 21");
280  }
281
282  @Test public void canceledBeforeResponseReadIsNeverDelivered() throws Exception {
283    client.getDispatcher().setMaxRequests(1); // Force requests to be executed serially.
284    server.setDispatcher(new Dispatcher() {
285      char nextResponse = 'A';
286      @Override public MockResponse dispatch(RecordedRequest request) {
287        client.cancel("request A");
288        return new MockResponse().setBody(Character.toString(nextResponse++));
289      }
290    });
291    server.play();
292
293    // Canceling a request after the server has received a request but before
294    // it has delivered the response. That request will never be received to the
295    // client.
296    Request requestA = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
297    client.enqueue(requestA, receiver);
298    assertEquals("/a", server.takeRequest().getPath());
299
300    // We then make a second request (not canceled) to make sure the receiver
301    // has nothing left to wait for.
302    Request requestB = new Request.Builder().url(server.getUrl("/b")).tag("request B").build();
303    client.enqueue(requestB, receiver);
304    assertEquals("/b", server.takeRequest().getPath());
305    receiver.await(requestB.url()).assertBody("B");
306
307    // At this point we know the receiver is ready: if it hasn't received 'A'
308    // yet it never will.
309    receiver.assertNoResponse(requestA.url());
310  }
311
312  @Test public void canceledAfterResponseIsDeliveredDoesNothing() throws Exception {
313    server.enqueue(new MockResponse().setBody("A"));
314    server.play();
315
316    final CountDownLatch latch = new CountDownLatch(1);
317    final AtomicReference<String> bodyRef = new AtomicReference<String>();
318
319    Request request = new Request.Builder().url(server.getUrl("/a")).tag("request A").build();
320    client.enqueue(request, new Response.Receiver() {
321      @Override public void onFailure(Failure failure) {
322        throw new AssertionError();
323      }
324
325      @Override public boolean onResponse(Response response) throws IOException {
326        client.cancel("request A");
327        bodyRef.set(response.body().string());
328        latch.countDown();
329        return true;
330      }
331    });
332
333    latch.await();
334    assertEquals("A", bodyRef.get());
335  }
336
337  @Test public void connectionReuseWhenResponseBodyConsumed() throws Exception {
338    server.enqueue(new MockResponse().setBody("abc"));
339    server.enqueue(new MockResponse().setBody("def"));
340    server.play();
341
342    Request request = new Request.Builder().url(server.getUrl("/a")).build();
343    client.enqueue(request, new Response.Receiver() {
344      @Override public void onFailure(Failure failure) {
345        throw new AssertionError();
346      }
347      @Override public boolean onResponse(Response response) throws IOException {
348        InputStream bytes = response.body().byteStream();
349        assertEquals('a', bytes.read());
350        assertEquals('b', bytes.read());
351        assertEquals('c', bytes.read());
352
353        // This request will share a connection with 'A' cause it's all done.
354        client.enqueue(new Request.Builder().url(server.getUrl("/b")).build(), receiver);
355        return true;
356      }
357    });
358
359    receiver.await(server.getUrl("/b")).assertCode(200).assertBody("def");
360    assertEquals(0, server.takeRequest().getSequenceNumber()); // New connection.
361    assertEquals(1, server.takeRequest().getSequenceNumber()); // Connection reuse!
362  }
363
364  @Test public void postBodyRetransmittedOnRedirect() throws Exception {
365    server.enqueue(new MockResponse()
366        .setResponseCode(302)
367        .addHeader("Location: /b")
368        .setBody("Moved to /b !"));
369    server.enqueue(new MockResponse()
370        .setBody("This is b."));
371    server.play();
372
373    Request request = new Request.Builder()
374        .url(server.getUrl("/"))
375        .post(Request.Body.create(MediaType.parse("text/plain"), "body!"))
376        .build();
377    client.enqueue(request, receiver);
378
379    receiver.await(server.getUrl("/b"))
380        .assertCode(200)
381        .assertBody("This is b.");
382
383    RecordedRequest request1 = server.takeRequest();
384    assertEquals("body!", request1.getUtf8Body());
385    assertEquals("5", request1.getHeader("Content-Length"));
386    assertEquals("text/plain; charset=utf-8", request1.getHeader("Content-Type"));
387    assertEquals(0, request1.getSequenceNumber());
388
389    RecordedRequest request2 = server.takeRequest();
390    assertEquals("body!", request2.getUtf8Body());
391    assertEquals("5", request2.getHeader("Content-Length"));
392    assertEquals("text/plain; charset=utf-8", request2.getHeader("Content-Type"));
393    assertEquals(1, request2.getSequenceNumber());
394  }
395}
396