MockWebServer.java revision 54ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5
154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/*
254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Copyright (C) 2011 Google Inc.
354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson *
454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * you may not use this file except in compliance with the License.
654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * You may obtain a copy of the License at
754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson *
854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson *
1054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * Unless required by applicable law or agreed to in writing, software
1154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
1254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * See the License for the specific language governing permissions and
1454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * limitations under the License.
1554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */
1654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
1754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpackage com.google.mockwebserver;
1854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
1954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
2054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream;
2154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream;
2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream;
2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException;
2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream;
2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream;
2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.HttpURLConnection;
2754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetAddress;
2854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetSocketAddress;
2954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.MalformedURLException;
3054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Proxy;
3154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.ServerSocket;
3254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Socket;
3354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.SocketException;
3454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.URL;
3554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.UnknownHostException;
3654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList;
3754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Collections;
3854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator;
3954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List;
4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Set;
4154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue;
4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService;
4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors;
4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque;
4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level;
4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger;
5054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket;
5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory;
5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
5354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/**
5454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence.
5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */
5754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer {
5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    static final String ASCII = "US-ASCII";
6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final Set<Socket> openClientSockets
6754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
6854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private boolean singleResponse;
6954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
7054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private ServerSocket serverSocket;
7254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private SSLSocketFactory sslSocketFactory;
7354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private ExecutorService executor;
7454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private boolean tunnelProxy;
7554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int port = -1;
7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getPort() {
7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (port == -1) {
8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return port;
8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public String getHostName() {
8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return InetAddress.getLocalHost().getHostName();
8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (UnknownHostException e) {
8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new AssertionError();
9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public Proxy toProxyAddress() {
9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a URL for connecting to this server.
9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param path the request path, such as "/".
10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return sslSocketFactory != null
10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                ? new URL("https://" + getHostName() + ":" + getPort() + path)
10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                : new URL("http://" + getHostName() + ":" + getPort() + path);
10654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
10754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
10854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
10954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
11054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * limit.
11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.bodyLimit = maxBodyLength;
11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
11654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
11754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Serve requests with HTTPS rather than otherwise.
11854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
11954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
12054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     negotiating TLS.
12154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
12254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
12354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.sslSocketFactory = sslSocketFactory;
12454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.tunnelProxy = tunnelProxy;
12554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * use this to verify the request sent was as intended.
13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestQueue.take();
13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * in practice.
13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getRequestCount() {
14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestCount.get();
14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void enqueue(MockResponse response) {
14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        responseQueue.add(response.clone());
14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * By default, this class processes requests coming in by adding them to a
15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * is appropriate for correctness testing.
15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * for benchmarking.
15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.singleResponse = singleResponse;
15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Equivalent to {@code play(0)}.
16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play() throws IOException {
16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        play(0);
16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
16754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
16854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * down.
17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     specific port is unavailable.
17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play(int port) throws IOException {
17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor = Executors.newCachedThreadPool();
17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket = new ServerSocket(port);
17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket.setReuseAddress(true);
18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.port = serverSocket.getLocalPort();
18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    acceptConnections();
18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
18754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
18854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                /*
19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * This gnarly block of code will release all sockets and
19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * all thread, even if any close fails.
19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 */
19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    serverSocket.close();
19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.next().close();
20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.remove();
20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (Throwable e) {
20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    executor.shutdown();
20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
21254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void acceptConnections() throws Exception {
21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                do {
21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Socket socket;
21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket = serverSocket.accept();
21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (SocketException ignored) {
22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        continue;
22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    MockResponse peek = responseQueue.peek();
22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        responseQueue.take();
22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket.close();
22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } else {
22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        openClientSockets.add(socket);
22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        serveConnection(socket);
22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } while (!responseQueue.isEmpty());
23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
23554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void shutdown() throws IOException {
23654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (serverSocket != null) {
23754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void serveConnection(final Socket raw) {
24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int sequenceNumber = 0;
24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    processConnection();
24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Exception e) {
25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void processConnection() throws Exception {
25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Socket socket;
25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sslSocketFactory != null) {
25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (tunnelProxy) {
25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        createTunnel();
25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = sslSocketFactory.createSocket(
26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.add(socket);
26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.remove(raw);
26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else {
26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = raw;
26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(in, out, socket)) {}
27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sequenceNumber == 0) {
27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
27754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                in.close();
27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                out.close();
28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                socket.close();
28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (responseQueue.isEmpty()) {
28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    shutdown();
28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                openClientSockets.remove(socket);
28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * is dispatched.
29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
29154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
29254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                while (true) {
29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    MockResponse connect = responseQueue.peek();
29454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (!processOneRequest(raw.getInputStream(), raw.getOutputStream(), raw)) {
29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        return;
29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Reads a request and writes its response. Returns true if a request
30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * was processed.
30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private boolean processOneRequest(InputStream in, OutputStream out, Socket socket)
30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    throws IOException, InterruptedException {
30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                RecordedRequest request = readRequest(in, sequenceNumber);
31054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (request == null) {
31154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    return false;
31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                MockResponse response = dispatch(request);
31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                writeResponse(out, response);
31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
31654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    in.close();
31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    out.close();
31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownInput();
32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownOutput();
32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                sequenceNumber++;
32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return true;
32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
32954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
33054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
33154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
33254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String request;
33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            request = readAsciiUntilCrlf(in);
33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (IOException streamIsClosed) {
33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because we closed the stream
33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.isEmpty()) {
34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because the stream is exhausted
34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<String> headers = new ArrayList<String>();
34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        int contentLength = -1;
34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean chunked = false;
34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String header;
34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            headers.add(header);
34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            String lowercaseHeader = header.toLowerCase();
35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
35254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
35354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
35454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
35554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunked = true;
35654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
35754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
35854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
35954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean hasBody = false;
36054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
36154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
36254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (contentLength != -1) {
36354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
36454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            transfer(contentLength, in, requestBody);
36554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (chunked) {
36654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
36754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            while (true) {
36854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
36954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (chunkSize == 0) {
37054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    readEmptyLine(in);
37154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    break;
37254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
37354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunkSizes.add(chunkSize);
37454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                transfer(chunkSize, in, requestBody);
37554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                readEmptyLine(in);
37654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
37754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
37854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
37954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
38054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
38154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
38254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (hasBody) {
38354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
38454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
38554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
38654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (!hasBody) {
38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must have a body: " + request);
38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
39054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
39154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
39854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a response to satisfy {@code request}.
39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (responseQueue.isEmpty()) {
40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        // to permit interactive/browser testing, ignore requests for favicons
40654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            System.out.println("served " + request.getRequestLine());
40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return new MockResponse()
40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (singleResponse) {
41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return responseQueue.peek();
41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            requestCount.incrementAndGet();
41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            requestQueue.add(request);
41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return responseQueue.take();
41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        for (String header : response.getHeaders()) {
42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.write(response.getBody());
42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.flush();
42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        byte[] buffer = new byte[1024];
43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (length > 0) {
43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count == -1) {
44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return;
44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write(buffer, 0, count);
44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            length -= count;
44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
44754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
44954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * {@code in} is exhausted.
45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
45254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        StringBuilder builder = new StringBuilder();
45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (true) {
45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int c = in.read();
45554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
45754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
45854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else if (c == -1) {
45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
46054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else {
46154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.append((char) c);
46254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
46454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String line = readAsciiUntilCrlf(in);
46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (!line.isEmpty()) {
46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
47454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
47554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        private int numBytesReceived = 0;
47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
47954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived += len;
48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
48154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
48254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(int oneByte) {
48354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived++;
48454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count < bodyLimit) {
48554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                super.write(oneByte);
48654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
48754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
48854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
48954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
49054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
49154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Runnable() {
49254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
49354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                String originalName = Thread.currentThread().getName();
49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Thread.currentThread().setName(name);
49554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
49654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    runnable.run();
49754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } finally {
49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Thread.currentThread().setName(originalName);
49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        };
50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson}
504