MockWebServer.java revision a0de05e00f3d51df7461cb2048c50bb30ef9112a
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;
250c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilsonimport java.net.HttpURLConnection;
26a0de05e00f3d51df7461cb2048c50bb30ef9112aJesse Wilsonimport java.net.InetAddress;
2760476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.InetSocketAddress;
28b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.MalformedURLException;
2960476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilsonimport java.net.Proxy;
30b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.ServerSocket;
31b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.Socket;
32b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.net.SocketException;
33b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.net.URL;
3400feece22909b7dc79fc96d666d157390b93858eJesse Wilsonimport java.net.UnknownHostException;
35b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.ArrayList;
36096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Collections;
37096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Iterator;
38b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.List;
39096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Set;
40b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.BlockingQueue;
41bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
42b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.ExecutorService;
43b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.Executors;
44b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque;
45b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
4651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
47b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Level;
48b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Logger;
49706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocket;
50706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocketFactory;
51e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_START;
52b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/**
54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * replays them upon request in sequence.
56b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */
57b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer {
58b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    static final String ASCII = "US-ASCII";
60b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
61b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
65b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
66096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private final Set<Socket> openClientSockets
67bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson            = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
68096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private boolean singleResponse;
6951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
70b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ServerSocket serverSocket;
72706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private SSLSocketFactory sslSocketFactory;
7351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ExecutorService executor;
74706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private boolean tunnelProxy;
75b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
76b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int port = -1;
77b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
78b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public int getPort() {
79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (port == -1) {
80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return port;
83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
850c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    public String getHostName() {
860c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return InetAddress.getLoopbackAddress().getHostName();
870c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    }
880c2fd828abec671333b8b88281825fd27a783723Jesse Wilson
8960476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    public Proxy toProxyAddress() {
900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9160476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    }
9260476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson
93b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
94b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a URL for connecting to this server.
95b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     *
96b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param path the request path, such as "/".
97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
9800feece22909b7dc79fc96d666d157390b93858eJesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
99096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        return sslSocketFactory != null
1000c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                ? new URL("https://" + getHostName() + ":" + getPort() + path)
1010c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                : new URL("http://" + getHostName() + ":" + getPort() + path);
102b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
103b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
104b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * limit.
107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        this.bodyLimit = maxBodyLength;
110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
112706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    /**
113706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * Serve requests with HTTPS rather than otherwise.
114706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *
115706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *     negotiating TLS.
117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     */
118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.sslSocketFactory = sslSocketFactory;
120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.tunnelProxy = tunnelProxy;
121b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
122b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
123b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
124b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * use this to verify the request sent was as intended.
126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return requestQueue.take();
129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
13151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    /**
13251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
13351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
13451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * in practice.
13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     */
13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public int getRequestCount() {
13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        return requestCount.get();
13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
140706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void enqueue(MockResponse response) {
1410c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        responseQueue.add(response.clone());
142706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    }
143706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
144b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
145096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * By default, this class processes requests coming in by adding them to a
146096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
147096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * is appropriate for correctness testing.
148096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     *
149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * for benchmarking.
152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     */
153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        this.singleResponse = singleResponse;
155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    /**
1580c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Equivalent to {@code play(0)}.
1590c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1600c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play() throws IOException {
1610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        play(0);
1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
165b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
166b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * down.
1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *
1680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
1690c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
1700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     specific port is unavailable.
171b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play(int port) throws IOException {
17351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        executor = Executors.newCachedThreadPool();
1740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        serverSocket = new ServerSocket(port);
17551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        serverSocket.setReuseAddress(true);
176706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
1770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        this.port = serverSocket.getLocalPort();
178b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
179b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
18051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                try {
181096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    acceptConnections();
182096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
183b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
184096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
185b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
186096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                /*
187096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * This gnarly block of code will release all sockets and
188096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * all thread, even if any close fails.
189096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 */
190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    serverSocket.close();
192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
193b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
195b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
196096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    try {
197096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.next().close();
198096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.remove();
199096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    } catch (Throwable e) {
200b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
20151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    }
202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
203096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
204096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    executor.shutdown();
205096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
206b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
207b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
208b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
209096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
210b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            private void acceptConnections() throws Exception {
211f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                do {
212b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    Socket socket;
213b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    try {
214b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        socket = serverSocket.accept();
215b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } catch (SocketException ignored) {
216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        continue;
217096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    }
218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    MockResponse peek = responseQueue.peek();
219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
2204559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        responseQueue.take();
2214559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        socket.close();
222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } else {
223b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        openClientSockets.add(socket);
224b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        serveConnection(socket);
2254559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                    }
226f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                } while (!responseQueue.isEmpty());
227096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
228096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
229b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
230b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
23151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public void shutdown() throws IOException {
23251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        if (serverSocket != null) {
233096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
23451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        }
23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
23651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
237706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private void serveConnection(final Socket raw) {
238096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
239b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
240706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            int sequenceNumber = 0;
241b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
242b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
243b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                try {
244b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    processConnection();
245b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                } catch (Exception e) {
246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            }
249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void processConnection() throws Exception {
251706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                Socket socket;
252706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (sslSocketFactory != null) {
253706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    if (tunnelProxy) {
254c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        createTunnel();
255b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    }
256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = sslSocketFactory.createSocket(
257706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
258706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
2598ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.add(socket);
2608ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.remove(raw);
261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                } else {
262706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = raw;
263b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
264b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
266706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
267706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
268b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {}
269b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
270b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (sequenceNumber == 0) {
271b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
272706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
273706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
274b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                in.close();
275b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                out.close();
2768ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                socket.close();
277b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (responseQueue.isEmpty()) {
278b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    shutdown();
279b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
2808ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                openClientSockets.remove(socket);
281b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
282706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
283706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            /**
284c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
285c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * is dispatched.
286c996149b500fc4825156106554457fe2394ae087Jesse Wilson             */
287c996149b500fc4825156106554457fe2394ae087Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
288c996149b500fc4825156106554457fe2394ae087Jesse Wilson                while (true) {
289c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    MockResponse connect = responseQueue.peek();
290c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
291c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
292c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
293c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
294c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        return;
295c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
296c996149b500fc4825156106554457fe2394ae087Jesse Wilson                }
297c996149b500fc4825156106554457fe2394ae087Jesse Wilson            }
298c996149b500fc4825156106554457fe2394ae087Jesse Wilson
299c996149b500fc4825156106554457fe2394ae087Jesse Wilson            /**
300706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * Reads a request and writes its response. Returns true if a request
301706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * was processed.
302706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             */
303e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson            private boolean processOneRequest(InputStream in, OutputStream out, Socket socket)
304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    throws IOException, InterruptedException {
305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                RecordedRequest request = readRequest(in, sequenceNumber);
306706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (request == null) {
307706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    return false;
308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
309096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                MockResponse response = dispatch(request);
31051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                writeResponse(out, response);
311e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
31251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    in.close();
31351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    out.close();
314e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
315e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownInput();
316e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
317e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownOutput();
31851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                }
319706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                sequenceNumber++;
320706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                return true;
321706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            }
322096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
323b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
324b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
325b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
326b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
329b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        String request;
330b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        try {
331b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            request = readAsciiUntilCrlf(in);
332b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        } catch (IOException streamIsClosed) {
333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because we closed the stream
334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        }
335b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (request.isEmpty()) {
336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because the stream is exhausted
337b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
338b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
339b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<String> headers = new ArrayList<String>();
340b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        int contentLength = -1;
341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        boolean chunked = false;
342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String header;
343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            headers.add(header);
345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            String lowercaseHeader = header.toLowerCase();
346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
349b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunked = true;
352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
355fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        boolean hasBody = false;
356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
358b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (contentLength != -1) {
359fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            transfer(contentLength, in, requestBody);
361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        } else if (chunked) {
362fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
363b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            while (true) {
364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
365b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                if (chunkSize == 0) {
366b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    readEmptyLine(in);
367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    break;
368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunkSizes.add(chunkSize);
370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                transfer(chunkSize, in, requestBody);
371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                readEmptyLine(in);
372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
3750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
3760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
377adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
378fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (hasBody) {
3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
380fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
3810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
382fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (!hasBody) {
3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must have a body: " + request);
384fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
385fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        } else {
386fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
387fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        }
388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson
389b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
390b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
391b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
392b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a response to satisfy {@code request}.
395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
396096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
397211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson        if (responseQueue.isEmpty()) {
398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
399b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
400096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
4010c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // to permit interactive/browser testing, ignore requests for favicons
4020c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
4030c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            System.out.println("served " + request.getRequestLine());
4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return new MockResponse()
4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
408096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        if (singleResponse) {
409096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.peek();
410096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        } else {
411096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestCount.incrementAndGet();
412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestQueue.add(request);
413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.take();
414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }
415b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
416b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
417b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
418b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        for (String header : response.getHeaders()) {
420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(response.getBody());
424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.flush();
425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        byte[] buffer = new byte[1024];
433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (length > 0) {
434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count == -1) {
436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return;
437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write(buffer, 0, count);
439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            length -= count;
440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * {@code in} is exhausted.
446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        StringBuilder builder = new StringBuilder();
449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (true) {
450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int c = in.read();
451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else if (c == -1) {
455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else {
457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.append((char) c);
458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String line = readAsciiUntilCrlf(in);
464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (!line.isEmpty()) {
465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        private int numBytesReceived = 0;
474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived += len;
476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(int oneByte) {
479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived++;
480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count < bodyLimit) {
481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                super.write(oneByte);
482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
485096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
486b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
487b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        return new Runnable() {
488b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
489096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                String originalName = Thread.currentThread().getName();
490096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                Thread.currentThread().setName(name);
491096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
492b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    runnable.run();
493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } finally {
494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    Thread.currentThread().setName(originalName);
495096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
496096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        };
498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
499fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson}
500