• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HttpOverSpdyTest(Protocol protocol)80   protected HttpOverSpdyTest(Protocol protocol){
81     this.protocol = protocol;
82   }
83 
setUp()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 
tearDown()92   @After public void tearDown() throws Exception {
93     Authenticator.setDefault(null);
94   }
95 
get()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 
emptyResponse()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 
noDefaultContentLengthOnStreamingPost()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 
userSuppliedContentLengthHeader()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 
closeAfterFlush()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 
setFixedLengthStreamingModeSetsContentLength()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 
spdyConnectionReuse()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 
synchronousSpdyRequest()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 
gzippedResponseBody()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 
authenticate()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 
redirect()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 
readAfterLastByte()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
readResponseHeaderTimeout()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    */
readTimeoutMoreGranularThanBodySize()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    */
readTimeoutOnSlowConnection()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 
spdyConnectionTimeout()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 
responsesAreCached()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 
conditionalCache()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 
responseCachedWithoutConsumingFullBody()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 
acceptAndTransmitCookies()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 */
disconnectWithStreamNotEstablished()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 
assertContent(String expected, HttpURLConnection connection, int limit)415   void assertContent(String expected, HttpURLConnection connection, int limit)
416       throws IOException {
417     connection.connect();
418     assertEquals(expected, readAscii(connection.getInputStream(), limit));
419   }
420 
readAscii(InputStream in, int count)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 
gzip(String bytes)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;
SpdyRequest(String path, CountDownLatch countDownLatch)445     public SpdyRequest(String path, CountDownLatch countDownLatch) {
446       this.path = path;
447       this.countDownLatch = countDownLatch;
448     }
449 
run()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