154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/* 254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Copyright (C) 2011 Google Inc. 354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License"); 554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * you may not use this file except in compliance with the License. 654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * You may obtain a copy of the License at 754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * http://www.apache.org/licenses/LICENSE-2.0 954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 1054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Unless required by applicable law or agreed to in writing, software 1154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS, 1254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * See the License for the specific language governing permissions and 1454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limitations under the License. 1554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 1754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpackage com.google.mockwebserver; 1854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 1954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START; 2024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; 2154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream; 2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream; 2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream; 2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException; 2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream; 2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream; 2754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetAddress; 2854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetSocketAddress; 2954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.MalformedURLException; 3054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Proxy; 3154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.ServerSocket; 3254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Socket; 3354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.SocketException; 3454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.URL; 3554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.UnknownHostException; 36d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fullerimport java.nio.charset.StandardCharsets; 37d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fullerimport java.security.SecureRandom; 3824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException; 3924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate; 4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList; 4154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator; 4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List; 43d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fullerimport java.util.Locale; 447a68ed6a49c3060b235810391a82412a95f9c979jwilsonimport java.util.Map; 4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue; 4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap; 4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService; 4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors; 4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue; 5054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger; 5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level; 5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger; 5324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext; 5454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket; 5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory; 5624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager; 5724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager; 5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/** 6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server 6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence. 6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer { 64d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller private static final X509TrustManager UNTRUSTED_TRUST_MANAGER = new X509TrustManager() { 65d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller @Override public void checkClientTrusted(X509Certificate[] chain, String authType) 66d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throws CertificateException { 67d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throw new CertificateException(); 68d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 69d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 70d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { 71d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throw new AssertionError(); 72d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 7354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 74d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller @Override public X509Certificate[] getAcceptedIssuers() { 75d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throw new AssertionError(); 76d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 77d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller }; 7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static final Logger logger = Logger.getLogger(MockWebServer.class.getName()); 80d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final BlockingQueue<RecordedRequest> requestQueue 8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson = new LinkedBlockingQueue<RecordedRequest>(); 83d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 847a68ed6a49c3060b235810391a82412a95f9c979jwilson /** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */ 857a68ed6a49c3060b235810391a82412a95f9c979jwilson private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>(); 8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private final AtomicInteger requestCount = new AtomicInteger(); 8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int bodyLimit = Integer.MAX_VALUE; 8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private ServerSocket serverSocket; 8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private SSLSocketFactory sslSocketFactory; 908ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller private ExecutorService acceptExecutor; 918ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller private ExecutorService requestExecutor; 9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private boolean tunnelProxy; 937a68ed6a49c3060b235810391a82412a95f9c979jwilson private Dispatcher dispatcher = new QueueDispatcher(); 9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int port = -1; 968ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller private int workerThreads = Integer.MAX_VALUE; 978ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller 9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getPort() { 9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (port == -1) { 10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Cannot retrieve port before calling play()"); 10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return port; 10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public String getHostName() { 10654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 10754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return InetAddress.getLocalHost().getHostName(); 10854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (UnknownHostException e) { 109d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throw new AssertionError(e); 11054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public Proxy toProxyAddress() { 11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort())); 11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 11654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 11754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 11854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns a URL for connecting to this server. 11954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 12054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param path the request path, such as "/". 12154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1227a68ed6a49c3060b235810391a82412a95f9c979jwilson public URL getUrl(String path) { 1237a68ed6a49c3060b235810391a82412a95f9c979jwilson try { 1247a68ed6a49c3060b235810391a82412a95f9c979jwilson return sslSocketFactory != null 1257a68ed6a49c3060b235810391a82412a95f9c979jwilson ? new URL("https://" + getHostName() + ":" + getPort() + path) 1267a68ed6a49c3060b235810391a82412a95f9c979jwilson : new URL("http://" + getHostName() + ":" + getPort() + path); 1277a68ed6a49c3060b235810391a82412a95f9c979jwilson } catch (MalformedURLException e) { 1287a68ed6a49c3060b235810391a82412a95f9c979jwilson throw new AssertionError(e); 1297a68ed6a49c3060b235810391a82412a95f9c979jwilson } 13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 13318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * Returns a cookie domain for this server. This returns the server's 13418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * non-loopback host name if it is known. Otherwise this returns ".local" 13518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson * for this server's loopback name. 13618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson */ 13718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson public String getCookieDomain() { 13818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson String hostName = getHostName(); 13918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson return hostName.contains(".") ? hostName : ".local"; 14018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson } 14118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson 1428ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller public void setWorkerThreads(int threads) { 1438ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller this.workerThreads = threads; 1448ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller } 1458ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller 14618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson /** 14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Sets the number of bytes of the POST body to keep in memory to the given 14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limit. 14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void setBodyLimit(int maxBodyLength) { 15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.bodyLimit = maxBodyLength; 15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Serve requests with HTTPS rather than otherwise. 15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param tunnelProxy whether to expect the HTTP CONNECT method before 15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * negotiating TLS. 15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) { 16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.sslSocketFactory = sslSocketFactory; 16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.tunnelProxy = tunnelProxy; 16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Awaits the next HTTP request, removes it, and returns it. Callers should 16754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * use this to verify the request sent was as intended. 16854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public RecordedRequest takeRequest() throws InterruptedException { 17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestQueue.take(); 17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the number of HTTP requests received thus far by this server. 17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This may exceed the number of HTTP connections when connection reuse is 17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * in practice. 17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public int getRequestCount() { 17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return requestCount.get(); 18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 1837a68ed6a49c3060b235810391a82412a95f9c979jwilson * Scripts {@code response} to be returned to a request made in sequence. 1847a68ed6a49c3060b235810391a82412a95f9c979jwilson * The first request is served by the first enqueued response; the second 1857a68ed6a49c3060b235810391a82412a95f9c979jwilson * request by the second enqueued response; and so on. 18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 1877a68ed6a49c3060b235810391a82412a95f9c979jwilson * @throws ClassCastException if the default dispatcher has been replaced 1887a68ed6a49c3060b235810391a82412a95f9c979jwilson * with {@link #setDispatcher(Dispatcher)}. 18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 1907a68ed6a49c3060b235810391a82412a95f9c979jwilson public void enqueue(MockResponse response) { 1917a68ed6a49c3060b235810391a82412a95f9c979jwilson ((QueueDispatcher) dispatcher).enqueueResponse(response.clone()); 19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Equivalent to {@code play(0)}. 19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play() throws IOException { 19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson play(0); 19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Starts the server, serves all enqueued requests, and shuts the server 20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * down. 20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * 20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * @param port the port to listen to, or 0 for any available port. 20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Automated tests should always use port 0 to avoid flakiness when a 20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * specific port is unavailable. 20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void play(int port) throws IOException { 2108ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller if (acceptExecutor != null) { 2116ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom throw new IllegalStateException("play() already called"); 2126ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom } 2138ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // The acceptExecutor handles the Socket.accept() and hands each request off to the 2148ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // requestExecutor. It also handles shutdown. 2158ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller acceptExecutor = Executors.newSingleThreadExecutor(); 2168ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // The requestExecutor has a fixed number of worker threads. In order to get strict 2178ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // guarantees that requests are handled in the order in which they are accepted 2188ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // workerThreads should be set to 1. 2198ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller requestExecutor = Executors.newFixedThreadPool(workerThreads); 22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket = new ServerSocket(port); 22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.setReuseAddress(true); 22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson this.port = serverSocket.getLocalPort(); 2248ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller acceptExecutor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() { 22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson acceptConnections(); 22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /* 23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * This gnarly block of code will release all sockets and 23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * all thread, even if any close fails. 23554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 23654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 23754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); 23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer server socket close failed", e); 24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2417a68ed6a49c3060b235810391a82412a95f9c979jwilson for (Iterator<Socket> s = openClientSockets.keySet().iterator(); s.hasNext(); ) { 24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.next().close(); 24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson s.remove(); 24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer socket close failed", e); 24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 2508ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller acceptExecutor.shutdown(); 2518ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller } catch (Throwable e) { 2528ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller logger.log(Level.WARNING, "MockWebServer acceptExecutor shutdown failed", e); 2538ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller } 2548ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller try { 2558ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller requestExecutor.shutdown(); 25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Throwable e) { 2578ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller logger.log(Level.WARNING, "MockWebServer requestExecutor shutdown failed", e); 25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void acceptConnections() throws Exception { 2627a68ed6a49c3060b235810391a82412a95f9c979jwilson while (true) { 26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = serverSocket.accept(); 2667a68ed6a49c3060b235810391a82412a95f9c979jwilson } catch (SocketException e) { 2677a68ed6a49c3060b235810391a82412a95f9c979jwilson return; 26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 269d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller SocketPolicy socketPolicy = dispatcher.peek().getSocketPolicy(); 2707a68ed6a49c3060b235810391a82412a95f9c979jwilson if (socketPolicy == DISCONNECT_AT_START) { 2717a68ed6a49c3060b235810391a82412a95f9c979jwilson dispatchBookkeepingRequest(0, socket); 27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 2747a68ed6a49c3060b235810391a82412a95f9c979jwilson openClientSockets.put(socket, true); 27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serveConnection(socket); 27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 2777a68ed6a49c3060b235810391a82412a95f9c979jwilson } 27854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void shutdown() throws IOException { 28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (serverSocket != null) { 28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson serverSocket.close(); // should cause acceptConnections() to break out 28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void serveConnection(final Socket raw) { 28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String name = "MockWebServer-" + raw.getRemoteSocketAddress(); 2908ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller requestExecutor.execute(namedRunnable(name, new Runnable() { 29154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int sequenceNumber = 0; 29254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 29454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson processConnection(); 29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (Exception e) { 29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.log(Level.WARNING, "MockWebServer connection failed", e); 29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void processConnection() throws Exception { 30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Socket socket; 30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sslSocketFactory != null) { 30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (tunnelProxy) { 30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson createTunnel(); 30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 307d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller SocketPolicy socketPolicy = dispatcher.peek().getSocketPolicy(); 3087a68ed6a49c3060b235810391a82412a95f9c979jwilson if (socketPolicy == FAIL_HANDSHAKE) { 3097a68ed6a49c3060b235810391a82412a95f9c979jwilson dispatchBookkeepingRequest(sequenceNumber, raw); 310d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller processHandshakeFailure(raw); 31124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson return; 31224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = sslSocketFactory.createSocket( 31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 315d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller SSLSocket sslSocket = (SSLSocket) socket; 316d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller sslSocket.setUseClientMode(false); 3177a68ed6a49c3060b235810391a82412a95f9c979jwilson openClientSockets.put(socket, true); 318d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 319d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller sslSocket.startHandshake(); 320d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(raw); 32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket = raw; 32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson InputStream in = new BufferedInputStream(socket.getInputStream()); 32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson OutputStream out = new BufferedOutputStream(socket.getOutputStream()); 32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 3297a68ed6a49c3060b235810391a82412a95f9c979jwilson while (processOneRequest(socket, in, out)) { 3307a68ed6a49c3060b235810391a82412a95f9c979jwilson } 33154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 33254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (sequenceNumber == 0) { 33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson logger.warning("MockWebServer connection didn't make a request"); 33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.close(); 33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson openClientSockets.remove(socket); 34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response 34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * is dispatched. 34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void createTunnel() throws IOException, InterruptedException { 34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 348d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller SocketPolicy socketPolicy = dispatcher.peek().getSocketPolicy(); 34924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) { 35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Tunnel without any CONNECT!"); 35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 352d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (socketPolicy == SocketPolicy.UPGRADE_TO_SSL_AT_END) return; 35354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 35554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 35654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 35754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Reads a request and writes its response. Returns true if a request 35854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * was processed. 35954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 36024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson private boolean processOneRequest(Socket socket, InputStream in, OutputStream out) 36154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throws IOException, InterruptedException { 362d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller RecordedRequest request = readRequest(socket, in, out, sequenceNumber); 36354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (request == null) { 36454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return false; 36554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3667a68ed6a49c3060b235810391a82412a95f9c979jwilson requestCount.incrementAndGet(); 3677a68ed6a49c3060b235810391a82412a95f9c979jwilson requestQueue.add(request); 3687a68ed6a49c3060b235810391a82412a95f9c979jwilson MockResponse response = dispatcher.dispatch(request); 3693088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AFTER_READING_REQUEST) { 3703088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath logger.info("Received request: " + request + " and disconnected without responding"); 3713088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath return false; 3723088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath } 3737a68ed6a49c3060b235810391a82412a95f9c979jwilson writeResponse(out, response); 3748ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller 3758ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // For socket policies that poison the socket after the response is written: 3768ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // The client has received the response and will no longer be blocked after 3778ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // writeResponse() has returned. A client can then re-use the connection before 3788ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // the socket is poisoned (i.e. keep-alive / connection pooling). The second 3798ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // request/response may fail at the beginning, middle, end, or even succeed 3808ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // depending on scheduling. Delays can be required in tests to improve the chances 3818ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // of sockets being in a known state when subsequent requests are made. 3828ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // 3838ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // For SHUTDOWN_OUTPUT_AT_END the client may detect a problem with its input socket 3848ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // after the request has been made but before the server has chosen a response. 3858ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // For clients that perform retries, this can cause the client to issue a retry 3868ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // request. The retry handler may call dispatcher.dispatch(request) before the 3878ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // initial, failed request handler does and cause non-obvious response ordering. 3888ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // Setting workerThreads = 1 ensures that the dispatcher is called for requests in 3898ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller // the order they are received. 3908ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller 39154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) { 39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson in.close(); 39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson out.close(); 39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) { 39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownInput(); 39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) { 39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson socket.shutdownOutput(); 39854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 3993b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey logger.info("Received request: " + request + " and responded: " + response); 40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson sequenceNumber++; 40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return true; 40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson })); 40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 406d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller private void processHandshakeFailure(Socket raw) throws Exception { 40724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLContext context = SSLContext.getInstance("TLS"); 408d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller context.init(null, new TrustManager[] { UNTRUSTED_TRUST_MANAGER }, new SecureRandom()); 40924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLSocketFactory sslSocketFactory = context.getSocketFactory(); 41024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket( 41124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true); 41224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson try { 41324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson socket.startHandshake(); // we're testing a handshake failure 41424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson throw new AssertionError(); 41524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } catch (IOException expected) { 41624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 41724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson socket.close(); 4187a68ed6a49c3060b235810391a82412a95f9c979jwilson } 4197a68ed6a49c3060b235810391a82412a95f9c979jwilson 4207a68ed6a49c3060b235810391a82412a95f9c979jwilson private void dispatchBookkeepingRequest(int sequenceNumber, Socket socket) throws InterruptedException { 42124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson requestCount.incrementAndGet(); 422a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath RecordedRequest request = new RecordedRequest(null, null, null, -1, null, sequenceNumber, 423a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath socket); 424a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath dispatcher.dispatch(request); 42524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson } 42624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson 427d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller /** @param sequenceNumber the index of this request on this connection. */ 428d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller private RecordedRequest readRequest(Socket socket, InputStream in, OutputStream out, 429d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller int sequenceNumber) throws IOException { 43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String request; 43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson request = readAsciiUntilCrlf(in); 43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } catch (IOException streamIsClosed) { 43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because we closed the stream 43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 4367a68ed6a49c3060b235810391a82412a95f9c979jwilson if (request.length() == 0) { 43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return null; // no request because the stream is exhausted 43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<String> headers = new ArrayList<String>(); 441d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller long contentLength = -1; 44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean chunked = false; 443d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller boolean expectContinue = false; 44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String header; 4457a68ed6a49c3060b235810391a82412a95f9c979jwilson while ((header = readAsciiUntilCrlf(in)).length() != 0) { 44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson headers.add(header); 447d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller String lowercaseHeader = header.toLowerCase(Locale.US); 44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { 449d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller contentLength = Long.parseLong(header.substring(15).trim()); 45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 451d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (lowercaseHeader.startsWith("transfer-encoding:") 452d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller && lowercaseHeader.substring(18).trim().equals("chunked")) { 45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunked = true; 45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 455d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (lowercaseHeader.startsWith("expect:") 456d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller && lowercaseHeader.substring(7).trim().equals("100-continue")) { 457d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller expectContinue = true; 458d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 459d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 460d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 461d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (expectContinue) { 462d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write(("HTTP/1.1 100 Continue\r\n").getBytes(StandardCharsets.US_ASCII)); 463d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write(("Content-Length: 0\r\n").getBytes(StandardCharsets.US_ASCII)); 464d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write(("\r\n").getBytes(StandardCharsets.US_ASCII)); 465d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.flush(); 46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson boolean hasBody = false; 46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson TruncatingOutputStream requestBody = new TruncatingOutputStream(); 47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson List<Integer> chunkSizes = new ArrayList<Integer>(); 471d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller MockResponse throttlePolicy = dispatcher.peek(); 47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (contentLength != -1) { 47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 474d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throttledTransfer(throttlePolicy, in, requestBody, contentLength); 47554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (chunked) { 47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson hasBody = true; 47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); 47954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (chunkSize == 0) { 48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 48154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson break; 48254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson chunkSizes.add(chunkSize); 484d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throttledTransfer(throttlePolicy, in, requestBody, chunkSize); 48554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson readEmptyLine(in); 48654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 48854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 489d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (request.startsWith("OPTIONS ") 490d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller || request.startsWith("GET ") 491d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller || request.startsWith("HEAD ") 492d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller || request.startsWith("TRACE ") 493d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller || request.startsWith("CONNECT ")) { 49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (hasBody) { 49554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalArgumentException("Request must not have a body: " + request); 49654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 497d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } else if (!request.startsWith("POST ") 498d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller && !request.startsWith("PUT ") 499d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller && !request.startsWith("PATCH ") 500d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller && !request.startsWith("DELETE ")) { // Permitted as spec is ambiguous. 50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new UnsupportedOperationException("Unexpected method: " + request); 50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 504d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller return new RecordedRequest(request, headers, chunkSizes, requestBody.numBytesReceived, 505d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller requestBody.toByteArray(), sequenceNumber, socket); 50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 50754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 5087a68ed6a49c3060b235810391a82412a95f9c979jwilson private void writeResponse(OutputStream out, MockResponse response) throws IOException { 509d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write((response.getStatus() + "\r\n").getBytes(StandardCharsets.US_ASCII)); 510d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller List<String> headers = response.getHeaders(); 511d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller for (int i = 0, size = headers.size(); i < size; i++) { 512d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller String header = headers.get(i); 513d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write((header + "\r\n").getBytes(StandardCharsets.US_ASCII)); 51454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 515d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write(("\r\n").getBytes(StandardCharsets.US_ASCII)); 5167a68ed6a49c3060b235810391a82412a95f9c979jwilson out.flush(); 51754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 518d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller InputStream in = response.getBodyStream(); 519d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (in == null) return; 520d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throttledTransfer(response, in, out, Long.MAX_VALUE); 52154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 52254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 52354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 52454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Transfer bytes from {@code in} to {@code out} until either {@code length} 525d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller * bytes have been transferred or {@code in} is exhausted. The transfer is 526d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller * throttled according to {@code throttlePolicy}. 52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 528d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller private void throttledTransfer(MockResponse throttlePolicy, InputStream in, OutputStream out, 529d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller long limit) throws IOException { 53054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson byte[] buffer = new byte[1024]; 531d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller int bytesPerPeriod = throttlePolicy.getThrottleBytesPerPeriod(); 532d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller long delayMs = throttlePolicy.getThrottleUnit().toMillis(throttlePolicy.getThrottlePeriod()); 533d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 534d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller while (true) { 535d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller for (int b = 0; b < bytesPerPeriod; ) { 536d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller int toRead = (int) Math.min(Math.min(buffer.length, limit), bytesPerPeriod - b); 537d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller int read = in.read(buffer, 0, toRead); 538d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (read == -1) return; 539d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 540d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.write(buffer, 0, read); 541d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller out.flush(); 542d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller b += read; 543d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller limit -= read; 544d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 545d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (limit == 0) return; 546d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } 547d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller 548d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller try { 549d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller if (delayMs != 0) Thread.sleep(delayMs); 550d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller } catch (InterruptedException e) { 551d5e25502a3ed333011753d5f2e1484072a7f5617Neil Fuller throw new AssertionError(); 55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Returns the text from {@code in} until the next "\r\n", or null if 55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * {@code in} is exhausted. 55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private String readAsciiUntilCrlf(InputStream in) throws IOException { 56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson StringBuilder builder = new StringBuilder(); 56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson while (true) { 56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson int c = in.read(); 56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { 56554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.deleteCharAt(builder.length() - 1); 56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else if (c == -1) { 56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return builder.toString(); 56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } else { 57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson builder.append((char) c); 57154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 57454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 57554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private void readEmptyLine(InputStream in) throws IOException { 57654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String line = readAsciiUntilCrlf(in); 5777a68ed6a49c3060b235810391a82412a95f9c979jwilson if (line.length() != 0) { 57854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson throw new IllegalStateException("Expected empty but was: " + line); 57954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 58054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 58154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 58254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson /** 5837a68ed6a49c3060b235810391a82412a95f9c979jwilson * Sets the dispatcher used to match incoming requests to mock responses. 5847a68ed6a49c3060b235810391a82412a95f9c979jwilson * The default dispatcher simply serves a fixed sequence of responses from 5857a68ed6a49c3060b235810391a82412a95f9c979jwilson * a {@link #enqueue(MockResponse) queue}; custom dispatchers can vary the 5867a68ed6a49c3060b235810391a82412a95f9c979jwilson * response based on timing or the content of the request. 5877a68ed6a49c3060b235810391a82412a95f9c979jwilson */ 5887a68ed6a49c3060b235810391a82412a95f9c979jwilson public void setDispatcher(Dispatcher dispatcher) { 5897a68ed6a49c3060b235810391a82412a95f9c979jwilson if (dispatcher == null) { 5907a68ed6a49c3060b235810391a82412a95f9c979jwilson throw new NullPointerException(); 5917a68ed6a49c3060b235810391a82412a95f9c979jwilson } 5927a68ed6a49c3060b235810391a82412a95f9c979jwilson this.dispatcher = dispatcher; 5937a68ed6a49c3060b235810391a82412a95f9c979jwilson } 5947a68ed6a49c3060b235810391a82412a95f9c979jwilson 5957a68ed6a49c3060b235810391a82412a95f9c979jwilson /** 59654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * An output stream that drops data after bodyLimit bytes. 59754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */ 59854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private class TruncatingOutputStream extends ByteArrayOutputStream { 59954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private int numBytesReceived = 0; 60054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(byte[] buffer, int offset, int len) { 60154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived += len; 60254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(buffer, offset, Math.min(len, bodyLimit - count)); 60354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 60454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson @Override public void write(int oneByte) { 60554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson numBytesReceived++; 60654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson if (count < bodyLimit) { 60754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson super.write(oneByte); 60854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 60954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 61054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 61154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson 61254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson private static Runnable namedRunnable(final String name, final Runnable runnable) { 61354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson return new Runnable() { 61454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson public void run() { 61554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson String originalName = Thread.currentThread().getName(); 61654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(name); 61754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson try { 61854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson runnable.run(); 61954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } finally { 62054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson Thread.currentThread().setName(originalName); 62154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 62254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 62354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson }; 62454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson } 62554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson} 626