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.
5609336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson *
5709336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson * @deprecated prefer com.google.mockwebserver.MockWebServer
58b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */
5909336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson@Deprecated
60b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer {
61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    static final String ASCII = "US-ASCII";
63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
64b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
65b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
66b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
67b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
68b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
69096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private final Set<Socket> openClientSockets
70bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson            = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
71096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private boolean singleResponse;
7251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
73b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ServerSocket serverSocket;
75706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private SSLSocketFactory sslSocketFactory;
7651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ExecutorService executor;
77706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private boolean tunnelProxy;
78b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int port = -1;
80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public int getPort() {
82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (port == -1) {
83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
85b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return port;
86b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
87b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
880c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    public String getHostName() {
890c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return InetAddress.getLoopbackAddress().getHostName();
900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    }
910c2fd828abec671333b8b88281825fd27a783723Jesse Wilson
9260476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    public Proxy toProxyAddress() {
930c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9460476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    }
9560476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson
96b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a URL for connecting to this server.
98b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     *
99b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param path the request path, such as "/".
100b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
10100feece22909b7dc79fc96d666d157390b93858eJesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
102096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        return sslSocketFactory != null
1030c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                ? new URL("https://" + getHostName() + ":" + getPort() + path)
1040c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                : new URL("http://" + getHostName() + ":" + getPort() + path);
105b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * limit.
110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
112b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        this.bodyLimit = maxBodyLength;
113b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
114b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
115706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    /**
116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * Serve requests with HTTPS rather than otherwise.
117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *
118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *     negotiating TLS.
120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     */
121706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
122706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.sslSocketFactory = sslSocketFactory;
123706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.tunnelProxy = tunnelProxy;
124b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * use this to verify the request sent was as intended.
129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
131b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return requestQueue.take();
132b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
133b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
13451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    /**
13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * in practice.
13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     */
13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public int getRequestCount() {
14051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        return requestCount.get();
14151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
14251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
143706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void enqueue(MockResponse response) {
1440c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        responseQueue.add(response.clone());
145706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    }
146706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
147b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
148096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * By default, this class processes requests coming in by adding them to a
149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * is appropriate for correctness testing.
151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     *
152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * for benchmarking.
155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     */
156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        this.singleResponse = singleResponse;
158096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
159096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
160096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    /**
1610c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Equivalent to {@code play(0)}.
1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play() throws IOException {
1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        play(0);
1650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
168b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
169b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * down.
1700c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *
1710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
1730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     specific port is unavailable.
174b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
1750c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play(int port) throws IOException {
17651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        executor = Executors.newCachedThreadPool();
1770c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        serverSocket = new ServerSocket(port);
17851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        serverSocket.setReuseAddress(true);
179706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
1800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        this.port = serverSocket.getLocalPort();
181b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
182b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
18351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                try {
184096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    acceptConnections();
185096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
186b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
187096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
188b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
189096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                /*
190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * This gnarly block of code will release all sockets and
191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * all thread, even if any close fails.
192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 */
193096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    serverSocket.close();
195096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
196b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
197096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
198b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
199096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    try {
200096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.next().close();
201096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.remove();
202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    } catch (Throwable e) {
203b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
20451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    }
205096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
206096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
207096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    executor.shutdown();
208096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
209b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
210b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
211b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
212096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
213b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            private void acceptConnections() throws Exception {
214f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                do {
215b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    Socket socket;
216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    try {
217b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        socket = serverSocket.accept();
218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } catch (SocketException ignored) {
219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        continue;
220096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    }
221b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    MockResponse peek = responseQueue.peek();
222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
2234559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        responseQueue.take();
2244559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        socket.close();
225b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } else {
226b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        openClientSockets.add(socket);
227b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        serveConnection(socket);
2284559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                    }
229f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                } while (!responseQueue.isEmpty());
230096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
231096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
232b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
233b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
23451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public void shutdown() throws IOException {
23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        if (serverSocket != null) {
236096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
23751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        }
23851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
23951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
240706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private void serveConnection(final Socket raw) {
241096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
242b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
243706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            int sequenceNumber = 0;
244b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
245b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                try {
247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    processConnection();
248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                } catch (Exception e) {
249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
251b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            }
252b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
253b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void processConnection() throws Exception {
254706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                Socket socket;
255706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (sslSocketFactory != null) {
256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    if (tunnelProxy) {
257c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        createTunnel();
258b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    }
259706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = sslSocketFactory.createSocket(
260706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
2628ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.add(socket);
2638ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.remove(raw);
264706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                } else {
265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = raw;
266b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
267b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
268706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
269706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
270706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
271b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {}
272b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
273b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (sequenceNumber == 0) {
274b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
275706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
276706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
277b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                in.close();
278b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                out.close();
2798ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                socket.close();
280b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (responseQueue.isEmpty()) {
281b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    shutdown();
282b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
2838ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                openClientSockets.remove(socket);
284b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
285706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
286706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            /**
287c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
288c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * is dispatched.
289c996149b500fc4825156106554457fe2394ae087Jesse Wilson             */
290c996149b500fc4825156106554457fe2394ae087Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
291c996149b500fc4825156106554457fe2394ae087Jesse Wilson                while (true) {
292c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    MockResponse connect = responseQueue.peek();
293c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
294c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
295c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
296c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
297c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        return;
298c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
299c996149b500fc4825156106554457fe2394ae087Jesse Wilson                }
300c996149b500fc4825156106554457fe2394ae087Jesse Wilson            }
301c996149b500fc4825156106554457fe2394ae087Jesse Wilson
302c996149b500fc4825156106554457fe2394ae087Jesse Wilson            /**
303706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * Reads a request and writes its response. Returns true if a request
304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * was processed.
305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             */
306e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson            private boolean processOneRequest(InputStream in, OutputStream out, Socket socket)
307706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    throws IOException, InterruptedException {
308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                RecordedRequest request = readRequest(in, sequenceNumber);
309706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (request == null) {
310706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    return false;
311706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
312096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                MockResponse response = dispatch(request);
31351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                writeResponse(out, response);
314e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
31551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    in.close();
31651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    out.close();
317e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
318e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownInput();
319e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
320e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownOutput();
32151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                }
322706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                sequenceNumber++;
323706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                return true;
324706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            }
325096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
326b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
329b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
330b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
331b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
332b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        String request;
333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        try {
334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            request = readAsciiUntilCrlf(in);
335b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        } catch (IOException streamIsClosed) {
336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because we closed the stream
337b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        }
338b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (request.isEmpty()) {
339b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because the stream is exhausted
340b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<String> headers = new ArrayList<String>();
343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        int contentLength = -1;
344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        boolean chunked = false;
345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String header;
346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            headers.add(header);
348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            String lowercaseHeader = header.toLowerCase();
349b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunked = true;
355b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
358fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        boolean hasBody = false;
359b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (contentLength != -1) {
362fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
363b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            transfer(contentLength, in, requestBody);
364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        } else if (chunked) {
365fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
366b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            while (true) {
367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                if (chunkSize == 0) {
369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    readEmptyLine(in);
370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    break;
371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunkSizes.add(chunkSize);
373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                transfer(chunkSize, in, requestBody);
374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                readEmptyLine(in);
375b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
376b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
377b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
3780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
380adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
381fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (hasBody) {
3820c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
383fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
3840c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
385fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (!hasBody) {
3860c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must have a body: " + request);
387fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        } else {
389fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
390fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        }
391fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson
392b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
396b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
397b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a response to satisfy {@code request}.
398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
399096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
400211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson        if (responseQueue.isEmpty()) {
401b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
402b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
403096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
4040c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // to permit interactive/browser testing, ignore requests for favicons
4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            System.out.println("served " + request.getRequestLine());
4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return new MockResponse()
4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
411096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        if (singleResponse) {
412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.peek();
413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        } else {
414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestCount.incrementAndGet();
415096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestQueue.add(request);
416096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.take();
417096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }
418b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        for (String header : response.getHeaders()) {
423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(response.getBody());
427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.flush();
428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        byte[] buffer = new byte[1024];
436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (length > 0) {
437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count == -1) {
439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return;
440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write(buffer, 0, count);
442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            length -= count;
443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * {@code in} is exhausted.
449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        StringBuilder builder = new StringBuilder();
452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (true) {
453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int c = in.read();
454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else if (c == -1) {
458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else {
460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.append((char) c);
461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String line = readAsciiUntilCrlf(in);
467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (!line.isEmpty()) {
468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        private int numBytesReceived = 0;
477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived += len;
479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(int oneByte) {
482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived++;
483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count < bodyLimit) {
484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                super.write(oneByte);
485b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
486b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
487b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
488096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
489b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
490b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        return new Runnable() {
491b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
492096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                String originalName = Thread.currentThread().getName();
493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                Thread.currentThread().setName(name);
494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
495b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    runnable.run();
496096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } finally {
497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    Thread.currentThread().setName(originalName);
498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
499096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
500096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        };
501096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
502fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson}
503