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;
2024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE;
213b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey
2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream;
2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream;
2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream;
2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException;
2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream;
2754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream;
2854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.HttpURLConnection;
2954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetAddress;
3054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.InetSocketAddress;
3154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.MalformedURLException;
3254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Proxy;
3354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.ServerSocket;
3454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.Socket;
3554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.SocketException;
3654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.URL;
3754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.net.UnknownHostException;
3824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException;
3924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate;
4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList;
4154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Collections;
4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator;
4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List;
4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Set;
4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue;
4654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
4754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService;
4854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors;
4954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingDeque;
5054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.LinkedBlockingQueue;
5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.atomic.AtomicInteger;
5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Level;
5354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.logging.Logger;
5424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext;
5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket;
5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory;
5724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager;
5824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager;
5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/**
6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence.
6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */
6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer {
6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    static final String ASCII = "US-ASCII";
6754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
6954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
7054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
7154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<MockResponse> responseQueue
7254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingDeque<MockResponse>();
7354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final Set<Socket> openClientSockets
7454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
7554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private boolean singleResponse;
7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final AtomicInteger requestCount = new AtomicInteger();
7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int bodyLimit = Integer.MAX_VALUE;
7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private ServerSocket serverSocket;
7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private SSLSocketFactory sslSocketFactory;
8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private ExecutorService executor;
8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private boolean tunnelProxy;
8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int port = -1;
8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getPort() {
8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (port == -1) {
8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return port;
9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public String getHostName() {
9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return InetAddress.getLocalHost().getHostName();
9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (UnknownHostException e) {
9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new AssertionError();
9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public Proxy toProxyAddress() {
10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
10354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
10454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
10554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a URL for connecting to this server.
10654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
10754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param path the request path, such as "/".
10854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
10954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public URL getUrl(String path) throws MalformedURLException, UnknownHostException {
11054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return sslSocketFactory != null
11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                ? new URL("https://" + getHostName() + ":" + getPort() + path)
11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                : new URL("http://" + getHostName() + ":" + getPort() + path);
11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
11454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
11554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
11618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * Returns a cookie domain for this server. This returns the server's
11718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * non-loopback host name if it is known. Otherwise this returns ".local"
11818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * for this server's loopback name.
11918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     */
12018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    public String getCookieDomain() {
12118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        String hostName = getHostName();
12218819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        return hostName.contains(".") ? hostName : ".local";
12318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    }
12418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson
12518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    /**
12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * limit.
12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.bodyLimit = maxBodyLength;
13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Serve requests with HTTPS rather than otherwise.
13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     negotiating TLS.
13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.sslSocketFactory = sslSocketFactory;
14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.tunnelProxy = tunnelProxy;
14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * use this to verify the request sent was as intended.
14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestQueue.take();
15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * in practice.
15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getRequestCount() {
15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestCount.get();
15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
16054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
16154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void enqueue(MockResponse response) {
16254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        responseQueue.add(response.clone());
16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
16454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
16554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * By default, this class processes requests coming in by adding them to a
16754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * queue and serves responses by removing them from another queue. This mode
16854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * is appropriate for correctness testing.
16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * <p>Serving a single response causes the server to be stateless: requests
17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * are not enqueued, and responses are not dequeued. This mode is appropriate
17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * for benchmarking.
17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setSingleResponse(boolean singleResponse) {
17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.singleResponse = singleResponse;
17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Equivalent to {@code play(0)}.
18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play() throws IOException {
18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        play(0);
18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
18754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * down.
18854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
18954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     specific port is unavailable.
19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play(int port) throws IOException {
19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor = Executors.newCachedThreadPool();
19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket = new ServerSocket(port);
19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket.setReuseAddress(true);
19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.port = serverSocket.getLocalPort();
19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    acceptConnections();
20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                /*
20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * This gnarly block of code will release all sockets and
20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * all thread, even if any close fails.
21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 */
21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
21254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    serverSocket.close();
21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                for (Iterator<Socket> s = openClientSockets.iterator(); s.hasNext();) {
21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.next().close();
21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.remove();
22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (Throwable e) {
22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    executor.shutdown();
22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
22854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void acceptConnections() throws Exception {
23254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                do {
23354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Socket socket;
23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
23554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket = serverSocket.accept();
23654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (SocketException ignored) {
23754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        continue;
23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    MockResponse peek = responseQueue.peek();
24054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (peek != null && peek.getSocketPolicy() == DISCONNECT_AT_START) {
24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        responseQueue.take();
24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket.close();
24354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } else {
24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        openClientSockets.add(socket);
24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        serveConnection(socket);
24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } while (!responseQueue.isEmpty());
24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void shutdown() throws IOException {
25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (serverSocket != null) {
25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void serveConnection(final Socket raw) {
25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int sequenceNumber = 0;
26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    processConnection();
26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Exception e) {
26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void processConnection() throws Exception {
27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Socket socket;
27354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sslSocketFactory != null) {
27454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (tunnelProxy) {
27554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        createTunnel();
27654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
27724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    MockResponse response = responseQueue.peek();
27824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    if (response != null && response.getSocketPolicy() == FAIL_HANDSHAKE) {
27924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        processHandshakeFailure(raw, sequenceNumber++);
28024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        return;
28124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    }
28254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = sslSocketFactory.createSocket(
28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.add(socket);
28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.remove(raw);
28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else {
28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = raw;
28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
29154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
29254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
29424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                while (!responseQueue.isEmpty() && processOneRequest(socket, in, out)) {}
29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sequenceNumber == 0) {
29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                in.close();
30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                out.close();
30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                socket.close();
30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (responseQueue.isEmpty()) {
30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    shutdown();
30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                openClientSockets.remove(socket);
30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
31054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
31154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * is dispatched.
31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
31454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                while (true) {
31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    MockResponse connect = responseQueue.peek();
31624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) {
31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (connect.getSocketPolicy() == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        return;
32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
32454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
32654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Reads a request and writes its response. Returns true if a request
32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * was processed.
32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
32924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            private boolean processOneRequest(Socket socket, InputStream in, OutputStream out)
33054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    throws IOException, InterruptedException {
33124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                RecordedRequest request = readRequest(socket, in, sequenceNumber);
33254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (request == null) {
33354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    return false;
33454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
33554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                MockResponse response = dispatch(request);
33654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                writeResponse(out, response);
33754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    in.close();
33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    out.close();
34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownInput();
34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownOutput();
34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
3453b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                logger.info("Received request: " + request + " and responded: " + response);
34654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                sequenceNumber++;
34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return true;
34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
35224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception {
35324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        responseQueue.take();
35424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        X509TrustManager untrusted = new X509TrustManager() {
35524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public void checkClientTrusted(X509Certificate[] chain, String authType)
35624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    throws CertificateException {
35724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new CertificateException();
35824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
35924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {
36024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new AssertionError();
36124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
36224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            @Override public X509Certificate[] getAcceptedIssuers() {
36324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                throw new AssertionError();
36424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            }
36524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        };
36624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLContext context = SSLContext.getInstance("TLS");
36724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        context.init(null, new TrustManager[] { untrusted }, new java.security.SecureRandom());
36824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLSocketFactory sslSocketFactory = context.getSocketFactory();
36924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(
37024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
37124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        try {
37224198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            socket.startHandshake(); // we're testing a handshake failure
37324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            throw new AssertionError();
37424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        } catch (IOException expected) {
37524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        }
37624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        socket.close();
37724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        requestCount.incrementAndGet();
37824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        requestQueue.add(new RecordedRequest(null, null, null, -1, null, sequenceNumber, socket));
37924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    }
38024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson
38154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
38254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
38354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
38424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private RecordedRequest readRequest(Socket socket, InputStream in, int sequenceNumber)
38524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            throws IOException {
38654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String request;
38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            request = readAsciiUntilCrlf(in);
38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (IOException streamIsClosed) {
39054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because we closed the stream
39154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.isEmpty()) {
39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because the stream is exhausted
39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<String> headers = new ArrayList<String>();
39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        int contentLength = -1;
39854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean chunked = false;
39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String header;
40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (!(header = readAsciiUntilCrlf(in)).isEmpty()) {
40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            headers.add(header);
40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            String lowercaseHeader = header.toLowerCase();
40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
40654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunked = true;
40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean hasBody = false;
41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (contentLength != -1) {
41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            transfer(contentLength, in, requestBody);
41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (chunked) {
41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            while (true) {
42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (chunkSize == 0) {
42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    readEmptyLine(in);
42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    break;
42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunkSizes.add(chunkSize);
42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                transfer(chunkSize, in, requestBody);
42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                readEmptyLine(in);
42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request .startsWith("TRACE ") || request.startsWith("CONNECT ")) {
43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (hasBody) {
43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (request.startsWith("POST ") || request.startsWith("PUT ")) {
43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (!hasBody) {
44054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must have a body: " + request);
44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
44454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
44724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber, socket);
44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
44954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a response to satisfy {@code request}.
45254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
45354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private MockResponse dispatch(RecordedRequest request) throws InterruptedException {
45454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (responseQueue.isEmpty()) {
45554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Unexpected request: " + request);
45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
45754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
45854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        // to permit interactive/browser testing, ignore requests for favicons
45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.getRequestLine().equals("GET /favicon.ico HTTP/1.1")) {
46054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            System.out.println("served " + request.getRequestLine());
46154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return new MockResponse()
46254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        .setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
46454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (singleResponse) {
46654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return responseQueue.peek();
46754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
46854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            requestCount.incrementAndGet();
46954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            requestQueue.add(request);
47054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return responseQueue.take();
47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
47254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
47354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
47454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
47554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
47654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        for (String header : response.getHeaders()) {
47754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write((header + "\r\n").getBytes(ASCII));
47854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
47954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.write(("\r\n").getBytes(ASCII));
48054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        out.flush();
4813b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey
4823b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey        byte[] body = response.getBody();
4833b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey        int bytesPerSecond = response.getBytesPerSecond();
4843b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey
4853b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey        for (int offset = 0; offset < body.length; offset += bytesPerSecond) {
4863b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey            int count = Math.min(body.length - offset, bytesPerSecond);
4873b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey            out.write(body, offset, count);
4883b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey            out.flush();
4893b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey
4903b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey            if (offset + count < body.length) {
4913b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                try {
4923b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                    Thread.sleep(1000);
4933b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                } catch (InterruptedException e) {
4943b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                    throw new AssertionError();
4953b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                }
4963b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey            }
4973b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey        }
49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
50454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
50554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        byte[] buffer = new byte[1024];
50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (length > 0) {
50754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
50854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count == -1) {
50954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return;
51054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
51154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write(buffer, 0, count);
51254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            length -= count;
51354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
51454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
51554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
51654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
51754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
51854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * {@code in} is exhausted.
51954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
52054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
52154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        StringBuilder builder = new StringBuilder();
52254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (true) {
52354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int c = in.read();
52454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
52554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
52654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else if (c == -1) {
52854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
52954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else {
53054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.append((char) c);
53154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
53254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
53354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
53454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
53554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
53654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String line = readAsciiUntilCrlf(in);
53754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (!line.isEmpty()) {
53854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
53954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
54054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
54154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
54254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
54354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
54454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
54554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
54654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        private int numBytesReceived = 0;
54754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
54854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived += len;
54954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
55054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
55154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(int oneByte) {
55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived++;
55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count < bodyLimit) {
55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                super.write(oneByte);
55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Runnable() {
56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                String originalName = Thread.currentThread().getName();
56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Thread.currentThread().setName(name);
56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
56554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    runnable.run();
56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } finally {
56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Thread.currentThread().setName(originalName);
56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        };
57154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
57254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson}
573