Home | History | Annotate | Download | only in framed
      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.internal.framed;
     17 
     18 import com.squareup.okhttp.Cache;
     19 import com.squareup.okhttp.ConnectionPool;
     20 import com.squareup.okhttp.HttpUrl;
     21 import com.squareup.okhttp.OkHttpClient;
     22 import com.squareup.okhttp.OkUrlFactory;
     23 import com.squareup.okhttp.Protocol;
     24 import com.squareup.okhttp.internal.RecordingAuthenticator;
     25 import com.squareup.okhttp.internal.SslContextBuilder;
     26 import com.squareup.okhttp.internal.Util;
     27 import com.squareup.okhttp.mockwebserver.MockResponse;
     28 import com.squareup.okhttp.mockwebserver.MockWebServer;
     29 import com.squareup.okhttp.mockwebserver.RecordedRequest;
     30 import com.squareup.okhttp.mockwebserver.SocketPolicy;
     31 import com.squareup.okhttp.testing.RecordingHostnameVerifier;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.net.Authenticator;
     35 import java.net.CookieManager;
     36 import java.net.HttpURLConnection;
     37 import java.net.SocketTimeoutException;
     38 import java.util.Arrays;
     39 import java.util.Collections;
     40 import java.util.List;
     41 import java.util.Map;
     42 import java.util.concurrent.CountDownLatch;
     43 import java.util.concurrent.ExecutorService;
     44 import java.util.concurrent.Executors;
     45 import java.util.concurrent.TimeUnit;
     46 import javax.net.ssl.HostnameVerifier;
     47 import javax.net.ssl.SSLContext;
     48 import okio.Buffer;
     49 import okio.BufferedSink;
     50 import okio.GzipSink;
     51 import okio.Okio;
     52 import org.junit.After;
     53 import org.junit.Before;
     54 import org.junit.Ignore;
     55 import org.junit.Rule;
     56 import org.junit.Test;
     57 import org.junit.rules.TemporaryFolder;
     58 
     59 import static java.util.concurrent.TimeUnit.SECONDS;
     60 import static org.junit.Assert.assertArrayEquals;
     61 import static org.junit.Assert.assertEquals;
     62 import static org.junit.Assert.assertNull;
     63 import static org.junit.Assert.fail;
     64 
     65 /** Test how SPDY interacts with HTTP features. */
     66 public abstract class HttpOverSpdyTest {
     67   @Rule public final TemporaryFolder tempDir = new TemporaryFolder();
     68   @Rule public final MockWebServer server = new MockWebServer();
     69 
     70   /** Protocol to test, for example {@link com.squareup.okhttp.Protocol#SPDY_3} */
     71   private final Protocol protocol;
     72   protected String hostHeader = ":host";
     73 
     74   protected SSLContext sslContext = SslContextBuilder.localhost();
     75   protected HostnameVerifier hostnameVerifier = new RecordingHostnameVerifier();
     76   protected final OkUrlFactory client = new OkUrlFactory(new OkHttpClient());
     77   protected HttpURLConnection connection;
     78   protected Cache cache;
     79 
     80   protected HttpOverSpdyTest(Protocol protocol){
     81     this.protocol = protocol;
     82   }
     83 
     84   @Before public void setUp() throws Exception {
     85     server.useHttps(sslContext.getSocketFactory(), false);
     86     client.client().setProtocols(Arrays.asList(protocol, Protocol.HTTP_1_1));
     87     client.client().setSslSocketFactory(sslContext.getSocketFactory());
     88     client.client().setHostnameVerifier(hostnameVerifier);
     89     cache = new Cache(tempDir.getRoot(), Integer.MAX_VALUE);
     90   }
     91 
     92   @After public void tearDown() throws Exception {
     93     Authenticator.setDefault(null);
     94   }
     95 
     96   @Test public void get() throws Exception {
     97     MockResponse response = new MockResponse().setBody("ABCDE").setStatus("HTTP/1.1 200 Sweet");
     98     server.enqueue(response);
     99 
    100     connection = client.open(server.getUrl("/foo"));
    101     assertContent("ABCDE", connection, Integer.MAX_VALUE);
    102     assertEquals(200, connection.getResponseCode());
    103     assertEquals("Sweet", connection.getResponseMessage());
    104 
    105     RecordedRequest request = server.takeRequest();
    106     assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
    107     assertEquals("https", request.getHeader(":scheme"));
    108     assertEquals(server.getHostName() + ":" + server.getPort(), request.getHeader(hostHeader));
    109   }
    110 
    111   @Test public void emptyResponse() throws IOException {
    112     server.enqueue(new MockResponse());
    113 
    114     connection = client.open(server.getUrl("/foo"));
    115     assertEquals(-1, connection.getInputStream().read());
    116   }
    117 
    118   byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8);
    119 
    120   @Test public void noDefaultContentLengthOnStreamingPost() throws Exception {
    121     server.enqueue(new MockResponse().setBody("ABCDE"));
    122 
    123     connection = client.open(server.getUrl("/foo"));
    124     connection.setDoOutput(true);
    125     connection.setChunkedStreamingMode(0);
    126     connection.getOutputStream().write(postBytes);
    127     assertContent("ABCDE", connection, Integer.MAX_VALUE);
    128 
    129     RecordedRequest request = server.takeRequest();
    130     assertEquals("POST /foo HTTP/1.1", request.getRequestLine());
    131     assertArrayEquals(postBytes, request.getBody().readByteArray());
    132     assertNull(request.getHeader("Content-Length"));
    133   }
    134 
    135   @Test public void userSuppliedContentLengthHeader() throws Exception {
    136     server.enqueue(new MockResponse().setBody("ABCDE"));
    137 
    138     connection = client.open(server.getUrl("/foo"));
    139     connection.setRequestProperty("Content-Length", String.valueOf(postBytes.length));
    140     connection.setDoOutput(true);
    141     connection.getOutputStream().write(postBytes);
    142     assertContent("ABCDE", connection, Integer.MAX_VALUE);
    143 
    144     RecordedRequest request = server.takeRequest();
    145     assertEquals("POST /foo HTTP/1.1", request.getRequestLine());
    146     assertArrayEquals(postBytes, request.getBody().readByteArray());
    147     assertEquals(postBytes.length, Integer.parseInt(request.getHeader("Content-Length")));
    148   }
    149 
    150   @Test public void closeAfterFlush() throws Exception {
    151     server.enqueue(new MockResponse().setBody("ABCDE"));
    152 
    153     connection = client.open(server.getUrl("/foo"));
    154     connection.setRequestProperty("Content-Length", String.valueOf(postBytes.length));
    155     connection.setDoOutput(true);
    156     connection.getOutputStream().write(postBytes); // push bytes into SpdyDataOutputStream.buffer
    157     connection.getOutputStream().flush(); // FramedConnection.writeData subject to write window
    158     connection.getOutputStream().close(); // FramedConnection.writeData empty frame
    159     assertContent("ABCDE", connection, Integer.MAX_VALUE);
    160 
    161     RecordedRequest request = server.takeRequest();
    162     assertEquals("POST /foo HTTP/1.1", request.getRequestLine());
    163     assertArrayEquals(postBytes, request.getBody().readByteArray());
    164     assertEquals(postBytes.length, Integer.parseInt(request.getHeader("Content-Length")));
    165   }
    166 
    167   @Test public void setFixedLengthStreamingModeSetsContentLength() throws Exception {
    168     server.enqueue(new MockResponse().setBody("ABCDE"));
    169 
    170     connection = client.open(server.getUrl("/foo"));
    171     connection.setFixedLengthStreamingMode(postBytes.length);
    172     connection.setDoOutput(true);
    173     connection.getOutputStream().write(postBytes);
    174     assertContent("ABCDE", connection, Integer.MAX_VALUE);
    175 
    176     RecordedRequest request = server.takeRequest();
    177     assertEquals("POST /foo HTTP/1.1", request.getRequestLine());
    178     assertArrayEquals(postBytes, request.getBody().readByteArray());
    179     assertEquals(postBytes.length, Integer.parseInt(request.getHeader("Content-Length")));
    180   }
    181 
    182   @Test public void spdyConnectionReuse() throws Exception {
    183     server.enqueue(new MockResponse().setBody("ABCDEF"));
    184     server.enqueue(new MockResponse().setBody("GHIJKL"));
    185 
    186     HttpURLConnection connection1 = client.open(server.getUrl("/r1"));
    187     HttpURLConnection connection2 = client.open(server.getUrl("/r2"));
    188     assertEquals("ABC", readAscii(connection1.getInputStream(), 3));
    189     assertEquals("GHI", readAscii(connection2.getInputStream(), 3));
    190     assertEquals("DEF", readAscii(connection1.getInputStream(), 3));
    191     assertEquals("JKL", readAscii(connection2.getInputStream(), 3));
    192     assertEquals(0, server.takeRequest().getSequenceNumber());
    193     assertEquals(1, server.takeRequest().getSequenceNumber());
    194   }
    195 
    196   @Test @Ignore public void synchronousSpdyRequest() throws Exception {
    197     server.enqueue(new MockResponse().setBody("A"));
    198     server.enqueue(new MockResponse().setBody("A"));
    199 
    200     ExecutorService executor = Executors.newCachedThreadPool();
    201     CountDownLatch countDownLatch = new CountDownLatch(2);
    202     executor.execute(new SpdyRequest("/r1", countDownLatch));
    203     executor.execute(new SpdyRequest("/r2", countDownLatch));
    204     countDownLatch.await();
    205     assertEquals(0, server.takeRequest().getSequenceNumber());
    206     assertEquals(1, server.takeRequest().getSequenceNumber());
    207   }
    208 
    209   @Test public void gzippedResponseBody() throws Exception {
    210     server.enqueue(
    211         new MockResponse().addHeader("Content-Encoding: gzip").setBody(gzip("ABCABCABC")));
    212     assertContent("ABCABCABC", client.open(server.getUrl("/r1")), Integer.MAX_VALUE);
    213   }
    214 
    215   @Test public void authenticate() throws Exception {
    216     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_UNAUTHORIZED)
    217         .addHeader("www-authenticate: Basic realm=\"protected area\"")
    218         .setBody("Please authenticate."));
    219     server.enqueue(new MockResponse().setBody("Successful auth!"));
    220 
    221     Authenticator.setDefault(new RecordingAuthenticator());
    222     connection = client.open(server.getUrl("/"));
    223     assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
    224 
    225     RecordedRequest denied = server.takeRequest();
    226     assertNull(denied.getHeader("Authorization"));
    227     RecordedRequest accepted = server.takeRequest();
    228     assertEquals("GET / HTTP/1.1", accepted.getRequestLine());
    229     assertEquals("Basic " + RecordingAuthenticator.BASE_64_CREDENTIALS,
    230         accepted.getHeader("Authorization"));
    231   }
    232 
    233   @Test public void redirect() throws Exception {
    234     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
    235         .addHeader("Location: /foo")
    236         .setBody("This page has moved!"));
    237     server.enqueue(new MockResponse().setBody("This is the new location!"));
    238 
    239     connection = client.open(server.getUrl("/"));
    240     assertContent("This is the new location!", connection, Integer.MAX_VALUE);
    241 
    242     RecordedRequest request1 = server.takeRequest();
    243     assertEquals("/", request1.getPath());
    244     RecordedRequest request2 = server.takeRequest();
    245     assertEquals("/foo", request2.getPath());
    246   }
    247 
    248   @Test public void readAfterLastByte() throws Exception {
    249     server.enqueue(new MockResponse().setBody("ABC"));
    250 
    251     connection = client.open(server.getUrl("/"));
    252     InputStream in = connection.getInputStream();
    253     assertEquals("ABC", readAscii(in, 3));
    254     assertEquals(-1, in.read());
    255     assertEquals(-1, in.read());
    256   }
    257 
    258   @Ignore // See https://github.com/square/okhttp/issues/578
    259   @Test(timeout = 3000) public void readResponseHeaderTimeout() throws Exception {
    260     server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
    261     server.enqueue(new MockResponse().setBody("A"));
    262 
    263     connection = client.open(server.getUrl("/"));
    264     connection.setReadTimeout(1000);
    265     assertContent("A", connection, Integer.MAX_VALUE);
    266   }
    267 
    268   /**
    269    * Test to ensure we don't  throw a read timeout on responses that are
    270    * progressing.  For this case, we take a 4KiB body and throttle it to
    271    * 1KiB/second.  We set the read timeout to two seconds.  If our
    272    * implementation is acting correctly, it will not throw, as it is
    273    * progressing.
    274    */
    275   @Test public void readTimeoutMoreGranularThanBodySize() throws Exception {
    276     char[] body = new char[4096]; // 4KiB to read
    277     Arrays.fill(body, 'y');
    278     server.enqueue(new MockResponse().setBody(new String(body)).throttleBody(1024, 1, SECONDS)); // slow connection 1KiB/second
    279 
    280     connection = client.open(server.getUrl("/"));
    281     connection.setReadTimeout(2000); // 2 seconds to read something.
    282     assertContent(new String(body), connection, Integer.MAX_VALUE);
    283   }
    284 
    285   /**
    286    * Test to ensure we throw a read timeout on responses that are progressing
    287    * too slowly.  For this case, we take a 2KiB body and throttle it to
    288    * 1KiB/second.  We set the read timeout to half a second.  If our
    289    * implementation is acting correctly, it will throw, as a byte doesn't
    290    * arrive in time.
    291    */
    292   @Test public void readTimeoutOnSlowConnection() throws Exception {
    293     char[] body = new char[2048]; // 2KiB to read
    294     Arrays.fill(body, 'y');
    295     server.enqueue(new MockResponse()
    296         .setBody(new String(body))
    297         .throttleBody(1024, 1, SECONDS)); // slow connection 1KiB/second
    298 
    299     connection = client.open(server.getUrl("/"));
    300     connection.setReadTimeout(500); // half a second to read something
    301     connection.connect();
    302     try {
    303       readAscii(connection.getInputStream(), Integer.MAX_VALUE);
    304       fail("Should have timed out!");
    305     } catch (SocketTimeoutException expected) {
    306       assertEquals("timeout", expected.getMessage());
    307     }
    308   }
    309 
    310   @Test public void spdyConnectionTimeout() throws Exception {
    311     MockResponse response = new MockResponse().setBody("A");
    312     response.setBodyDelay(1, TimeUnit.SECONDS);
    313     server.enqueue(response);
    314 
    315     HttpURLConnection connection1 = client.open(server.getUrl("/"));
    316     connection1.setReadTimeout(2000);
    317     HttpURLConnection connection2 = client.open(server.getUrl("/"));
    318     connection2.setReadTimeout(200);
    319     connection1.connect();
    320     connection2.connect();
    321     assertContent("A", connection1, Integer.MAX_VALUE);
    322   }
    323 
    324   @Test public void responsesAreCached() throws IOException {
    325     client.client().setCache(cache);
    326 
    327     server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("A"));
    328 
    329     assertContent("A", client.open(server.getUrl("/")), Integer.MAX_VALUE);
    330     assertEquals(1, cache.getRequestCount());
    331     assertEquals(1, cache.getNetworkCount());
    332     assertEquals(0, cache.getHitCount());
    333     assertContent("A", client.open(server.getUrl("/")), Integer.MAX_VALUE);
    334     assertContent("A", client.open(server.getUrl("/")), Integer.MAX_VALUE);
    335     assertEquals(3, cache.getRequestCount());
    336     assertEquals(1, cache.getNetworkCount());
    337     assertEquals(2, cache.getHitCount());
    338   }
    339 
    340   @Test public void conditionalCache() throws IOException {
    341     client.client().setCache(cache);
    342 
    343     server.enqueue(new MockResponse().addHeader("ETag: v1").setBody("A"));
    344     server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
    345 
    346     assertContent("A", client.open(server.getUrl("/")), Integer.MAX_VALUE);
    347     assertEquals(1, cache.getRequestCount());
    348     assertEquals(1, cache.getNetworkCount());
    349     assertEquals(0, cache.getHitCount());
    350     assertContent("A", client.open(server.getUrl("/")), Integer.MAX_VALUE);
    351     assertEquals(2, cache.getRequestCount());
    352     assertEquals(2, cache.getNetworkCount());
    353     assertEquals(1, cache.getHitCount());
    354   }
    355 
    356   @Test public void responseCachedWithoutConsumingFullBody() throws IOException {
    357     client.client().setCache(cache);
    358 
    359     server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("ABCD"));
    360     server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("EFGH"));
    361 
    362     HttpURLConnection connection1 = client.open(server.getUrl("/"));
    363     InputStream in1 = connection1.getInputStream();
    364     assertEquals("AB", readAscii(in1, 2));
    365     in1.close();
    366 
    367     HttpURLConnection connection2 = client.open(server.getUrl("/"));
    368     InputStream in2 = connection2.getInputStream();
    369     assertEquals("ABCD", readAscii(in2, Integer.MAX_VALUE));
    370     in2.close();
    371   }
    372 
    373   @Test public void acceptAndTransmitCookies() throws Exception {
    374     CookieManager cookieManager = new CookieManager();
    375     client.client().setCookieHandler(cookieManager);
    376 
    377     server.enqueue(new MockResponse()
    378         .addHeader("set-cookie: c=oreo; domain=" + server.getCookieDomain())
    379         .setBody("A"));
    380     server.enqueue(new MockResponse()
    381         .setBody("B"));
    382 
    383     HttpUrl url = server.url("/");
    384     assertContent("A", client.open(url.url()), Integer.MAX_VALUE);
    385     Map<String, List<String>> requestHeaders = Collections.emptyMap();
    386     assertEquals(Collections.singletonMap("Cookie", Arrays.asList("c=oreo")),
    387         cookieManager.get(url.uri(), requestHeaders));
    388 
    389     assertContent("B", client.open(url.url()), Integer.MAX_VALUE);
    390     RecordedRequest requestA = server.takeRequest();
    391     assertNull(requestA.getHeader("Cookie"));
    392     RecordedRequest requestB = server.takeRequest();
    393     assertEquals("c=oreo", requestB.getHeader("Cookie"));
    394   }
    395 
    396   /** https://github.com/square/okhttp/issues/1191 */
    397   @Test public void disconnectWithStreamNotEstablished() throws Exception {
    398     ConnectionPool connectionPool = new ConnectionPool(5, 5000);
    399     client.client().setConnectionPool(connectionPool);
    400 
    401     server.enqueue(new MockResponse().setBody("abc"));
    402 
    403     // Disconnect before the stream is created. A connection is still established!
    404     HttpURLConnection connection1 = client.open(server.getUrl("/"));
    405     connection1.connect();
    406     connection1.disconnect();
    407 
    408     // That connection is pooled, and it works.
    409     assertEquals(1, connectionPool.getSpdyConnectionCount());
    410     HttpURLConnection connection2 = client.open(server.getUrl("/"));
    411     assertContent("abc", connection2, 3);
    412     assertEquals(0, server.takeRequest().getSequenceNumber());
    413   }
    414 
    415   void assertContent(String expected, HttpURLConnection connection, int limit)
    416       throws IOException {
    417     connection.connect();
    418     assertEquals(expected, readAscii(connection.getInputStream(), limit));
    419   }
    420 
    421   private String readAscii(InputStream in, int count) throws IOException {
    422     StringBuilder result = new StringBuilder();
    423     for (int i = 0; i < count; i++) {
    424       int value = in.read();
    425       if (value == -1) {
    426         in.close();
    427         break;
    428       }
    429       result.append((char) value);
    430     }
    431     return result.toString();
    432   }
    433 
    434   public Buffer gzip(String bytes) throws IOException {
    435     Buffer bytesOut = new Buffer();
    436     BufferedSink sink = Okio.buffer(new GzipSink(bytesOut));
    437     sink.writeUtf8(bytes);
    438     sink.close();
    439     return bytesOut;
    440   }
    441 
    442   class SpdyRequest implements Runnable {
    443     String path;
    444     CountDownLatch countDownLatch;
    445     public SpdyRequest(String path, CountDownLatch countDownLatch) {
    446       this.path = path;
    447       this.countDownLatch = countDownLatch;
    448     }
    449 
    450     @Override public void run() {
    451       try {
    452         HttpURLConnection conn = client.open(server.getUrl(path));
    453         assertEquals("A", readAscii(conn.getInputStream(), 1));
    454         countDownLatch.countDown();
    455       } catch (Exception e) {
    456         throw new RuntimeException(e);
    457       }
    458     }
    459   }
    460 }
    461