Home | History | Annotate | Download | only in okhttp
      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  */
     16 package com.squareup.okhttp;
     17 
     18 import com.squareup.okhttp.internal.RecordingHostnameVerifier;
     19 import com.squareup.okhttp.internal.SslContextBuilder;
     20 import com.squareup.okhttp.mockwebserver.Dispatcher;
     21 import com.squareup.okhttp.mockwebserver.MockResponse;
     22 import com.squareup.okhttp.mockwebserver.MockWebServer;
     23 import com.squareup.okhttp.mockwebserver.RecordedRequest;
     24 import com.squareup.okhttp.mockwebserver.SocketPolicy;
     25 import java.io.File;
     26 import java.io.IOException;
     27 import java.io.InputStream;
     28 import java.net.HttpURLConnection;
     29 import java.util.UUID;
     30 import java.util.concurrent.CountDownLatch;
     31 import java.util.concurrent.atomic.AtomicReference;
     32 import javax.net.ssl.SSLContext;
     33 import org.junit.After;
     34 import org.junit.Before;
     35 import org.junit.Test;
     36 
     37 import static org.junit.Assert.assertEquals;
     38 import static org.junit.Assert.assertNull;
     39 import static org.junit.Assert.assertTrue;
     40 
     41 public 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