• 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.getUriHost();
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 
networkInterceptorsCanChangeRequestMethodFromGetToPost()213   @Test public void networkInterceptorsCanChangeRequestMethodFromGetToPost() throws Exception {
214     server.enqueue(new MockResponse());
215 
216     client.networkInterceptors().add(new Interceptor() {
217       @Override
218       public Response intercept(Chain chain) throws IOException {
219         Request originalRequest = chain.request();
220         MediaType mediaType = MediaType.parse("text/plain");
221         RequestBody body = RequestBody.create(mediaType, "abc");
222         return chain.proceed(originalRequest.newBuilder()
223             .method("POST", body)
224             .header("Content-Type", mediaType.toString())
225             .header("Content-Length", Long.toString(body.contentLength()))
226             .build());
227       }
228     });
229 
230     Request request = new Request.Builder()
231         .url(server.url("/"))
232         .get()
233         .build();
234 
235     client.newCall(request).execute();
236 
237     RecordedRequest recordedRequest = server.takeRequest();
238     assertEquals("POST", recordedRequest.getMethod());
239     assertEquals("abc", recordedRequest.getBody().readUtf8());
240   }
241 
applicationInterceptorsRewriteRequestToServer()242   @Test public void applicationInterceptorsRewriteRequestToServer() throws Exception {
243     rewriteRequestToServer(client.interceptors());
244   }
245 
networkInterceptorsRewriteRequestToServer()246   @Test public void networkInterceptorsRewriteRequestToServer() throws Exception {
247     rewriteRequestToServer(client.networkInterceptors());
248   }
249 
rewriteRequestToServer(List<Interceptor> interceptors)250   private void rewriteRequestToServer(List<Interceptor> interceptors) throws Exception {
251     server.enqueue(new MockResponse());
252 
253     interceptors.add(new Interceptor() {
254       @Override public Response intercept(Chain chain) throws IOException {
255         Request originalRequest = chain.request();
256         return chain.proceed(originalRequest.newBuilder()
257             .method("POST", uppercase(originalRequest.body()))
258             .addHeader("OkHttp-Intercepted", "yep")
259             .build());
260       }
261     });
262 
263     Request request = new Request.Builder()
264         .url(server.url("/"))
265         .addHeader("Original-Header", "foo")
266         .method("PUT", RequestBody.create(MediaType.parse("text/plain"), "abc"))
267         .build();
268 
269     client.newCall(request).execute();
270 
271     RecordedRequest recordedRequest = server.takeRequest();
272     assertEquals("ABC", recordedRequest.getBody().readUtf8());
273     assertEquals("foo", recordedRequest.getHeader("Original-Header"));
274     assertEquals("yep", recordedRequest.getHeader("OkHttp-Intercepted"));
275     assertEquals("POST", recordedRequest.getMethod());
276   }
277 
applicationInterceptorsRewriteResponseFromServer()278   @Test public void applicationInterceptorsRewriteResponseFromServer() throws Exception {
279     rewriteResponseFromServer(client.interceptors());
280   }
281 
networkInterceptorsRewriteResponseFromServer()282   @Test public void networkInterceptorsRewriteResponseFromServer() throws Exception {
283     rewriteResponseFromServer(client.networkInterceptors());
284   }
285 
rewriteResponseFromServer(List<Interceptor> interceptors)286   private void rewriteResponseFromServer(List<Interceptor> interceptors) throws Exception {
287     server.enqueue(new MockResponse()
288         .addHeader("Original-Header: foo")
289         .setBody("abc"));
290 
291     interceptors.add(new Interceptor() {
292       @Override public Response intercept(Chain chain) throws IOException {
293         Response originalResponse = chain.proceed(chain.request());
294         return originalResponse.newBuilder()
295             .body(uppercase(originalResponse.body()))
296             .addHeader("OkHttp-Intercepted", "yep")
297             .build();
298       }
299     });
300 
301     Request request = new Request.Builder()
302         .url(server.url("/"))
303         .build();
304 
305     Response response = client.newCall(request).execute();
306     assertEquals("ABC", response.body().string());
307     assertEquals("yep", response.header("OkHttp-Intercepted"));
308     assertEquals("foo", response.header("Original-Header"));
309   }
310 
multipleApplicationInterceptors()311   @Test public void multipleApplicationInterceptors() throws Exception {
312     multipleInterceptors(client.interceptors());
313   }
314 
multipleNetworkInterceptors()315   @Test public void multipleNetworkInterceptors() throws Exception {
316     multipleInterceptors(client.networkInterceptors());
317   }
318 
multipleInterceptors(List<Interceptor> interceptors)319   private void multipleInterceptors(List<Interceptor> interceptors) throws Exception {
320     server.enqueue(new MockResponse());
321 
322     interceptors.add(new Interceptor() {
323       @Override public Response intercept(Chain chain) throws IOException {
324         Request originalRequest = chain.request();
325         Response originalResponse = chain.proceed(originalRequest.newBuilder()
326             .addHeader("Request-Interceptor", "Android") // 1. Added first.
327             .build());
328         return originalResponse.newBuilder()
329             .addHeader("Response-Interceptor", "Donut") // 4. Added last.
330             .build();
331       }
332     });
333     interceptors.add(new Interceptor() {
334       @Override public Response intercept(Chain chain) throws IOException {
335         Request originalRequest = chain.request();
336         Response originalResponse = chain.proceed(originalRequest.newBuilder()
337             .addHeader("Request-Interceptor", "Bob") // 2. Added second.
338             .build());
339         return originalResponse.newBuilder()
340             .addHeader("Response-Interceptor", "Cupcake") // 3. Added third.
341             .build();
342       }
343     });
344 
345     Request request = new Request.Builder()
346         .url(server.url("/"))
347         .build();
348 
349     Response response = client.newCall(request).execute();
350     assertEquals(Arrays.asList("Cupcake", "Donut"),
351         response.headers("Response-Interceptor"));
352 
353     RecordedRequest recordedRequest = server.takeRequest();
354     assertEquals(Arrays.asList("Android", "Bob"),
355         recordedRequest.getHeaders().values("Request-Interceptor"));
356   }
357 
asyncApplicationInterceptors()358   @Test public void asyncApplicationInterceptors() throws Exception {
359     asyncInterceptors(client.interceptors());
360   }
361 
asyncNetworkInterceptors()362   @Test public void asyncNetworkInterceptors() throws Exception {
363     asyncInterceptors(client.networkInterceptors());
364   }
365 
asyncInterceptors(List<Interceptor> interceptors)366   private void asyncInterceptors(List<Interceptor> interceptors) throws Exception {
367     server.enqueue(new MockResponse());
368 
369     interceptors.add(new Interceptor() {
370       @Override public Response intercept(Chain chain) throws IOException {
371         Response originalResponse = chain.proceed(chain.request());
372         return originalResponse.newBuilder()
373             .addHeader("OkHttp-Intercepted", "yep")
374             .build();
375       }
376     });
377 
378     Request request = new Request.Builder()
379         .url(server.url("/"))
380         .build();
381     client.newCall(request).enqueue(callback);
382 
383     callback.await(request.httpUrl())
384         .assertCode(200)
385         .assertHeader("OkHttp-Intercepted", "yep");
386   }
387 
applicationInterceptorsCanMakeMultipleRequestsToServer()388   @Test public void applicationInterceptorsCanMakeMultipleRequestsToServer() throws Exception {
389     server.enqueue(new MockResponse().setBody("a"));
390     server.enqueue(new MockResponse().setBody("b"));
391 
392     client.interceptors().add(new Interceptor() {
393       @Override public Response intercept(Chain chain) throws IOException {
394         Response response1 = chain.proceed(chain.request());
395         response1.body().close();
396         return chain.proceed(chain.request());
397       }
398     });
399 
400     Request request = new Request.Builder()
401         .url(server.url("/"))
402         .build();
403 
404     Response response = client.newCall(request).execute();
405     assertEquals(response.body().string(), "b");
406   }
407 
408   /** Make sure interceptors can interact with the OkHttp client. */
interceptorMakesAnUnrelatedRequest()409   @Test public void interceptorMakesAnUnrelatedRequest() throws Exception {
410     server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor.
411     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
412 
413     client.interceptors().add(new Interceptor() {
414       @Override public Response intercept(Chain chain) throws IOException {
415         if (chain.request().url().getPath().equals("/b")) {
416           Request requestA = new Request.Builder()
417               .url(server.url("/a"))
418               .build();
419           Response responseA = client.newCall(requestA).execute();
420           assertEquals("a", responseA.body().string());
421         }
422 
423         return chain.proceed(chain.request());
424       }
425     });
426 
427     Request requestB = new Request.Builder()
428         .url(server.url("/b"))
429         .build();
430     Response responseB = client.newCall(requestB).execute();
431     assertEquals("b", responseB.body().string());
432   }
433 
434   /** Make sure interceptors can interact with the OkHttp client asynchronously. */
interceptorMakesAnUnrelatedAsyncRequest()435   @Test public void interceptorMakesAnUnrelatedAsyncRequest() throws Exception {
436     server.enqueue(new MockResponse().setBody("a")); // Fetched by interceptor.
437     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
438 
439     client.interceptors().add(new Interceptor() {
440       @Override public Response intercept(Chain chain) throws IOException {
441         if (chain.request().url().getPath().equals("/b")) {
442           Request requestA = new Request.Builder()
443               .url(server.url("/a"))
444               .build();
445 
446           try {
447             RecordingCallback callbackA = new RecordingCallback();
448             client.newCall(requestA).enqueue(callbackA);
449             callbackA.await(requestA.httpUrl()).assertBody("a");
450           } catch (Exception e) {
451             throw new RuntimeException(e);
452           }
453         }
454 
455         return chain.proceed(chain.request());
456       }
457     });
458 
459     Request requestB = new Request.Builder()
460         .url(server.url("/b"))
461         .build();
462     RecordingCallback callbackB = new RecordingCallback();
463     client.newCall(requestB).enqueue(callbackB);
464     callbackB.await(requestB.httpUrl()).assertBody("b");
465   }
466 
applicationkInterceptorThrowsRuntimeExceptionSynchronous()467   @Test public void applicationkInterceptorThrowsRuntimeExceptionSynchronous() throws Exception {
468     interceptorThrowsRuntimeExceptionSynchronous(client.interceptors());
469   }
470 
networkInterceptorThrowsRuntimeExceptionSynchronous()471   @Test public void networkInterceptorThrowsRuntimeExceptionSynchronous() throws Exception {
472     interceptorThrowsRuntimeExceptionSynchronous(client.networkInterceptors());
473   }
474 
475   /**
476    * When an interceptor throws an unexpected exception, synchronous callers can catch it and deal
477    * with it.
478    *
479    * TODO(jwilson): test that resources are not leaked when this happens.
480    */
interceptorThrowsRuntimeExceptionSynchronous( List<Interceptor> interceptors)481   private void interceptorThrowsRuntimeExceptionSynchronous(
482       List<Interceptor> interceptors) throws Exception {
483     interceptors.add(new Interceptor() {
484       @Override public Response intercept(Chain chain) throws IOException {
485         throw new RuntimeException("boom!");
486       }
487     });
488 
489     Request request = new Request.Builder()
490         .url(server.url("/"))
491         .build();
492 
493     try {
494       client.newCall(request).execute();
495       fail();
496     } catch (RuntimeException expected) {
497       assertEquals("boom!", expected.getMessage());
498     }
499   }
500 
networkInterceptorModifiedRequestIsReturned()501   @Test public void networkInterceptorModifiedRequestIsReturned() throws IOException {
502     server.enqueue(new MockResponse());
503 
504     Interceptor modifyHeaderInterceptor = new Interceptor() {
505       @Override public Response intercept(Chain chain) throws IOException {
506         return chain.proceed(chain.request().newBuilder()
507           .header("User-Agent", "intercepted request")
508           .build());
509       }
510     };
511 
512     client.networkInterceptors().add(modifyHeaderInterceptor);
513 
514     Request request = new Request.Builder()
515         .url(server.url("/"))
516         .header("User-Agent", "user request")
517         .build();
518 
519     Response response = client.newCall(request).execute();
520     assertNotNull(response.request().header("User-Agent"));
521     assertEquals("user request", response.request().header("User-Agent"));
522     assertEquals("intercepted request", response.networkResponse().request().header("User-Agent"));
523   }
524 
applicationInterceptorThrowsRuntimeExceptionAsynchronous()525   @Test public void applicationInterceptorThrowsRuntimeExceptionAsynchronous() throws Exception {
526     interceptorThrowsRuntimeExceptionAsynchronous(client.interceptors());
527   }
528 
networkInterceptorThrowsRuntimeExceptionAsynchronous()529   @Test public void networkInterceptorThrowsRuntimeExceptionAsynchronous() throws Exception {
530     interceptorThrowsRuntimeExceptionAsynchronous(client.networkInterceptors());
531   }
532 
533   /**
534    * When an interceptor throws an unexpected exception, asynchronous callers are left hanging. The
535    * exception goes to the uncaught exception handler.
536    *
537    * TODO(jwilson): test that resources are not leaked when this happens.
538    */
interceptorThrowsRuntimeExceptionAsynchronous( List<Interceptor> interceptors)539   private void interceptorThrowsRuntimeExceptionAsynchronous(
540         List<Interceptor> interceptors) throws Exception {
541     interceptors.add(new Interceptor() {
542       @Override public Response intercept(Chain chain) throws IOException {
543         throw new RuntimeException("boom!");
544       }
545     });
546 
547     ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor();
548     client.setDispatcher(new Dispatcher(executor));
549 
550     Request request = new Request.Builder()
551         .url(server.url("/"))
552         .build();
553     client.newCall(request).enqueue(callback);
554 
555     assertEquals("boom!", executor.takeException().getMessage());
556   }
557 
applicationInterceptorReturnsNull()558   @Test public void applicationInterceptorReturnsNull() throws Exception {
559     server.enqueue(new MockResponse());
560 
561     Interceptor interceptor = new Interceptor() {
562       @Override public Response intercept(Chain chain) throws IOException {
563         chain.proceed(chain.request());
564         return null;
565       }
566     };
567     client.interceptors().add(interceptor);
568 
569     ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor();
570     client.setDispatcher(new Dispatcher(executor));
571 
572     Request request = new Request.Builder()
573         .url(server.url("/"))
574         .build();
575     try {
576       client.newCall(request).execute();
577       fail();
578     } catch (NullPointerException expected) {
579       assertEquals("application interceptor " + interceptor
580           + " returned null", expected.getMessage());
581     }
582   }
583 
networkInterceptorReturnsNull()584   @Test public void networkInterceptorReturnsNull() throws Exception {
585     server.enqueue(new MockResponse());
586 
587     Interceptor interceptor = new Interceptor() {
588       @Override public Response intercept(Chain chain) throws IOException {
589         chain.proceed(chain.request());
590         return null;
591       }
592     };
593     client.networkInterceptors().add(interceptor);
594 
595     ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor();
596     client.setDispatcher(new Dispatcher(executor));
597 
598     Request request = new Request.Builder()
599         .url(server.url("/"))
600         .build();
601     try {
602       client.newCall(request).execute();
603       fail();
604     } catch (NullPointerException expected) {
605       assertEquals("network interceptor " + interceptor + " returned null", expected.getMessage());
606     }
607   }
608 
uppercase(final RequestBody original)609   private RequestBody uppercase(final RequestBody original) {
610     return new RequestBody() {
611       @Override public MediaType contentType() {
612         return original.contentType();
613       }
614 
615       @Override public long contentLength() throws IOException {
616         return original.contentLength();
617       }
618 
619       @Override public void writeTo(BufferedSink sink) throws IOException {
620         Sink uppercase = uppercase(sink);
621         BufferedSink bufferedSink = Okio.buffer(uppercase);
622         original.writeTo(bufferedSink);
623         bufferedSink.emit();
624       }
625     };
626   }
627 
628   private Sink uppercase(final BufferedSink original) {
629     return new ForwardingSink(original) {
630       @Override public void write(Buffer source, long byteCount) throws IOException {
631         original.writeUtf8(source.readUtf8(byteCount).toUpperCase(Locale.US));
632       }
633     };
634   }
635 
636   static ResponseBody uppercase(ResponseBody original) throws IOException {
637     return ResponseBody.create(original.contentType(), original.contentLength(),
638         Okio.buffer(uppercase(original.source())));
639   }
640 
641   private static Source uppercase(final Source original) {
642     return new ForwardingSource(original) {
643       @Override public long read(Buffer sink, long byteCount) throws IOException {
644         Buffer mixedCase = new Buffer();
645         long count = original.read(mixedCase, byteCount);
646         sink.writeUtf8(mixedCase.readUtf8().toUpperCase(Locale.US));
647         return count;
648       }
649     };
650   }
651 
652   private Buffer gzip(String data) throws IOException {
653     Buffer result = new Buffer();
654     BufferedSink sink = Okio.buffer(new GzipSink(result));
655     sink.writeUtf8(data);
656     sink.close();
657     return result;
658   }
659 
660   /** Catches exceptions that are otherwise headed for the uncaught exception handler. */
661   private static class ExceptionCatchingExecutor extends ThreadPoolExecutor {
662     private final BlockingQueue<Exception> exceptions = new LinkedBlockingQueue<>();
663 
664     public ExceptionCatchingExecutor() {
665       super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
666     }
667 
668     @Override public void execute(final Runnable runnable) {
669       super.execute(new Runnable() {
670         @Override public void run() {
671           try {
672             runnable.run();
673           } catch (Exception e) {
674             exceptions.add(e);
675           }
676         }
677       });
678     }
679 
680     public Exception takeException() throws InterruptedException {
681       return exceptions.take();
682     }
683   }
684 }
685