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;
3624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException;
3724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate;
3854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList;
3954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator;
4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List;
417a68ed6a49c3060b235810391a82412a95f9c979jwilsonimport java.util.Map;
4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue;
4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService;
4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors;
4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level;
4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger;
5024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext;
5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket;
5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory;
5324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager;
5424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager;
5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/**
5754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence.
5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */
6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer {
6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    static final String ASCII = "US-ASCII";
6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
677a68ed6a49c3060b235810391a82412a95f9c979jwilson    /** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */
687a68ed6a49c3060b235810391a82412a95f9c979jwilson    private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>();
6954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
7054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private ServerSocket serverSocket;
7254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private SSLSocketFactory sslSocketFactory;
738ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller    private ExecutorService acceptExecutor;
748ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller    private ExecutorService requestExecutor;
7554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private boolean tunnelProxy;
767a68ed6a49c3060b235810391a82412a95f9c979jwilson    private Dispatcher dispatcher = new QueueDispatcher();
7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int port = -1;
798ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller    private int workerThreads = Integer.MAX_VALUE;
808ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller
8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getPort() {
8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (port == -1) {
8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return port;
8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public String getHostName() {
9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return InetAddress.getLocalHost().getHostName();
9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (UnknownHostException e) {
9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new AssertionError();
9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public Proxy toProxyAddress() {
9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a URL for connecting to this server.
10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param path the request path, such as "/".
10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
1067a68ed6a49c3060b235810391a82412a95f9c979jwilson    public URL getUrl(String path) {
1077a68ed6a49c3060b235810391a82412a95f9c979jwilson        try {
1087a68ed6a49c3060b235810391a82412a95f9c979jwilson            return sslSocketFactory != null
1097a68ed6a49c3060b235810391a82412a95f9c979jwilson                    ? new URL("https://" + getHostName() + ":" + getPort() + path)
1107a68ed6a49c3060b235810391a82412a95f9c979jwilson                    : new URL("http://" + getHostName() + ":" + getPort() + path);
1117a68ed6a49c3060b235810391a82412a95f9c979jwilson        } catch (MalformedURLException e) {
1127a68ed6a49c3060b235810391a82412a95f9c979jwilson            throw new AssertionError(e);
1137a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
11654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
11718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * Returns a cookie domain for this server. This returns the server's
11818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * non-loopback host name if it is known. Otherwise this returns ".local"
11918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * for this server's loopback name.
12018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     */
12118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    public String getCookieDomain() {
12218819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        String hostName = getHostName();
12318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        return hostName.contains(".") ? hostName : ".local";
12418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    }
12518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson
1268ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller    public void setWorkerThreads(int threads) {
1278ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        this.workerThreads = threads;
1288ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller    }
1298ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller
13018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    /**
13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * limit.
13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.bodyLimit = maxBodyLength;
13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Serve requests with HTTPS rather than otherwise.
14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     negotiating TLS.
14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.sslSocketFactory = sslSocketFactory;
14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.tunnelProxy = tunnelProxy;
14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * use this to verify the request sent was as intended.
15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestQueue.take();
15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * in practice.
16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getRequestCount() {
16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestCount.get();
16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
1677a68ed6a49c3060b235810391a82412a95f9c979jwilson     * Scripts {@code response} to be returned to a request made in sequence.
1687a68ed6a49c3060b235810391a82412a95f9c979jwilson     * The first request is served by the first enqueued response; the second
1697a68ed6a49c3060b235810391a82412a95f9c979jwilson     * request by the second enqueued response; and so on.
17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
1717a68ed6a49c3060b235810391a82412a95f9c979jwilson     * @throws ClassCastException if the default dispatcher has been replaced
1727a68ed6a49c3060b235810391a82412a95f9c979jwilson     *     with {@link #setDispatcher(Dispatcher)}.
17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
1747a68ed6a49c3060b235810391a82412a95f9c979jwilson    public void enqueue(MockResponse response) {
1757a68ed6a49c3060b235810391a82412a95f9c979jwilson        ((QueueDispatcher) dispatcher).enqueueResponse(response.clone());
17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Equivalent to {@code play(0)}.
18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play() throws IOException {
18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        play(0);
18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
18754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * down.
18854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     specific port is unavailable.
19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play(int port) throws IOException {
1948ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        if (acceptExecutor != null) {
1956ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom            throw new IllegalStateException("play() already called");
1966ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom        }
1978ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        // The acceptExecutor handles the Socket.accept() and hands each request off to the
1988ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        // requestExecutor. It also handles shutdown.
1998ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        acceptExecutor = Executors.newSingleThreadExecutor();
2008ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        // The requestExecutor has a fixed number of worker threads. In order to get strict
2018ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        // guarantees that requests are handled in the order in which they are accepted
2028ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        // workerThreads should be set to 1.
2038ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        requestExecutor = Executors.newFixedThreadPool(workerThreads);
20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket = new ServerSocket(port);
20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket.setReuseAddress(true);
20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.port = serverSocket.getLocalPort();
2088ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        acceptExecutor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    acceptConnections();
21254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                /*
21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * This gnarly block of code will release all sockets and
21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * all thread, even if any close fails.
21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 */
22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    serverSocket.close();
22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
2257a68ed6a49c3060b235810391a82412a95f9c979jwilson                for (Iterator<Socket> s = openClientSockets.keySet().iterator(); s.hasNext(); ) {
22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.next().close();
22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.remove();
22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (Throwable e) {
23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
2348ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                    acceptExecutor.shutdown();
2358ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                } catch (Throwable e) {
2368ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                    logger.log(Level.WARNING, "MockWebServer acceptExecutor shutdown failed", e);
2378ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                }
2388ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                try {
2398ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                    requestExecutor.shutdown();
24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
2418ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                    logger.log(Level.WARNING, "MockWebServer requestExecutor shutdown failed", e);
24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void acceptConnections() throws Exception {
2467a68ed6a49c3060b235810391a82412a95f9c979jwilson                while (true) {
24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Socket socket;
24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket = serverSocket.accept();
2507a68ed6a49c3060b235810391a82412a95f9c979jwilson                    } catch (SocketException e) {
2517a68ed6a49c3060b235810391a82412a95f9c979jwilson                        return;
25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2537a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
2547a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == DISCONNECT_AT_START) {
2557a68ed6a49c3060b235810391a82412a95f9c979jwilson                        dispatchBookkeepingRequest(0, socket);
25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket.close();
25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } else {
2587a68ed6a49c3060b235810391a82412a95f9c979jwilson                        openClientSockets.put(socket, true);
25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        serveConnection(socket);
26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2617a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void shutdown() throws IOException {
26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (serverSocket != null) {
26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void serveConnection(final Socket raw) {
27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
2748ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller        requestExecutor.execute(namedRunnable(name, new Runnable() {
27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int sequenceNumber = 0;
27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
27854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    processConnection();
28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Exception e) {
28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void processConnection() throws Exception {
28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Socket socket;
28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sslSocketFactory != null) {
28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (tunnelProxy) {
28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        createTunnel();
29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2917a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
2927a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == FAIL_HANDSHAKE) {
2937a68ed6a49c3060b235810391a82412a95f9c979jwilson                        dispatchBookkeepingRequest(sequenceNumber, raw);
29424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        processHandshakeFailure(raw, sequenceNumber++);
29524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        return;
29624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    }
29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = sslSocketFactory.createSocket(
29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
3007a68ed6a49c3060b235810391a82412a95f9c979jwilson                    openClientSockets.put(socket, true);
30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.remove(raw);
30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else {
30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = raw;
30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
3097a68ed6a49c3060b235810391a82412a95f9c979jwilson                while (processOneRequest(socket, in, out)) {
3107a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
31154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sequenceNumber == 0) {
31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
31654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                in.close();
31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                out.close();
31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                socket.close();
31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                openClientSockets.remove(socket);
32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * is dispatched.
32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                while (true) {
3287a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
32924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) {
33054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
33154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
3327a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        return;
33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Reads a request and writes its response. Returns true if a request
34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * was processed.
34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
34224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            private boolean processOneRequest(Socket socket, InputStream in, OutputStream out)
34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    throws IOException, InterruptedException {
34424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                RecordedRequest request = readRequest(socket, in, sequenceNumber);
34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (request == null) {
34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    return false;
34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
3487a68ed6a49c3060b235810391a82412a95f9c979jwilson                requestCount.incrementAndGet();
3497a68ed6a49c3060b235810391a82412a95f9c979jwilson                requestQueue.add(request);
3507a68ed6a49c3060b235810391a82412a95f9c979jwilson                MockResponse response = dispatcher.dispatch(request);
3513088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AFTER_READING_REQUEST) {
3523088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath                  logger.info("Received request: " + request + " and disconnected without responding");
3533088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath                  return false;
3543088bcc1d22f3e163e5e7ecbc997daadfdaaeec3Narayan Kamath                }
3557a68ed6a49c3060b235810391a82412a95f9c979jwilson                writeResponse(out, response);
3568ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller
3578ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // For socket policies that poison the socket after the response is written:
3588ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // The client has received the response and will no longer be blocked after
3598ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // writeResponse() has returned. A client can then re-use the connection before
3608ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // the socket is poisoned (i.e. keep-alive / connection pooling). The second
3618ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // request/response may fail at the beginning, middle, end, or even succeed
3628ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // depending on scheduling. Delays can be required in tests to improve the chances
3638ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // of sockets being in a known state when subsequent requests are made.
3648ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                //
3658ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // For SHUTDOWN_OUTPUT_AT_END the client may detect a problem with its input socket
3668ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // after the request has been made but before the server has chosen a response.
3678ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // For clients that perform retries, this can cause the client to issue a retry
3688ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // request. The retry handler may call dispatcher.dispatch(request) before the
3698ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // initial, failed request handler does and cause non-obvious response ordering.
3708ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // Setting workerThreads = 1 ensures that the dispatcher is called for requests in
3718ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller                // the order they are received.
3728ea0a457ae604fe61aa119e05aad3ac71f042b90Neil Fuller
37354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
37454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    in.close();
37554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    out.close();
37654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
37754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownInput();
37854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
37954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownOutput();
38054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
3813b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                logger.info("Received request: " + request + " and responded: " + response);
38254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                sequenceNumber++;
38354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return true;
38454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
38554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
38654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
38824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception {
38924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        X509TrustManager untrusted = new X509TrustManager() {
39024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
39124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    throws CertificateException {
39224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new CertificateException();
39324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
39424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {
39524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new AssertionError();
39624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
39724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public X509Certificate[] getAcceptedIssuers() {
39824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new AssertionError();
39924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
40024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        };
40124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLContext context = SSLContext.getInstance("TLS");
40224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        context.init(null, new TrustManager[] { untrusted }, new java.security.SecureRandom());
40324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLSocketFactory sslSocketFactory = context.getSocketFactory();
40424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(
40524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
40624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        try {
40724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            socket.startHandshake(); // we're testing a handshake failure
40824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            throw new AssertionError();
40924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        } catch (IOException expected) {
41024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        }
41124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        socket.close();
4127a68ed6a49c3060b235810391a82412a95f9c979jwilson    }
4137a68ed6a49c3060b235810391a82412a95f9c979jwilson
4147a68ed6a49c3060b235810391a82412a95f9c979jwilson    private void dispatchBookkeepingRequest(int sequenceNumber, Socket socket) throws InterruptedException {
41524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        requestCount.incrementAndGet();
416a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath        RecordedRequest request = new RecordedRequest(null, null, null, -1, null, sequenceNumber,
417a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath                socket);
418a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath        dispatcher.dispatch(request);
419a4a53d9c9bf86165a5e2bd9ce4bcb834f09d4c37Narayan Kamath        requestQueue.add(request);
42024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    }
42124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson
42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
42524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private RecordedRequest readRequest(Socket socket, InputStream in, int sequenceNumber)
42624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            throws IOException {
42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String request;
42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            request = readAsciiUntilCrlf(in);
43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (IOException streamIsClosed) {
43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because we closed the stream
43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
4337a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (request.length() == 0) {
43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because the stream is exhausted
43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<String> headers = new ArrayList<String>();
43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        int contentLength = -1;
43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean chunked = false;
44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String header;
4417a68ed6a49c3060b235810391a82412a95f9c979jwilson        while ((header = readAsciiUntilCrlf(in)).length() != 0) {
44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            headers.add(header);
44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            String lowercaseHeader = header.toLowerCase();
44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
44754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
44954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunked = true;
45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
45254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean hasBody = false;
45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
45554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (contentLength != -1) {
45754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
45854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            transfer(contentLength, in, requestBody);
45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (chunked) {
46054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
46154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            while (true) {
46254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (chunkSize == 0) {
46454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    readEmptyLine(in);
46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    break;
46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunkSizes.add(chunkSize);
46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                transfer(chunkSize, in, requestBody);
46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                readEmptyLine(in);
47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
47454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
4757a68ed6a49c3060b235810391a82412a95f9c979jwilson                || request.startsWith("TRACE ") || request.startsWith("CONNECT ")) {
47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (hasBody) {
47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
4797a68ed6a49c3060b235810391a82412a95f9c979jwilson        } else if (!request.startsWith("POST ") && !request.startsWith("PUT ")) {
48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
48154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
48254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
48354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
48424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber, socket);
48554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
48654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
4877a68ed6a49c3060b235810391a82412a95f9c979jwilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
4887a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
4897a68ed6a49c3060b235810391a82412a95f9c979jwilson        for (String header : response.getHeaders()) {
4907a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.write((header + "\r\n").getBytes(ASCII));
49154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
4927a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.write(("\r\n").getBytes(ASCII));
4937a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.flush();
49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
4957a68ed6a49c3060b235810391a82412a95f9c979jwilson        final InputStream in = response.getBodyStream();
4967a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (in == null) {
4977a68ed6a49c3060b235810391a82412a95f9c979jwilson            return;
49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
4997a68ed6a49c3060b235810391a82412a95f9c979jwilson        final int bytesPerSecond = response.getBytesPerSecond();
50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
5017a68ed6a49c3060b235810391a82412a95f9c979jwilson        // Stream data in MTU-sized increments
5027a68ed6a49c3060b235810391a82412a95f9c979jwilson        final byte[] buffer = new byte[1452];
5037a68ed6a49c3060b235810391a82412a95f9c979jwilson        final long delayMs;
5047a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (bytesPerSecond == Integer.MAX_VALUE) {
5057a68ed6a49c3060b235810391a82412a95f9c979jwilson            delayMs = 0;
50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
5077a68ed6a49c3060b235810391a82412a95f9c979jwilson            delayMs = (1000 * buffer.length) / bytesPerSecond;
5087a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
5097a68ed6a49c3060b235810391a82412a95f9c979jwilson
5107a68ed6a49c3060b235810391a82412a95f9c979jwilson        int read;
5117a68ed6a49c3060b235810391a82412a95f9c979jwilson        long sinceDelay = 0;
5127a68ed6a49c3060b235810391a82412a95f9c979jwilson        while ((read = in.read(buffer)) != -1) {
5137a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.write(buffer, 0, read);
5147a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.flush();
5157a68ed6a49c3060b235810391a82412a95f9c979jwilson
5167a68ed6a49c3060b235810391a82412a95f9c979jwilson            sinceDelay += read;
5177a68ed6a49c3060b235810391a82412a95f9c979jwilson            if (sinceDelay >= buffer.length && delayMs > 0) {
5187a68ed6a49c3060b235810391a82412a95f9c979jwilson                sinceDelay %= buffer.length;
5197a68ed6a49c3060b235810391a82412a95f9c979jwilson                try {
5207a68ed6a49c3060b235810391a82412a95f9c979jwilson                    Thread.sleep(delayMs);
5217a68ed6a49c3060b235810391a82412a95f9c979jwilson                } catch (InterruptedException e) {
5227a68ed6a49c3060b235810391a82412a95f9c979jwilson                    throw new AssertionError();
5237a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
5247a68ed6a49c3060b235810391a82412a95f9c979jwilson            }
52554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
52654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
52854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
52954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
53054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
53154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
53254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
53354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        byte[] buffer = new byte[1024];
53454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (length > 0) {
53554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
53654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count == -1) {
53754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return;
53854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
53954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write(buffer, 0, count);
54054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            length -= count;
54154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
54254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
54354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
54454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
54554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
54654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * {@code in} is exhausted.
54754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
54854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
54954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        StringBuilder builder = new StringBuilder();
55054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (true) {
55154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int c = in.read();
55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else if (c == -1) {
55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else {
55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.append((char) c);
55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String line = readAsciiUntilCrlf(in);
5657a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (line.length() != 0) {
56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
5717a68ed6a49c3060b235810391a82412a95f9c979jwilson     * Sets the dispatcher used to match incoming requests to mock responses.
5727a68ed6a49c3060b235810391a82412a95f9c979jwilson     * The default dispatcher simply serves a fixed sequence of responses from
5737a68ed6a49c3060b235810391a82412a95f9c979jwilson     * a {@link #enqueue(MockResponse) queue}; custom dispatchers can vary the
5747a68ed6a49c3060b235810391a82412a95f9c979jwilson     * response based on timing or the content of the request.
5757a68ed6a49c3060b235810391a82412a95f9c979jwilson     */
5767a68ed6a49c3060b235810391a82412a95f9c979jwilson    public void setDispatcher(Dispatcher dispatcher) {
5777a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (dispatcher == null) {
5787a68ed6a49c3060b235810391a82412a95f9c979jwilson            throw new NullPointerException();
5797a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
5807a68ed6a49c3060b235810391a82412a95f9c979jwilson        this.dispatcher = dispatcher;
5817a68ed6a49c3060b235810391a82412a95f9c979jwilson    }
5827a68ed6a49c3060b235810391a82412a95f9c979jwilson
5837a68ed6a49c3060b235810391a82412a95f9c979jwilson    /**
58454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
58554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
58654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
58754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        private int numBytesReceived = 0;
58854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
58954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived += len;
59054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
59154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
59254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(int oneByte) {
59354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived++;
59454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count < bodyLimit) {
59554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                super.write(oneByte);
59654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
59754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
59854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
59954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
60054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
60154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Runnable() {
60254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
60354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                String originalName = Thread.currentThread().getName();
60454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Thread.currentThread().setName(name);
60554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
60654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    runnable.run();
60754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } finally {
60854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Thread.currentThread().setName(originalName);
60954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
61054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
61154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        };
61254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
61354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson}
614