MockWebServer.java revision b7f4d6c3968c372767b2510f38a3d506067aced6
1b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/*
2b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Copyright (C) 2010 The Android Open Source Project
3b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson *
4b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
5b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * you may not use this file except in compliance with the License.
6b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * You may obtain a copy of the License at
7b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson *
8b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
9b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson *
10b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * Unless required by applicable law or agreed to in writing, software
11b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
12b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * See the License for the specific language governing permissions and
14b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * limitations under the License.
15b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */
16b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
17b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpackage tests.http;
18b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
19b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.BufferedInputStream;
20b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.BufferedOutputStream;
21b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.ByteArrayOutputStream;
22b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.IOException;
23b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.InputStream;
24b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.io.OutputStream;
2500feece22909b7dc79fc96d666d157390b93858eJesse Wilsonimport java.net.InetAddress;
2660476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.InetSocketAddress;
27b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.MalformedURLException;
2860476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.Proxy;
29b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.ServerSocket;
30b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.Socket;
31b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.net.SocketException;
32b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.URL;
3300feece22909b7dc79fc96d666d157390b93858eJesse Wilsonimport java.net.UnknownHostException;
34b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.ArrayList;
35096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Collections;
36096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.HashSet;
37096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Iterator;
38b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.List;
39096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Set;
40b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.BlockingQueue;
41b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.ExecutorService;
42b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.Executors;
43b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque;
44b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
4551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
46b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Level;
47b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Logger;
48706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocket;
49706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocketFactory;
50e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_START;
51b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
52b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/**
53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * replays them upon request in sequence.
55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */
56b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer {
57b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
58b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    static final String ASCII = "US-ASCII";
59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
60b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
65096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private final Set<Socket> openClientSockets
66096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            = Collections.synchronizedSet(new HashSet<Socket>());
67096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private boolean singleResponse;
6851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
69b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ServerSocket serverSocket;
71706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private SSLSocketFactory sslSocketFactory;
7251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ExecutorService executor;
73706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private boolean tunnelProxy;
74b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
75b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int port = -1;
76b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
77b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public int getPort() {
78b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (port == -1) {
79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return port;
82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
8460476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    public Proxy toProxyAddress() {
8560476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress("localhost", getPort()));
8660476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    }
8760476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson
88b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
89b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a URL for connecting to this server.
90b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     *
91b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param path the request path, such as "/".
92b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
9300feece22909b7dc79fc96d666d157390b93858eJesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
9400feece22909b7dc79fc96d666d157390b93858eJesse Wilson        String host = InetAddress.getLocalHost().getHostName();
95096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        return sslSocketFactory != null
9600feece22909b7dc79fc96d666d157390b93858eJesse Wilson                ? new URL("https://" + host + ":" + getPort() + path)
9700feece22909b7dc79fc96d666d157390b93858eJesse Wilson                : new URL("http://" + host + ":" + getPort() + path);
98b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
99b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
100b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
101b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
102b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * limit.
103b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
104b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        this.bodyLimit = maxBodyLength;
106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
108706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    /**
109706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * Serve requests with HTTPS rather than otherwise.
110706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *
111706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
112706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *     negotiating TLS.
113706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     */
114706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
115706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.sslSocketFactory = sslSocketFactory;
116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.tunnelProxy = tunnelProxy;
117b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
118b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
119b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
120b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
121b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * use this to verify the request sent was as intended.
122b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
123b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
124b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return requestQueue.take();
125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
12751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    /**
12851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
12951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
13051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * in practice.
13151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     */
13251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public int getRequestCount() {
13351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        return requestCount.get();
13451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
136706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void enqueue(MockResponse response) {
137706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        responseQueue.add(response);
138706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    }
139706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
140b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
141096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * By default, this class processes requests coming in by adding them to a
142096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
143096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * is appropriate for correctness testing.
144096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     *
145096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
146096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
147096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * for benchmarking.
148096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     */
149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        this.singleResponse = singleResponse;
151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    /**
154b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
155b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * down.
156b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
157b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public void play() throws IOException {
15851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        executor = Executors.newCachedThreadPool();
15951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        serverSocket = new ServerSocket(0);
16051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        serverSocket.setReuseAddress(true);
161706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
16251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        port = serverSocket.getLocalPort();
163b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
164b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
16551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                try {
166096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    acceptConnections();
167096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
168b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
169096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
170b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
171096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                /*
172096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * This gnarly block of code will release all sockets and
173096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * all thread, even if any close fails.
174096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 */
175096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
176096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    serverSocket.close();
177096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
178b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
179096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
180b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
181096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    try {
182096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.next().close();
183096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.remove();
184096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    } catch (Throwable e) {
185b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
18651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    }
187096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
188096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
189096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    executor.shutdown();
190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
191b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
192b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
193b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
195b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            private void acceptConnections() throws Exception {
196b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                while (!responseQueue.isEmpty()) {
197b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    Socket socket;
198b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    try {
199b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        socket = serverSocket.accept();
200b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } catch (SocketException ignored) {
201b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        continue;
202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    }
203b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    MockResponse peek = responseQueue.peek();
204b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
2054559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        responseQueue.take();
2064559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        socket.close();
207b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } else {
208b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        openClientSockets.add(socket);
209b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        serveConnection(socket);
2104559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                    }
211096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
212096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
213096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
214b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
215b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
21651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public void shutdown() throws IOException {
21751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        if (serverSocket != null) {
218096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
21951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        }
22051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
22151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
222706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private void serveConnection(final Socket raw) {
223096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
224b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
225706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            int sequenceNumber = 0;
226b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
227b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
228b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                try {
229b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    processConnection();
230b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                } catch (Exception e) {
231b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
232b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
233b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            }
234b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
235b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void processConnection() throws Exception {
236706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                Socket socket;
237706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (sslSocketFactory != null) {
238706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    if (tunnelProxy) {
239e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                        if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
240706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                            throw new IllegalStateException("Tunnel without any CONNECT!");
241b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                        }
242b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    }
243706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = sslSocketFactory.createSocket(
244706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
245706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
2468ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.add(socket);
2478ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.remove(raw);
248706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                } else {
249706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = raw;
250b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
251b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
252706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
253706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
254706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
255b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {}
256b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
257b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (sequenceNumber == 0) {
258b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
259706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
260706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
261b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                in.close();
262b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                out.close();
2638ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                socket.close();
264b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (responseQueue.isEmpty()) {
265b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    shutdown();
266b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
2678ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                openClientSockets.remove(socket);
268b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
269706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
270706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            /**
271706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * Reads a request and writes its response. Returns true if a request
272706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * was processed.
273706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             */
274e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson            private boolean processOneRequest(InputStream in, OutputStream out, Socket socket)
275706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    throws IOException, InterruptedException {
276706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                RecordedRequest request = readRequest(in, sequenceNumber);
277706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (request == null) {
278706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    return false;
279706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
280096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                MockResponse response = dispatch(request);
28151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                writeResponse(out, response);
282e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
28351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    in.close();
28451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    out.close();
285e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
286e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownInput();
287e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
288e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownOutput();
28951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                }
290706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                sequenceNumber++;
291706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                return true;
292706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            }
293096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
294b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
295b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
296b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
297b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
298b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
299b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
300b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        String request;
301b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        try {
302b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            request = readAsciiUntilCrlf(in);
303b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        } catch (IOException streamIsClosed) {
304b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because we closed the stream
305b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        }
306b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (request.isEmpty()) {
307b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because the stream is exhausted
308b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
309b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
310b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<String> headers = new ArrayList<String>();
311b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        int contentLength = -1;
312b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        boolean chunked = false;
313b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String header;
314b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
315b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            headers.add(header);
316b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            String lowercaseHeader = header.toLowerCase();
317b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
318b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
319b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
320b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
321b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
322b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunked = true;
323b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
324b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
325b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
326fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        boolean hasBody = false;
327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
329b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (contentLength != -1) {
330fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
331b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            transfer(contentLength, in, requestBody);
332b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        } else if (chunked) {
333fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
334b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            while (true) {
335b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
336b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                if (chunkSize == 0) {
337b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    readEmptyLine(in);
338b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    break;
339b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
340b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunkSizes.add(chunkSize);
341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                transfer(chunkSize, in, requestBody);
342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                readEmptyLine(in);
343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
346706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        if (request.startsWith("GET ") || request.startsWith("CONNECT ")) {
347fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (hasBody) {
348fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson                throw new IllegalArgumentException("GET requests should not have a body!");
349fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
350fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        } else if (request.startsWith("POST ")) {
351fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (!hasBody) {
352fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson                throw new IllegalArgumentException("POST requests must have a body!");
353fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
354fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        } else {
355fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
356fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        }
357fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson
358b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
359b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
362b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
363b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a response to satisfy {@code request}.
364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
365096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
366211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson        if (responseQueue.isEmpty()) {
367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
369096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
370096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        if (singleResponse) {
371096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.peek();
372096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        } else {
373096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestCount.incrementAndGet();
374096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestQueue.add(request);
375096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.take();
376096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }
377b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
378b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
379b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
380b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
381b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        for (String header : response.getHeaders()) {
382b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
383b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
384b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
385b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(response.getBody());
386b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.flush();
387b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
388b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
389b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
390b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
391b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
392b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        byte[] buffer = new byte[1024];
395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (length > 0) {
396b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
397b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count == -1) {
398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return;
399b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
400b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write(buffer, 0, count);
401b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            length -= count;
402b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
403b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
404b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
405b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
406b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
407b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * {@code in} is exhausted.
408b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
409b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
410b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        StringBuilder builder = new StringBuilder();
411b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (true) {
412b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int c = in.read();
413b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
414b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
415b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
416b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else if (c == -1) {
417b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
418b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else {
419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.append((char) c);
420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String line = readAsciiUntilCrlf(in);
426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (!line.isEmpty()) {
427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        private int numBytesReceived = 0;
436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived += len;
438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(int oneByte) {
441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived++;
442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count < bodyLimit) {
443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                super.write(oneByte);
444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
447096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
448b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
449b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        return new Runnable() {
450b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
451096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                String originalName = Thread.currentThread().getName();
452096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                Thread.currentThread().setName(name);
453096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
454b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    runnable.run();
455096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } finally {
456096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    Thread.currentThread().setName(originalName);
457096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
458096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
459096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        };
460096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
461fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson}
462