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;
39b274574b7e2681f4411852af9857b4540bf75841Elliott Hughesimport java.util.Locale;
40096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilsonimport java.util.Set;
41b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.BlockingQueue;
42bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
43b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.ExecutorService;
44b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.Executors;
45b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque;
46b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
4751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
48b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Level;
49b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilsonimport java.util.logging.Logger;
50706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocket;
51706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilsonimport javax.net.ssl.SSLSocketFactory;
52e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilsonimport static tests.http.SocketPolicy.DISCONNECT_AT_START;
53b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
54b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson/**
55b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
56b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson * replays them upon request in sequence.
5709336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson *
5899b4489d0555c6e0e5df941cbfad4cf250c8f0b8Elliott Hughes * @deprecated Use {@code com.google.mockwebserver.MockWebServer} instead.
59b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson */
6009336c914b4fc813e493acc82469b9ad89fd8694Jesse Wilson@Deprecated
61b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilsonpublic final class MockWebServer {
62b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
63b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    static final String ASCII = "US-ASCII";
64b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
65b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
66b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
67b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
68b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
69b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
70096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private final Set<Socket> openClientSockets
71bc4c79c6a2059003f695f7ad204de36700e8d701Jesse Wilson            = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
72096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private boolean singleResponse;
7351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
74b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ServerSocket serverSocket;
76706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private SSLSocketFactory sslSocketFactory;
7751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    private ExecutorService executor;
78706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private boolean tunnelProxy;
79b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
80b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private int port = -1;
81b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
82b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public int getPort() {
83b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (port == -1) {
84b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
85b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
86b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return port;
87b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
88b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
890c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    public String getHostName() {
900c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return InetAddress.getLoopbackAddress().getHostName();
910c2fd828abec671333b8b88281825fd27a783723Jesse Wilson    }
920c2fd828abec671333b8b88281825fd27a783723Jesse Wilson
9360476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    public Proxy toProxyAddress() {
940c2fd828abec671333b8b88281825fd27a783723Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9560476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson    }
9660476787f0e0f052366d8031c74e507ffd3d16a3Jesse Wilson
97b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
98b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a URL for connecting to this server.
99b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     *
100b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param path the request path, such as "/".
101b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
10200feece22909b7dc79fc96d666d157390b93858eJesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
103096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        return sslSocketFactory != null
1040c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                ? new URL("https://" + getHostName() + ":" + getPort() + path)
1050c2fd828abec671333b8b88281825fd27a783723Jesse Wilson                : new URL("http://" + getHostName() + ":" + getPort() + path);
106b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
107b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
108b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
109b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
110b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * limit.
111b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
112b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
113b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        this.bodyLimit = maxBodyLength;
114b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
115b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
116706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    /**
117706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * Serve requests with HTTPS rather than otherwise.
118706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *
119706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
120706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     *     negotiating TLS.
121706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson     */
122706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
123706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.sslSocketFactory = sslSocketFactory;
124706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson        this.tunnelProxy = tunnelProxy;
125b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
126b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
127b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
128b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
129b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * use this to verify the request sent was as intended.
130b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
131b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
132b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return requestQueue.take();
133b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
134b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
13551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    /**
13651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
13751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
13851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     * in practice.
13951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson     */
14051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public int getRequestCount() {
14151e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        return requestCount.get();
14251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
14351e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
144706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    public void enqueue(MockResponse response) {
1450c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        responseQueue.add(response.clone());
146706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    }
147706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
148b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
149096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * By default, this class processes requests coming in by adding them to a
150096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
151096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * is appropriate for correctness testing.
152096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     *
153096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
154096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
155096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     * for benchmarking.
156096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson     */
157096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
158096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        this.singleResponse = singleResponse;
159096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
160096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
161096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    /**
1620c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * Equivalent to {@code play(0)}.
1630c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     */
1640c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play() throws IOException {
1650c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        play(0);
1660c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    }
1670c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
1680c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    /**
169b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
170b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * down.
1710c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *
1720c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
1730c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
1740c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson     *     specific port is unavailable.
175b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
1760c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson    public void play(int port) throws IOException {
17751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        executor = Executors.newCachedThreadPool();
1780c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        serverSocket = new ServerSocket(port);
17951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        serverSocket.setReuseAddress(true);
180706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
1810c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        this.port = serverSocket.getLocalPort();
182b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
183b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
18451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                try {
185096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    acceptConnections();
186096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
187b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
188096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
189b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
190096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                /*
191096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * This gnarly block of code will release all sockets and
192096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 * all thread, even if any close fails.
193096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                 */
194096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
195096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    serverSocket.close();
196096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
197b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
198096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
199b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
200096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    try {
201096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.next().close();
202096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                        s.remove();
203096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    } catch (Throwable e) {
204b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
20551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    }
206096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
207096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
208096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    executor.shutdown();
209096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } catch (Throwable e) {
210b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
211b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
212b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
213096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
214b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            private void acceptConnections() throws Exception {
215f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                do {
216b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    Socket socket;
217b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    try {
218b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        socket = serverSocket.accept();
219b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } catch (SocketException ignored) {
220b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        continue;
221096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    }
222b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    MockResponse peek = responseQueue.peek();
223b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
2244559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        responseQueue.take();
2254559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                        socket.close();
226b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    } else {
227b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        openClientSockets.add(socket);
228b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                        serveConnection(socket);
2294559b1d37edcb5d7f1da086cf2e3290388d74f46Brian Carlstrom                    }
230f162edaa335461474b020027bb2e85eb3be2c179Jesse Wilson                } while (!responseQueue.isEmpty());
231096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
232096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
233b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
234b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
23551e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    public void shutdown() throws IOException {
23651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        if (serverSocket != null) {
237096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
23851e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson        }
23951e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson    }
24051e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson
241706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson    private void serveConnection(final Socket raw) {
242096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
243b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
244706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            int sequenceNumber = 0;
245b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
246b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
247b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                try {
248b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    processConnection();
249b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                } catch (Exception e) {
250b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
251b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
252b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            }
253b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
254b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void processConnection() throws Exception {
255706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                Socket socket;
256706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (sslSocketFactory != null) {
257706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    if (tunnelProxy) {
258c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        createTunnel();
259b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    }
260706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = sslSocketFactory.createSocket(
261706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
262706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
2638ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.add(socket);
2648ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                    openClientSockets.remove(raw);
265706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                } else {
266706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    socket = raw;
267b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
268b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
269706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
270706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
271706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
272b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {}
273b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson
274b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (sequenceNumber == 0) {
275b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
276706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
277706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
278b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                in.close();
279b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                out.close();
2808ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                socket.close();
281b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                if (responseQueue.isEmpty()) {
282b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    shutdown();
283b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                }
2848ac847a52e72f0cefbb20a6850ae04468d433a9eJesse Wilson                openClientSockets.remove(socket);
285b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
286706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson
287706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            /**
288c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
289c996149b500fc4825156106554457fe2394ae087Jesse Wilson             * is dispatched.
290c996149b500fc4825156106554457fe2394ae087Jesse Wilson             */
291c996149b500fc4825156106554457fe2394ae087Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
292c996149b500fc4825156106554457fe2394ae087Jesse Wilson                while (true) {
293c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    MockResponse connect = responseQueue.peek();
294c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
295c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
296c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
297c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
298c996149b500fc4825156106554457fe2394ae087Jesse Wilson                        return;
299c996149b500fc4825156106554457fe2394ae087Jesse Wilson                    }
300c996149b500fc4825156106554457fe2394ae087Jesse Wilson                }
301c996149b500fc4825156106554457fe2394ae087Jesse Wilson            }
302c996149b500fc4825156106554457fe2394ae087Jesse Wilson
303c996149b500fc4825156106554457fe2394ae087Jesse Wilson            /**
304706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * Reads a request and writes its response. Returns true if a request
305706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             * was processed.
306706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson             */
307e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson            private boolean processOneRequest(InputStream in, OutputStream out, Socket socket)
308706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    throws IOException, InterruptedException {
309706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                RecordedRequest request = readRequest(in, sequenceNumber);
310706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                if (request == null) {
311706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                    return false;
312706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                }
313096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                MockResponse response = dispatch(request);
31451e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                writeResponse(out, response);
315e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
31651e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    in.close();
31751e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                    out.close();
318e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
319e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownInput();
320e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
321e942f46f10bb9384a1b186b3d7b74f9704c57090Jesse Wilson                    socket.shutdownOutput();
32251e468abf2628ce964d3657042f3ac8f2c947504Jesse Wilson                }
323706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                sequenceNumber++;
324706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson                return true;
325706d53593cd8841d378dbe298a8d1940db1e71dfJesse Wilson            }
326096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }));
327b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
328b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
329b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
330b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
331b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
332b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
333b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        String request;
334b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        try {
335b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            request = readAsciiUntilCrlf(in);
336b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        } catch (IOException streamIsClosed) {
337b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because we closed the stream
338b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        }
339b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (request.isEmpty()) {
340b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            return null; // no request because the stream is exhausted
341b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
342b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
343b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<String> headers = new ArrayList<String>();
344b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        int contentLength = -1;
345b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        boolean chunked = false;
346b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String header;
347b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
348b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            headers.add(header);
349b274574b7e2681f4411852af9857b4540bf75841Elliott Hughes            String lowercaseHeader = header.toLowerCase(Locale.ROOT);
350b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
351b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
352b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
353b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
354b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
355b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunked = true;
356b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
357b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
358b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
359fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        boolean hasBody = false;
360b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
361b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
362b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (contentLength != -1) {
363fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
364b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            transfer(contentLength, in, requestBody);
365b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        } else if (chunked) {
366fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            hasBody = true;
367b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            while (true) {
368b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
369b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                if (chunkSize == 0) {
370b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    readEmptyLine(in);
371b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                    break;
372b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                }
373b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                chunkSizes.add(chunkSize);
374b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                transfer(chunkSize, in, requestBody);
375b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                readEmptyLine(in);
376b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
377b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
378b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
3790c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
3800c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
381adb64fbba2b781467e055706c3de0873dfc01166Jesse Wilson                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
382fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (hasBody) {
3830c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
384fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
3850c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
386fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            if (!hasBody) {
3870c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                throw new IllegalArgumentException("Request must have a body: " + request);
388fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            }
389fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        } else {
390fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
391fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson        }
392fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson
393b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
394b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
395b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
396b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
397b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
398b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns a response to satisfy {@code request}.
399b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
400096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
401211d3bbada505912bb16e9d1a6c1a9f1f5c16cffJesse Wilson        if (responseQueue.isEmpty()) {
402b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
403b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
404096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
4050c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        // to permit interactive/browser testing, ignore requests for favicons
4060c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
4070c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            System.out.println("served " + request.getRequestLine());
4080c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson            return new MockResponse()
4090c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
4100c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson        }
4110c59055dd24e1659f85d9ff7e2148883f663bd62Jesse Wilson
412096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        if (singleResponse) {
413096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.peek();
414096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        } else {
415096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestCount.incrementAndGet();
416096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            requestQueue.add(request);
417096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            return responseQueue.take();
418096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        }
419b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
420b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
421b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
422b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
423b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        for (String header : response.getHeaders()) {
424b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
425b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
426b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
427b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.write(response.getBody());
428b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        out.flush();
429b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
430b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
431b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
432b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
433b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
434b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
435b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
436b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        byte[] buffer = new byte[1024];
437b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (length > 0) {
438b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
439b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count == -1) {
440b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return;
441b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
442b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            out.write(buffer, 0, count);
443b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            length -= count;
444b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
445b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
446b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
447b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
448b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
449b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * {@code in} is exhausted.
450b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
451b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
452b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        StringBuilder builder = new StringBuilder();
453b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        while (true) {
454b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            int c = in.read();
455b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
456b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
457b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
458b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else if (c == -1) {
459b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                return builder.toString();
460b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            } else {
461b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                builder.append((char) c);
462b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
463b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
464b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
465b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
466b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
467b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        String line = readAsciiUntilCrlf(in);
468b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        if (!line.isEmpty()) {
469b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
470b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
471b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
472b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson
473b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    /**
474b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
475b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson     */
476b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
477b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        private int numBytesReceived = 0;
478b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
479b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived += len;
480b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
481b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
482b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        @Override public void write(int oneByte) {
483b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            numBytesReceived++;
484b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            if (count < bodyLimit) {
485b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson                super.write(oneByte);
486b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson            }
487b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson        }
488b1b5baac449d2725002338735f4db34bec8fd001Jesse Wilson    }
489096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson
490b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
491b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson        return new Runnable() {
492b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson            public void run() {
493096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                String originalName = Thread.currentThread().getName();
494096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                Thread.currentThread().setName(name);
495096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                try {
496b7f4d6c3968c372767b2510f38a3d506067aced6Jesse Wilson                    runnable.run();
497096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                } finally {
498096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                    Thread.currentThread().setName(originalName);
499096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson                }
500096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson            }
501096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson        };
502096aac7b8a607d3da237900f52cab1c5066bf992Jesse Wilson    }
503fd4350050d7a0d333f9d346560b167040c3f44dfJesse Wilson}
504