• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.mockwebserver.MockResponse;
19 import com.squareup.okhttp.mockwebserver.MockWebServer;
20 import com.squareup.okhttp.mockwebserver.RecordedRequest;
21 import java.io.IOException;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Locale;
25 import java.util.concurrent.BlockingQueue;
26 import java.util.concurrent.LinkedBlockingQueue;
27 import java.util.concurrent.SynchronousQueue;
28 import java.util.concurrent.ThreadPoolExecutor;
29 import java.util.concurrent.TimeUnit;
30 import okio.Buffer;
31 import okio.BufferedSink;
32 import okio.ForwardingSink;
33 import okio.ForwardingSource;
34 import okio.GzipSink;
35 import okio.Okio;
36 import okio.Sink;
37 import okio.Source;
38 import org.junit.Rule;
39 import org.junit.Test;
40 
41 import static org.junit.Assert.assertEquals;
42 import static org.junit.Assert.assertNotNull;
43 import static org.junit.Assert.assertNull;
44 import static org.junit.Assert.assertSame;
45 import static org.junit.Assert.fail;
46 
47 public final class InterceptorTest {
48   @Rule public MockWebServer server = new MockWebServer();
49 
50   private OkHttpClient client = new OkHttpClient();
51   private RecordingCallback callback = new RecordingCallback();
52 
applicationInterceptorsCanShortCircuitResponses()53   @Test public void applicationInterceptorsCanShortCircuitResponses() throws Exception {
54     server.shutdown(); // Accept no connections.
55 
56     Request request = new Request.Builder()
57         .url("https://localhost:1/")
58         .build();
59 
60     final Response interceptorResponse = new Response.Builder()
61         .request(request)
62         .protocol(Protocol.HTTP_1_1)
63         .code(200)
64         .message("Intercepted!")
65         .body(ResponseBody.create(MediaType.parse("text/plain; charset=utf-8"), "abc"))
66         .build();
67 
68     client.interceptors().add(new Interceptor() {
69       @Override public Response intercept(Chain chain) throws IOException {
70         return interceptorResponse;
71       }
72     });
73 
74     Response response = client.newCall(request).execute();
75     assertSame(interceptorResponse, response);
76   }
77 
networkInterceptorsCannotShortCircuitResponses()78   @Test public void networkInterceptorsCannotShortCircuitResponses() throws Exception {
79     server.enqueue(new MockResponse().setResponseCode(500));
80 
81     Interceptor interceptor = new Interceptor() {
82       @Override public Response intercept(Chain chain) throws IOException {
83         return new Response.Builder()
84             .request(chain.request())
85             .protocol(Protocol.HTTP_1_1)
86             .code(200)
87             .message("Intercepted!")
88             .body(ResponseBody.create(MediaType.parse("text/plain; charset=utf-8"), "abc"))
89             .build();
90       }
91     };
92     client.networkInterceptors().add(interceptor);
93 
94     Request request = new Request.Builder()
95         .url(server.url("/"))
96         .build();
97 
98     try {
99       client.newCall(request).execute();
100       fail();
101     } catch (IllegalStateException expected) {
102       assertEquals("network interceptor " + interceptor + " must call proceed() exactly once",
103           expected.getMessage());
104     }
105   }
106 
networkInterceptorsCannotCallProceedMultipleTimes()107   @Test public void networkInterceptorsCannotCallProceedMultipleTimes() throws Exception {
108     server.enqueue(new MockResponse());
109     server.enqueue(new MockResponse());
110 
111     Interceptor interceptor = new Interceptor() {
112       @Override public Response intercept(Chain chain) throws IOException {
113         chain.proceed(chain.request());
114         return chain.proceed(chain.request());
115       }
116     };
117     client.networkInterceptors().add(interceptor);
118 
119     Request request = new Request.Builder()
120         .url(server.url("/"))
121         .build();
122 
123     try {
124       client.newCall(request).execute();
125       fail();
126     } catch (IllegalStateException expected) {
127       assertEquals("network interceptor " + interceptor + " must call proceed() exactly once",
128           expected.getMessage());
129     }
130   }
131 
networkInterceptorsCannotChangeServerAddress()132   @Test public void networkInterceptorsCannotChangeServerAddress() throws Exception {
133     server.enqueue(new MockResponse().setResponseCode(500));
134 
135     Interceptor interceptor = new Interceptor() {
136       @Override public Response intercept(Chain chain) throws IOException {
137         Address address = chain.connection().getRoute().getAddress();
138         String sameHost = address.getRfc2732Host();
139         int differentPort = address.getUriPort() + 1;
140         return chain.proceed(chain.request().newBuilder()
141             .url(HttpUrl.parse("http://" + sameHost + ":" + differentPort + "/"))
142             .build());
143       }
144     };
145     client.networkInterceptors().add(interceptor);
146 
147     Request request = new Request.Builder()
148         .url(server.url("/"))
149         .build();
150 
151     try {
152       client.newCall(request).execute();
153       fail();
154     } catch (IllegalStateException expected) {
155       assertEquals("network interceptor " + interceptor + " must retain the same host and port",
156           expected.getMessage());
157     }
158   }
159 
networkInterceptorsHaveConnectionAccess()160   @Test public void networkInterceptorsHaveConnectionAccess() throws Exception {
161     server.enqueue(new MockResponse());
162 
163     client.networkInterceptors().add(new Interceptor() {
164       @Override public Response intercept(Chain chain) throws IOException {
165         Connection connection = chain.connection();
166         assertNotNull(connection);
167         return chain.proceed(chain.request());
168       }
169     });
170 
171     Request request = new Request.Builder()
172         .url(server.url("/"))
173         .build();
174     client.newCall(request).execute();
175   }
176 
networkInterceptorsObserveNetworkHeaders()177   @Test public void networkInterceptorsObserveNetworkHeaders() throws Exception {
178     server.enqueue(new MockResponse()
179         .setBody(gzip("abcabcabc"))
180         .addHeader("Content-Encoding: gzip"));
181 
182     client.networkInterceptors().add(new Interceptor() {
183       @Override public Response intercept(Chain chain) throws IOException {
184         // The network request has everything: User-Agent, Host, Accept-Encoding.
185         Request networkRequest = chain.request();
186         assertNotNull(networkRequest.header("User-Agent"));
187         assertEquals(server.getHostName() + ":" + server.getPort(),
188             networkRequest.header("Host"));
189         assertNotNull(networkRequest.header("Accept-Encoding"));
190 
191         // The network response also has everything, including the raw gzipped content.
192         Response networkResponse = chain.proceed(networkRequest);
193         assertEquals("gzip", networkResponse.header("Content-Encoding"));
194         return networkResponse;
195       }
196     });
197 
198     Request request = new Request.Builder()
199         .url(server.url("/"))
200         .build();
201 
202     // No extra headers in the application's request.
203     assertNull(request.header("User-Agent"));
204     assertNull(request.header("Host"));
205     assertNull(request.header("Accept-Encoding"));
206 
207     // No extra headers in the application's response.
208     Response response = client.newCall(request).execute();
209     assertNull(request.header("Content-Encoding"));
210     assertEquals("abcabcabc", response.body().string());
211   }
212 
applicationInterceptorsRewriteRequestToServer()213   @Test public void applicationInterceptorsRewriteRequestToServer() throws Exception {
214     rewriteRequestToServer(client.interceptors());
215   }
216 
networkInterceptorsRewriteRequestToServer()217   @Test public void networkInterceptorsRewriteRequestToServer() throws Exception {
218     rewriteRequestToServer(client.networkInterceptors());
219   }
220 
rewriteRequestToServer(List<Interceptor> interceptors)221   private void rewriteRequestToServer(List<Interceptor> interceptors) throws Exception {
222     server.enqueue(new MockResponse());
223 
224     interceptors.add(new Interceptor() {
225       @Override public Response intercept(Chain chain) throws IOException {
226         Request originalRequest = chain.request();
227         return chain.proceed(originalRequest.newBuilder()
228             .method("POST", uppercase(originalRequest.body()))
229             .addHeader("OkHttp-Intercepted", "yep")
230             .build());
231       }
232     });
233 
234     Request request = new Request.Builder()
235         .url(server.url("/"))
236         .addHeader("Original-Header", "foo")
237         .method("PUT", RequestBody.create(MediaType.parse("text/plain"), "abc"))
238         .build();
239 
240     client.newCall(request).execute();
241 
242     RecordedRequest recordedRequest = server.takeRequest();
243     assertEquals("ABC", recordedRequest.getBody().readUtf8());
244     assertEquals("foo", recordedRequest.getHeader("Original-Header"));
245     assertEquals("yep", recordedRequest.getHeader("OkHttp-Intercepted"));
246     assertEquals("POST", recordedRequest.getMethod());
247   }
248 
applicationInterceptorsRewriteResponseFromServer()249   @Test public void applicationInterceptorsRewriteResponseFromServer() throws Exception {
250     rewriteResponseFromServer(client.interceptors());
251   }
252 
networkInterceptorsRewriteResponseFromServer()253   @Test public void networkInterceptorsRewriteResponseFromServer() throws Exception {
254     rewriteResponseFromServer(client.networkInterceptors());
255   }
256 
rewriteResponseFromServer(List<Interceptor> interceptors)257   private void rewriteResponseFromServer(List<Interceptor> interceptors) throws Exception {
258     server.enqueue(new MockResponse()
259         .addHeader("Original-Header: foo")
260         .setBody("abc"));
261 
262     interceptors.add(new Interceptor() {
263       @Override public Response intercept(Chain chain) throws IOException {
264         Response originalResponse = chain.proceed(chain.request());
265         return originalResponse.newBuilder()
266             .body(uppercase(originalResponse.body()))
267             .addHeader("OkHttp-Intercepted", "yep")
268             .build();
269       }
270     });
271 
272     Request request = new Request.Builder()
273         .url(server.url("/"))
274         .build();
275 
276     Response response = client.newCall(request).execute();
277     assertEquals("ABC", response.body().string());
278     assertEquals("yep", response.header("OkHttp-Intercepted"));
279     assertEquals("foo", response.header("Original-Header"));
280   }
281 
multipleApplicationInterceptors()282   @Test public void multipleApplicationInterceptors() throws Exception {
283     multipleInterceptors(client.interceptors());
284   }
285 
multipleNetworkInterceptors()286   @Test public void multipleNetworkInterceptors() throws Exception {
287     multipleInterceptors(client.networkInterceptors());
288   }
289 
multipleInterceptors(List<Interceptor> interceptors)290   private void multipleInterceptors(List<Interceptor> interceptors) throws Exception {
291     server.enqueue(new MockResponse());
292 
293     interceptors.add(new Interceptor() {
294       @Override public Response intercept(Chain chain) throws IOException {
295         Request originalRequest = chain.request();
296         Response originalResponse = chain.proceed(originalRequest.newBuilder()
297             .addHeader("Request-Interceptor", "Android") // 1. Added first.
298             .build());
299         return originalResponse.newBuilder()
300             .addHeader("Response-Interceptor", "Donut") // 4. Added last.
301             .build();
302       }
303     });
304     interceptors.add(new Interceptor() {
305       @Override public Response intercept(Chain chain) throws IOException {
306         Request originalRequest = chain.request();
307         Response originalResponse = chain.proceed(originalRequest.newBuilder()
308             .addHeader("Request-Interceptor", "Bob") // 2. Added second.
309             .build());
310         return originalResponse.newBuilder()
311             .addHeader("Response-Interceptor", "Cupcake") // 3. Added third.
312             .build();
313       }
314     });
315 
316     Request request = new Request.Builder()
317         .url(server.url("/"))
318         .build();
319 
320     Response response = client.newCall(request).execute();
321     assertEquals(Arrays.asList("Cupcake", "Donut"),
322         response.headers("Response-Interceptor"));
323 
324     RecordedRequest recordedRequest = server.takeRequest();
325     assertEquals(Arrays.asList("Android", "Bob"),
326         recordedRequest.getHeaders().values("Request-Interceptor"));
327   }
328 
asyncApplicationInterceptors()329   @Test public void asyncApplicationInterceptors() throws Exception {
330     asyncInterceptors(client.interceptors());
331   }
332 
asyncNetworkInterceptors()333   @Test public void asyncNetworkInterceptors() throws Exception {
334     asyncInterceptors(client.networkInterceptors());
335   }
336 
asyncInterceptors(List<Interceptor> interceptors)337   private void asyncInterceptors(List<Interceptor> interceptors) throws Exception {
338     server.enqueue(new MockResponse());
339 
340     interceptors.add(new Interceptor() {
341       @Override public Response intercept(Chain chain) throws IOException {
342         Response originalResponse = chain.proceed(chain.request());
343         return originalResponse.newBuilder()
344             .addHeader("OkHttp-Intercepted", "yep")
345             .build();
346       }
347     });
348 
349     Request request = new Request.Builder()
350         .url(server.url("/"))
351         .build();
352     client.newCall(request).enqueue(callback);
353 
354     callback.await(request.httpUrl())
355         .assertCode(200)
356         .assertHeader("OkHttp-Intercepted", "yep");
357   }
358 
applicationInterceptorsCanMakeMultipleRequestsToServer()359   @Test public void applicationInterceptorsCanMakeMultipleRequestsToServer() throws Exception {
360     server.enqueue(new MockResponse().setBody("a"));
361     server.enqueue(new MockResponse().setBody("b"));
362 
363     client.interceptors().add(new Interceptor() {
364       @Override public Response intercept(Chain chain) throws IOException {
365         chain.proceed(chain.request());
366         return chain.proceed(chain.request());
367       }
368     });
369 
370     Request request = new Request.Builder()
371         .url(server.url("/"))
372         .build();
373 
374     Response response = client.newCall(request).execute();
375     assertEquals(response.body().string(), "b");
376   }
377 
378   /** Make sure interceptors can interact with the OkHttp client. */
interceptorMakesAnUnrelatedRequest()379   @Test public void interceptorMakesAnUnrelatedRequest() throws Exception {
380     server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor.
381     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
382 
383     client.interceptors().add(new Interceptor() {
384       @Override public Response intercept(Chain chain) throws IOException {
385         if (chain.request().url().getPath().equals("/b")) {
386           Request requestA = new Request.Builder()
387               .url(server.url("/a"))
388               .build();
389           Response responseA = client.newCall(requestA).execute();
390           assertEquals("a", responseA.body().string());
391         }
392 
393         return chain.proceed(chain.request());
394       }
395     });
396 
397     Request requestB = new Request.Builder()
398         .url(server.url("/b"))
399         .build();
400     Response responseB = client.newCall(requestB).execute();
401     assertEquals("b", responseB.body().string());
402   }
403 
404   /** Make sure interceptors can interact with the OkHttp client asynchronously. */
interceptorMakesAnUnrelatedAsyncRequest()405   @Test public void interceptorMakesAnUnrelatedAsyncRequest() throws Exception {
406     server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor.
407     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
408 
409     client.interceptors().add(new Interceptor() {
410       @Override public Response intercept(Chain chain) throws IOException {
411         if (chain.request().url().getPath().equals("/b")) {
412           Request requestA = new Request.Builder()
413               .url(server.url("/a"))
414               .build();
415 
416           try {
417             RecordingCallback callbackA = new RecordingCallback();
418             client.newCall(requestA).enqueue(callbackA);
419             callbackA.await(requestA.httpUrl()).assertBody("a");
420           } catch (Exception e) {
421             throw new RuntimeException(e);
422           }
423         }
424 
425         return chain.proceed(chain.request());
426       }
427     });
428 
429     Request requestB = new Request.Builder()
430         .url(server.url("/b"))
431         .build();
432     RecordingCallback callbackB = new RecordingCallback();
433     client.newCall(requestB).enqueue(callbackB);
434     callbackB.await(requestB.httpUrl()).assertBody("b");
435   }
436 
applicationkInterceptorThrowsRuntimeExceptionSynchronous()437   @Test public void applicationkInterceptorThrowsRuntimeExceptionSynchronous() throws Exception {
438     interceptorThrowsRuntimeExceptionSynchronous(client.interceptors());
439   }
440 
networkInterceptorThrowsRuntimeExceptionSynchronous()441   @Test public void networkInterceptorThrowsRuntimeExceptionSynchronous() throws Exception {
442     interceptorThrowsRuntimeExceptionSynchronous(client.networkInterceptors());
443   }
444 
445   /**
446    * When an interceptor throws an unexpected exception, synchronous callers can catch it and deal
447    * with it.
448    *
449    * TODO(jwilson): test that resources are not leaked when this happens.
450    */
interceptorThrowsRuntimeExceptionSynchronous( List<Interceptor> interceptors)451   private void interceptorThrowsRuntimeExceptionSynchronous(
452       List<Interceptor> interceptors) throws Exception {
453     interceptors.add(new Interceptor() {
454       @Override public Response intercept(Chain chain) throws IOException {
455         throw new RuntimeException("boom!");
456       }
457     });
458 
459     Request request = new Request.Builder()
460         .url(server.url("/"))
461         .build();
462 
463     try {
464       client.newCall(request).execute();
465       fail();
466     } catch (RuntimeException expected) {
467       assertEquals("boom!", expected.getMessage());
468     }
469   }
470 
applicationInterceptorThrowsRuntimeExceptionAsynchronous()471   @Test public void applicationInterceptorThrowsRuntimeExceptionAsynchronous() throws Exception {
472     interceptorThrowsRuntimeExceptionAsynchronous(client.interceptors());
473   }
474 
networkInterceptorThrowsRuntimeExceptionAsynchronous()475   @Test public void networkInterceptorThrowsRuntimeExceptionAsynchronous() throws Exception {
476     interceptorThrowsRuntimeExceptionAsynchronous(client.networkInterceptors());
477   }
478 
networkInterceptorModifiedRequestIsReturned()479   @Test public void networkInterceptorModifiedRequestIsReturned() throws IOException {
480     server.enqueue(new MockResponse());
481 
482     Interceptor modifyHeaderInterceptor = new Interceptor() {
483       @Override public Response intercept(Chain chain) throws IOException {
484         return chain.proceed(chain.request().newBuilder()
485           .header("User-Agent", "intercepted request")
486           .build());
487       }
488     };
489 
490     client.networkInterceptors().add(modifyHeaderInterceptor);
491 
492     Request request = new Request.Builder()
493         .url(server.url("/"))
494         .header("User-Agent", "user request")
495         .build();
496 
497     Response response = client.newCall(request).execute();
498     assertNotNull(response.request().header("User-Agent"));
499     assertEquals("user request", response.request().header("User-Agent"));
500     assertEquals("intercepted request", response.networkResponse().request().header("User-Agent"));
501   }
502 
503   /**
504    * When an interceptor throws an unexpected exception, asynchronous callers are left hanging. The
505    * exception goes to the uncaught exception handler.
506    *
507    * TODO(jwilson): test that resources are not leaked when this happens.
508    */
interceptorThrowsRuntimeExceptionAsynchronous( List<Interceptor> interceptors)509   private void interceptorThrowsRuntimeExceptionAsynchronous(
510         List<Interceptor> interceptors) throws Exception {
511     interceptors.add(new Interceptor() {
512       @Override public Response intercept(Chain chain) throws IOException {
513         throw new RuntimeException("boom!");
514       }
515     });
516 
517     ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor();
518     client.setDispatcher(new Dispatcher(executor));
519 
520     Request request = new Request.Builder()
521         .url(server.url("/"))
522         .build();
523     client.newCall(request).enqueue(callback);
524 
525     assertEquals("boom!", executor.takeException().getMessage());
526   }
527 
uppercase(final RequestBody original)528   private RequestBody uppercase(final RequestBody original) {
529     return new RequestBody() {
530       @Override public MediaType contentType() {
531         return original.contentType();
532       }
533 
534       @Override public long contentLength() throws IOException {
535         return original.contentLength();
536       }
537 
538       @Override public void writeTo(BufferedSink sink) throws IOException {
539         Sink uppercase = uppercase(sink);
540         BufferedSink bufferedSink = Okio.buffer(uppercase);
541         original.writeTo(bufferedSink);
542         bufferedSink.emit();
543       }
544     };
545   }
546 
547   private Sink uppercase(final BufferedSink original) {
548     return new ForwardingSink(original) {
549       @Override public void write(Buffer source, long byteCount) throws IOException {
550         original.writeUtf8(source.readUtf8(byteCount).toUpperCase(Locale.US));
551       }
552     };
553   }
554 
555   static ResponseBody uppercase(ResponseBody original) throws IOException {
556     return ResponseBody.create(original.contentType(), original.contentLength(),
557         Okio.buffer(uppercase(original.source())));
558   }
559 
560   private static Source uppercase(final Source original) {
561     return new ForwardingSource(original) {
562       @Override public long read(Buffer sink, long byteCount) throws IOException {
563         Buffer mixedCase = new Buffer();
564         long count = original.read(mixedCase, byteCount);
565         sink.writeUtf8(mixedCase.readUtf8().toUpperCase(Locale.US));
566         return count;
567       }
568     };
569   }
570 
571   private Buffer gzip(String data) throws IOException {
572     Buffer result = new Buffer();
573     BufferedSink sink = Okio.buffer(new GzipSink(result));
574     sink.writeUtf8(data);
575     sink.close();
576     return result;
577   }
578 
579   /** Catches exceptions that are otherwise headed for the uncaught exception handler. */
580   private static class ExceptionCatchingExecutor extends ThreadPoolExecutor {
581     private final BlockingQueue<Exception> exceptions = new LinkedBlockingQueue<>();
582 
583     public ExceptionCatchingExecutor() {
584       super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
585     }
586 
587     @Override public void execute(final Runnable runnable) {
588       super.execute(new Runnable() {
589         @Override public void run() {
590           try {
591             runnable.run();
592           } catch (Exception e) {
593             exceptions.add(e);
594           }
595         }
596       });
597     }
598 
599     public Exception takeException() throws InterruptedException {
600       return exceptions.take();
601     }
602   }
603 }
604