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;
2154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedInputStream;
2254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.BufferedOutputStream;
2354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.ByteArrayOutputStream;
2454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.IOException;
2554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.InputStream;
2654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.io.OutputStream;
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;
3624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.CertificateException;
3724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport java.security.cert.X509Certificate;
3854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.ArrayList;
3954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.Iterator;
4054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.List;
417a68ed6a49c3060b235810391a82412a95f9c979jwilsonimport java.util.Map;
4254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.BlockingQueue;
4354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ConcurrentHashMap;
4454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.ExecutorService;
4554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport java.util.concurrent.Executors;
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;
5024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.SSLContext;
5154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocket;
5254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonimport javax.net.ssl.SSLSocketFactory;
5324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.TrustManager;
5424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilsonimport javax.net.ssl.X509TrustManager;
5554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
5654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson/**
5754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * A scriptable web server. Callers supply canned responses and the server
5854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson * replays them upon request in sequence.
5954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson */
6054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilsonpublic final class MockWebServer {
6154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    static final String ASCII = "US-ASCII";
6354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
6454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static final Logger logger = Logger.getLogger(MockWebServer.class.getName());
6554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private final BlockingQueue<RecordedRequest> requestQueue
6654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            = new LinkedBlockingQueue<RecordedRequest>();
677a68ed6a49c3060b235810391a82412a95f9c979jwilson    /** All map values are Boolean.TRUE. (Collections.newSetFromMap isn't available in Froyo) */
687a68ed6a49c3060b235810391a82412a95f9c979jwilson    private final Map<Socket, Boolean> openClientSockets = new ConcurrentHashMap<Socket, Boolean>();
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;
757a68ed6a49c3060b235810391a82412a95f9c979jwilson    private Dispatcher dispatcher = new QueueDispatcher();
7654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
7754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private int port = -1;
7854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
7954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getPort() {
8054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (port == -1) {
8154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Cannot retrieve port before calling play()");
8254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
8354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return port;
8454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
8554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
8654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public String getHostName() {
8754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
8854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return InetAddress.getLocalHost().getHostName();
8954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (UnknownHostException e) {
9054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new AssertionError();
9154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
9254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public Proxy toProxyAddress() {
9554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getHostName(), getPort()));
9654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
9754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
9854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
9954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns a URL for connecting to this server.
10054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
10154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param path the request path, such as "/".
10254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
1037a68ed6a49c3060b235810391a82412a95f9c979jwilson    public URL getUrl(String path) {
1047a68ed6a49c3060b235810391a82412a95f9c979jwilson        try {
1057a68ed6a49c3060b235810391a82412a95f9c979jwilson            return sslSocketFactory != null
1067a68ed6a49c3060b235810391a82412a95f9c979jwilson                    ? new URL("https://" + getHostName() + ":" + getPort() + path)
1077a68ed6a49c3060b235810391a82412a95f9c979jwilson                    : new URL("http://" + getHostName() + ":" + getPort() + path);
1087a68ed6a49c3060b235810391a82412a95f9c979jwilson        } catch (MalformedURLException e) {
1097a68ed6a49c3060b235810391a82412a95f9c979jwilson            throw new AssertionError(e);
1107a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
11154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
11254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
11354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
11418819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * Returns a cookie domain for this server. This returns the server's
11518819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * non-loopback host name if it is known. Otherwise this returns ".local"
11618819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     * for this server's loopback name.
11718819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson     */
11818819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    public String getCookieDomain() {
11918819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        String hostName = getHostName();
12018819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson        return hostName.contains(".") ? hostName : ".local";
12118819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    }
12218819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson
12318819471837d75b4e579bc0c23d6f8c8d9011653Jesse Wilson    /**
12454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Sets the number of bytes of the POST body to keep in memory to the given
12554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * limit.
12654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
12754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void setBodyLimit(int maxBodyLength) {
12854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.bodyLimit = maxBodyLength;
12954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
13054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
13154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
13254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Serve requests with HTTPS rather than otherwise.
13354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
13454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param tunnelProxy whether to expect the HTTP CONNECT method before
13554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     negotiating TLS.
13654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
13754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void useHttps(SSLSocketFactory sslSocketFactory, boolean tunnelProxy) {
13854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.sslSocketFactory = sslSocketFactory;
13954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.tunnelProxy = tunnelProxy;
14054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
14254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
14354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Awaits the next HTTP request, removes it, and returns it. Callers should
14454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * use this to verify the request sent was as intended.
14554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
14654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public RecordedRequest takeRequest() throws InterruptedException {
14754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestQueue.take();
14854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
14954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
15054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
15154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the number of HTTP requests received thus far by this server.
15254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * This may exceed the number of HTTP connections when connection reuse is
15354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * in practice.
15454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
15554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public int getRequestCount() {
15654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return requestCount.get();
15754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
15854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
15954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
1607a68ed6a49c3060b235810391a82412a95f9c979jwilson     * Scripts {@code response} to be returned to a request made in sequence.
1617a68ed6a49c3060b235810391a82412a95f9c979jwilson     * The first request is served by the first enqueued response; the second
1627a68ed6a49c3060b235810391a82412a95f9c979jwilson     * request by the second enqueued response; and so on.
16354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
1647a68ed6a49c3060b235810391a82412a95f9c979jwilson     * @throws ClassCastException if the default dispatcher has been replaced
1657a68ed6a49c3060b235810391a82412a95f9c979jwilson     *     with {@link #setDispatcher(Dispatcher)}.
16654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
1677a68ed6a49c3060b235810391a82412a95f9c979jwilson    public void enqueue(MockResponse response) {
1687a68ed6a49c3060b235810391a82412a95f9c979jwilson        ((QueueDispatcher) dispatcher).enqueueResponse(response.clone());
16954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
17054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
17154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
17254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Equivalent to {@code play(0)}.
17354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
17454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play() throws IOException {
17554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        play(0);
17654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
17754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
17854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
17954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Starts the server, serves all enqueued requests, and shuts the server
18054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * down.
18154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *
18254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param port the port to listen to, or 0 for any available port.
18354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     Automated tests should always use port 0 to avoid flakiness when a
18454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     *     specific port is unavailable.
18554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
18654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void play(int port) throws IOException {
1876ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom        if (executor != null) {
1886ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom            throw new IllegalStateException("play() already called");
1896ae1753c9c0b3c0b2479a7c4099e38e4249dc50eBrian Carlstrom        }
19054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor = Executors.newCachedThreadPool();
19154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket = new ServerSocket(port);
19254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        serverSocket.setReuseAddress(true);
19354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
19454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        this.port = serverSocket.getLocalPort();
19554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable("MockWebServer-accept-" + port, new Runnable() {
19654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
19754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
19854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    acceptConnections();
19954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
20054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
20154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
20254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
20354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                /*
20454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * This gnarly block of code will release all sockets and
20554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 * all thread, even if any close fails.
20654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                 */
20754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
20854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    serverSocket.close();
20954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
21054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer server socket close failed", e);
21154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
2127a68ed6a49c3060b235810391a82412a95f9c979jwilson                for (Iterator<Socket> s = openClientSockets.keySet().iterator(); s.hasNext(); ) {
21354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
21454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.next().close();
21554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        s.remove();
21654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } catch (Throwable e) {
21754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        logger.log(Level.WARNING, "MockWebServer socket close failed", e);
21854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
21954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
22054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
22154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    executor.shutdown();
22254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Throwable e) {
22354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer executor shutdown failed", e);
22454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
22554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
22654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
22754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void acceptConnections() throws Exception {
2287a68ed6a49c3060b235810391a82412a95f9c979jwilson                while (true) {
22954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Socket socket;
23054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    try {
23154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket = serverSocket.accept();
2327a68ed6a49c3060b235810391a82412a95f9c979jwilson                    } catch (SocketException e) {
2337a68ed6a49c3060b235810391a82412a95f9c979jwilson                        return;
23454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2357a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
2367a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == DISCONNECT_AT_START) {
2377a68ed6a49c3060b235810391a82412a95f9c979jwilson                        dispatchBookkeepingRequest(0, socket);
23854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        socket.close();
23954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    } else {
2407a68ed6a49c3060b235810391a82412a95f9c979jwilson                        openClientSockets.put(socket, true);
24154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        serveConnection(socket);
24254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2437a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
24454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
24554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
24654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
24754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
24854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    public void shutdown() throws IOException {
24954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (serverSocket != null) {
25054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            serverSocket.close(); // should cause acceptConnections() to break out
25154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
25254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
25354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
25454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void serveConnection(final Socket raw) {
25554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String name = "MockWebServer-" + raw.getRemoteSocketAddress();
25654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        executor.execute(namedRunnable(name, new Runnable() {
25754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int sequenceNumber = 0;
25854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
25954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
26054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
26154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    processConnection();
26254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } catch (Exception e) {
26354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.log(Level.WARNING, "MockWebServer connection failed", e);
26454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
26554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
26654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
26754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void processConnection() throws Exception {
26854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Socket socket;
26954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sslSocketFactory != null) {
27054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    if (tunnelProxy) {
27154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        createTunnel();
27254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
2737a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
2747a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == FAIL_HANDSHAKE) {
2757a68ed6a49c3060b235810391a82412a95f9c979jwilson                        dispatchBookkeepingRequest(sequenceNumber, raw);
27624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        processHandshakeFailure(raw, sequenceNumber++);
27724198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                        return;
27824198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    }
27954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = sslSocketFactory.createSocket(
28054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                            raw, raw.getInetAddress().getHostAddress(), raw.getPort(), true);
28154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    ((SSLSocket) socket).setUseClientMode(false);
2827a68ed6a49c3060b235810391a82412a95f9c979jwilson                    openClientSockets.put(socket, true);
28354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    openClientSockets.remove(raw);
28454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else {
28554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket = raw;
28654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
28754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
28854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                InputStream in = new BufferedInputStream(socket.getInputStream());
28954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                OutputStream out = new BufferedOutputStream(socket.getOutputStream());
29054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
2917a68ed6a49c3060b235810391a82412a95f9c979jwilson                while (processOneRequest(socket, in, out)) {
2927a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
29354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
29454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (sequenceNumber == 0) {
29554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    logger.warning("MockWebServer connection didn't make a request");
29654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
29754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
29854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                in.close();
29954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                out.close();
30054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                socket.close();
30154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                openClientSockets.remove(socket);
30254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
30354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
30454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
30554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Respond to CONNECT requests until a SWITCH_TO_SSL_AT_END response
30654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * is dispatched.
30754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
30854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            private void createTunnel() throws IOException, InterruptedException {
30954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                while (true) {
3107a68ed6a49c3060b235810391a82412a95f9c979jwilson                    final SocketPolicy socketPolicy = dispatcher.peekSocketPolicy();
31124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                    if (!processOneRequest(raw, raw.getInputStream(), raw.getOutputStream())) {
31254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        throw new IllegalStateException("Tunnel without any CONNECT!");
31354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
3147a68ed6a49c3060b235810391a82412a95f9c979jwilson                    if (socketPolicy == SocketPolicy.UPGRADE_TO_SSL_AT_END) {
31554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                        return;
31654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    }
31754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
31854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
31954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
32054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            /**
32154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * Reads a request and writes its response. Returns true if a request
32254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             * was processed.
32354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson             */
32424198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            private boolean processOneRequest(Socket socket, InputStream in, OutputStream out)
32554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    throws IOException, InterruptedException {
32624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                RecordedRequest request = readRequest(socket, in, sequenceNumber);
32754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (request == null) {
32854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    return false;
32954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
3307a68ed6a49c3060b235810391a82412a95f9c979jwilson                requestCount.incrementAndGet();
3317a68ed6a49c3060b235810391a82412a95f9c979jwilson                requestQueue.add(request);
3327a68ed6a49c3060b235810391a82412a95f9c979jwilson                MockResponse response = dispatcher.dispatch(request);
333e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AFTER_READING_REQUEST) {
334e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath                  logger.info("Received request: " + request + " and disconnected without responding");
335e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath                  return false;
336e44762f71ed6eeb718a0bb6f3848beb7696fd97dNarayan Kamath                }
3377a68ed6a49c3060b235810391a82412a95f9c979jwilson                writeResponse(out, response);
33854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (response.getSocketPolicy() == SocketPolicy.DISCONNECT_AT_END) {
33954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    in.close();
34054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    out.close();
34154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_INPUT_AT_END) {
34254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownInput();
34354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } else if (response.getSocketPolicy() == SocketPolicy.SHUTDOWN_OUTPUT_AT_END) {
34454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    socket.shutdownOutput();
34554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
3463b406d4de9b5f0f1caa990f079075eb0bfc5220aJeff Sharkey                logger.info("Received request: " + request + " and responded: " + response);
34754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                sequenceNumber++;
34854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return true;
34954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
35054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }));
35154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
35254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
35324198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private void processHandshakeFailure(Socket raw, int sequenceNumber) throws Exception {
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();
3777a68ed6a49c3060b235810391a82412a95f9c979jwilson    }
3787a68ed6a49c3060b235810391a82412a95f9c979jwilson
3797a68ed6a49c3060b235810391a82412a95f9c979jwilson    private void dispatchBookkeepingRequest(int sequenceNumber, Socket socket) throws InterruptedException {
38024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson        requestCount.incrementAndGet();
381cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath        RecordedRequest request = new RecordedRequest(null, null, null, -1, null, sequenceNumber,
382cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath                socket);
383cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath        dispatcher.dispatch(request);
384cb8ab9cb9f5da083e7390920f0f7d0c8a879cb6bNarayan Kamath        requestQueue.add(request);
38524198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    }
38624198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson
38754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
38854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * @param sequenceNumber the index of this request on this connection.
38954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
39024198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson    private RecordedRequest readRequest(Socket socket, InputStream in, int sequenceNumber)
39124198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson            throws IOException {
39254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String request;
39354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        try {
39454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            request = readAsciiUntilCrlf(in);
39554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } catch (IOException streamIsClosed) {
39654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because we closed the stream
39754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
3987a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (request.length() == 0) {
39954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            return null; // no request because the stream is exhausted
40054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
40154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
40254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<String> headers = new ArrayList<String>();
40354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        int contentLength = -1;
40454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean chunked = false;
40554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String header;
4067a68ed6a49c3060b235810391a82412a95f9c979jwilson        while ((header = readAsciiUntilCrlf(in)).length() != 0) {
40754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            headers.add(header);
40854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            String lowercaseHeader = header.toLowerCase();
40954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
41054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                contentLength = Integer.parseInt(header.substring(15).trim());
41154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
41254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (lowercaseHeader.startsWith("transfer-encoding:") &&
41354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    lowercaseHeader.substring(18).trim().equals("chunked")) {
41454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunked = true;
41554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
41654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
41754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
41854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        boolean hasBody = false;
41954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        TruncatingOutputStream requestBody = new TruncatingOutputStream();
42054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        List<Integer> chunkSizes = new ArrayList<Integer>();
42154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (contentLength != -1) {
42254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
42354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            transfer(contentLength, in, requestBody);
42454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else if (chunked) {
42554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            hasBody = true;
42654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            while (true) {
42754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
42854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                if (chunkSize == 0) {
42954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    readEmptyLine(in);
43054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    break;
43154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
43254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                chunkSizes.add(chunkSize);
43354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                transfer(chunkSize, in, requestBody);
43454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                readEmptyLine(in);
43554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
43654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
43754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
43854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        if (request.startsWith("OPTIONS ") || request.startsWith("GET ")
43954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                || request.startsWith("HEAD ") || request.startsWith("DELETE ")
4407a68ed6a49c3060b235810391a82412a95f9c979jwilson                || request.startsWith("TRACE ") || request.startsWith("CONNECT ")) {
44154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (hasBody) {
44254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                throw new IllegalArgumentException("Request must not have a body: " + request);
44354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
4447a68ed6a49c3060b235810391a82412a95f9c979jwilson        } else if (!request.startsWith("POST ") && !request.startsWith("PUT ")) {
44554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new UnsupportedOperationException("Unexpected method: " + request);
44654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
44754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
44854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new RecordedRequest(request, headers, chunkSizes,
44924198dd39014c4490b963f039b04d52e01d79bc5Jesse Wilson                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber, socket);
45054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
45154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
4527a68ed6a49c3060b235810391a82412a95f9c979jwilson    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
4537a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
4547a68ed6a49c3060b235810391a82412a95f9c979jwilson        for (String header : response.getHeaders()) {
4557a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.write((header + "\r\n").getBytes(ASCII));
45654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
4577a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.write(("\r\n").getBytes(ASCII));
4587a68ed6a49c3060b235810391a82412a95f9c979jwilson        out.flush();
45954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
4607a68ed6a49c3060b235810391a82412a95f9c979jwilson        final InputStream in = response.getBodyStream();
4617a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (in == null) {
4627a68ed6a49c3060b235810391a82412a95f9c979jwilson            return;
46354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
4647a68ed6a49c3060b235810391a82412a95f9c979jwilson        final int bytesPerSecond = response.getBytesPerSecond();
46554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
4667a68ed6a49c3060b235810391a82412a95f9c979jwilson        // Stream data in MTU-sized increments
4677a68ed6a49c3060b235810391a82412a95f9c979jwilson        final byte[] buffer = new byte[1452];
4687a68ed6a49c3060b235810391a82412a95f9c979jwilson        final long delayMs;
4697a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (bytesPerSecond == Integer.MAX_VALUE) {
4707a68ed6a49c3060b235810391a82412a95f9c979jwilson            delayMs = 0;
47154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        } else {
4727a68ed6a49c3060b235810391a82412a95f9c979jwilson            delayMs = (1000 * buffer.length) / bytesPerSecond;
4737a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
4747a68ed6a49c3060b235810391a82412a95f9c979jwilson
4757a68ed6a49c3060b235810391a82412a95f9c979jwilson        int read;
4767a68ed6a49c3060b235810391a82412a95f9c979jwilson        long sinceDelay = 0;
4777a68ed6a49c3060b235810391a82412a95f9c979jwilson        while ((read = in.read(buffer)) != -1) {
4787a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.write(buffer, 0, read);
4797a68ed6a49c3060b235810391a82412a95f9c979jwilson            out.flush();
4807a68ed6a49c3060b235810391a82412a95f9c979jwilson
4817a68ed6a49c3060b235810391a82412a95f9c979jwilson            sinceDelay += read;
4827a68ed6a49c3060b235810391a82412a95f9c979jwilson            if (sinceDelay >= buffer.length && delayMs > 0) {
4837a68ed6a49c3060b235810391a82412a95f9c979jwilson                sinceDelay %= buffer.length;
4847a68ed6a49c3060b235810391a82412a95f9c979jwilson                try {
4857a68ed6a49c3060b235810391a82412a95f9c979jwilson                    Thread.sleep(delayMs);
4867a68ed6a49c3060b235810391a82412a95f9c979jwilson                } catch (InterruptedException e) {
4877a68ed6a49c3060b235810391a82412a95f9c979jwilson                    throw new AssertionError();
4887a68ed6a49c3060b235810391a82412a95f9c979jwilson                }
4897a68ed6a49c3060b235810391a82412a95f9c979jwilson            }
49054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
49154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
49254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
49354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
49454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Transfer bytes from {@code in} to {@code out} until either {@code length}
49554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * bytes have been transferred or {@code in} is exhausted.
49654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
49754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
49854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        byte[] buffer = new byte[1024];
49954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (length > 0) {
50054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int count = in.read(buffer, 0, Math.min(buffer.length, length));
50154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count == -1) {
50254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return;
50354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
50454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            out.write(buffer, 0, count);
50554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            length -= count;
50654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
50754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
50854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
50954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
51054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * Returns the text from {@code in} until the next "\r\n", or null if
51154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * {@code in} is exhausted.
51254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
51354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private String readAsciiUntilCrlf(InputStream in) throws IOException {
51454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        StringBuilder builder = new StringBuilder();
51554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        while (true) {
51654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            int c = in.read();
51754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
51854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.deleteCharAt(builder.length() - 1);
51954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
52054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else if (c == -1) {
52154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                return builder.toString();
52254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            } else {
52354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                builder.append((char) c);
52454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
52554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
52654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
52754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
52854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private void readEmptyLine(InputStream in) throws IOException {
52954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        String line = readAsciiUntilCrlf(in);
5307a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (line.length() != 0) {
53154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            throw new IllegalStateException("Expected empty but was: " + line);
53254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
53354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
53454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
53554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    /**
5367a68ed6a49c3060b235810391a82412a95f9c979jwilson     * Sets the dispatcher used to match incoming requests to mock responses.
5377a68ed6a49c3060b235810391a82412a95f9c979jwilson     * The default dispatcher simply serves a fixed sequence of responses from
5387a68ed6a49c3060b235810391a82412a95f9c979jwilson     * a {@link #enqueue(MockResponse) queue}; custom dispatchers can vary the
5397a68ed6a49c3060b235810391a82412a95f9c979jwilson     * response based on timing or the content of the request.
5407a68ed6a49c3060b235810391a82412a95f9c979jwilson     */
5417a68ed6a49c3060b235810391a82412a95f9c979jwilson    public void setDispatcher(Dispatcher dispatcher) {
5427a68ed6a49c3060b235810391a82412a95f9c979jwilson        if (dispatcher == null) {
5437a68ed6a49c3060b235810391a82412a95f9c979jwilson            throw new NullPointerException();
5447a68ed6a49c3060b235810391a82412a95f9c979jwilson        }
5457a68ed6a49c3060b235810391a82412a95f9c979jwilson        this.dispatcher = dispatcher;
5467a68ed6a49c3060b235810391a82412a95f9c979jwilson    }
5477a68ed6a49c3060b235810391a82412a95f9c979jwilson
5487a68ed6a49c3060b235810391a82412a95f9c979jwilson    /**
54954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     * An output stream that drops data after bodyLimit bytes.
55054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson     */
55154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private class TruncatingOutputStream extends ByteArrayOutputStream {
55254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        private int numBytesReceived = 0;
55354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(byte[] buffer, int offset, int len) {
55454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived += len;
55554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            super.write(buffer, offset, Math.min(len, bodyLimit - count));
55654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
55754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        @Override public void write(int oneByte) {
55854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            numBytesReceived++;
55954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            if (count < bodyLimit) {
56054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                super.write(oneByte);
56154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
56254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        }
56354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
56454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson
56554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    private static Runnable namedRunnable(final String name, final Runnable runnable) {
56654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        return new Runnable() {
56754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            public void run() {
56854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                String originalName = Thread.currentThread().getName();
56954ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                Thread.currentThread().setName(name);
57054ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                try {
57154ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    runnable.run();
57254ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                } finally {
57354ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                    Thread.currentThread().setName(originalName);
57454ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson                }
57554ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson            }
57654ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson        };
57754ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson    }
57854ce6cb5d13f732a3e71aa3555cd3709d5bf3cf5Jesse Wilson}
579