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