15f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen/*
25f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * Copyright (C) 2010 The Android Open Source Project
35f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen *
45f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * Licensed under the Apache License, Version 2.0 (the "License");
55f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * you may not use this file except in compliance with the License.
65f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * You may obtain a copy of the License at
75f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen *
85f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen *      http://www.apache.org/licenses/LICENSE-2.0
95f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen *
105f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * Unless required by applicable law or agreed to in writing, software
115f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * distributed under the License is distributed on an "AS IS" BASIS,
125f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * See the License for the specific language governing permissions and
145f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * limitations under the License.
155f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen */
165f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
175f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenpackage coretestutils.http;
185f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
195f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.BufferedInputStream;
205f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.BufferedOutputStream;
215f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.ByteArrayOutputStream;
225f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.File;
235f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.IOException;
245f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.InputStream;
255f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.io.OutputStream;
265f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.net.MalformedURLException;
275f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.net.ServerSocket;
285f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.net.Socket;
295f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.net.URL;
305f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.ArrayList;
315f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.LinkedList;
325f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.List;
335f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.Queue;
345f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.BlockingQueue;
355f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.Callable;
365f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.ExecutionException;
375f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.ExecutorService;
385f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.Executors;
395f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.Future;
405f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.LinkedBlockingQueue;
415f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.TimeUnit;
425f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport java.util.concurrent.TimeoutException;
435f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
445f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenimport android.util.Log;
455f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
465f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen/**
475f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * A scriptable web server. Callers supply canned responses and the server
485f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * replays them upon request in sequence.
495f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen *
505f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen * TODO: merge with the version from libcore/support/src/tests/java once it's in.
515f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen */
525f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyenpublic final class MockWebServer {
535f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    static final String ASCII = "US-ASCII";
545f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    static final String LOG_TAG = "coretestutils.http.MockWebServer";
555f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
565f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private final BlockingQueue<RecordedRequest> requestQueue
575f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            = new LinkedBlockingQueue<RecordedRequest>();
585f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private final BlockingQueue<MockResponse> responseQueue
595f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            = new LinkedBlockingQueue<MockResponse>();
605f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private int bodyLimit = Integer.MAX_VALUE;
615f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private final ExecutorService executor = Executors.newCachedThreadPool();
625f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    // keep Futures around so we can rethrow any exceptions thrown by Callables
635f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
645f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private final Object downloadPauseLock = new Object();
655f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    // global flag to signal when downloads should resume on the server
665f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private volatile boolean downloadResume = false;
675f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
685f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private int port = -1;
695f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
705f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public int getPort() {
715f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (port == -1) {
725f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            throw new IllegalStateException("Cannot retrieve port before calling play()");
735f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
745f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return port;
755f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
765f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
775f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
785f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Returns a URL for connecting to this server.
795f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     *
805f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * @param path the request path, such as "/".
815f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
825f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public URL getUrl(String path) throws MalformedURLException {
835f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return new URL("http://localhost:" + getPort() + path);
845f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
855f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
865f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
875f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Sets the number of bytes of the POST body to keep in memory to the given
885f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * limit.
895f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
905f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void setBodyLimit(int maxBodyLength) {
915f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        this.bodyLimit = maxBodyLength;
925f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
935f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
945f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void enqueue(MockResponse response) {
955f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        responseQueue.add(response);
965f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
975f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
985f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
995f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Awaits the next HTTP request, removes it, and returns it. Callers should
1005f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * use this to verify the request sent was as intended.
1015f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
1025f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public RecordedRequest takeRequest() throws InterruptedException {
1035f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return requestQueue.take();
1045f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1055f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1065f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
1075f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
1085f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1095f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1105f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public List<RecordedRequest> drainRequests() {
1115f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
1125f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        requestQueue.drainTo(requests);
1135f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return requests;
1145f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1155f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1165f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
1175f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Starts the server, serves all enqueued requests, and shuts the server
1185f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * down using the default (server-assigned) port.
1195f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
1205f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void play() throws IOException {
1215f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        play(0);
1225f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1235f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1245f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
1255f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Starts the server, serves all enqueued requests, and shuts the server
1265f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * down.
1275f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     *
1285f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * @param port The port number to use to listen to connections on; pass in 0 to have the
1295f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * server automatically assign a free port
1305f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
1315f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void play(int portNumber) throws IOException {
1325f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        final ServerSocket ss = new ServerSocket(portNumber);
1335f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        ss.setReuseAddress(true);
1345f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        port = ss.getLocalPort();
1355f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        submitCallable(new Callable<Void>() {
1365f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            public Void call() throws Exception {
1375f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                int count = 0;
1385f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                while (true) {
1395f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    if (count > 0 && responseQueue.isEmpty()) {
1405f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        ss.close();
1415f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        executor.shutdown();
1425f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        return null;
1435f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    }
1445f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1455f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    serveConnection(ss.accept());
1465f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    count++;
1475f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                }
1485f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
1495f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        });
1505f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1515f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1525f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private void serveConnection(final Socket s) {
1535f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        submitCallable(new Callable<Void>() {
1545f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            public Void call() throws Exception {
1555f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                InputStream in = new BufferedInputStream(s.getInputStream());
1565f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                OutputStream out = new BufferedOutputStream(s.getOutputStream());
1575f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1585f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                int sequenceNumber = 0;
1595f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                while (true) {
1605f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    RecordedRequest request = readRequest(in, sequenceNumber);
1615f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    if (request == null) {
1625f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        if (sequenceNumber == 0) {
1635f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            throw new IllegalStateException("Connection without any request!");
1645f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        } else {
1655f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            break;
1665f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        }
1675f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    }
1685f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    requestQueue.add(request);
1695f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    MockResponse response = computeResponse(request);
1705f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writeResponse(out, response);
1715f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    if (response.shouldCloseConnectionAfter()) {
1725f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        break;
1735f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    }
1745f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    sequenceNumber++;
1755f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                }
1765f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1775f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                in.close();
1785f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                out.close();
1795f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                return null;
1805f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
1815f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        });
1825f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1835f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1845f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private void submitCallable(Callable<?> callable) {
1855f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        Future<?> future = executor.submit(callable);
1865f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        futures.add(future);
1875f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
1885f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
1895f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
1905f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Check for and raise any exceptions that have been thrown by child threads.  Will not block on
1915f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * children still running.
1925f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * @throws ExecutionException for the first child thread that threw an exception
1935f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
1945f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void checkForExceptions() throws ExecutionException, InterruptedException {
1955f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        final int originalSize = futures.size();
1965f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        for (int i = 0; i < originalSize; i++) {
1975f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            Future<?> future = futures.remove();
1985f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            try {
1995f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                future.get(0, TimeUnit.SECONDS);
2005f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            } catch (TimeoutException e) {
2015f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                futures.add(future); // still running
2025f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2035f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2045f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
2055f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2065f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
2075f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * @param sequenceNumber the index of this request on this connection.
2085f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
2095f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
2105f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        String request = readAsciiUntilCrlf(in);
2115f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (request.equals("")) {
2125f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            return null; // end of data; no more requests
2135f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2145f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2155f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        List<String> headers = new ArrayList<String>();
2165f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        int contentLength = -1;
2175f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        boolean chunked = false;
2185f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        String header;
2195f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        while (!(header = readAsciiUntilCrlf(in)).equals("")) {
2205f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            headers.add(header);
2215f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            String lowercaseHeader = header.toLowerCase();
2225f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
2235f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                contentLength = Integer.parseInt(header.substring(15).trim());
2245f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2255f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (lowercaseHeader.startsWith("transfer-encoding:") &&
2265f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    lowercaseHeader.substring(18).trim().equals("chunked")) {
2275f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                chunked = true;
2285f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2295f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2305f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2315f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        boolean hasBody = false;
2325f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        TruncatingOutputStream requestBody = new TruncatingOutputStream();
2335f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        List<Integer> chunkSizes = new ArrayList<Integer>();
2345f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (contentLength != -1) {
2355f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            hasBody = true;
2365f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            transfer(contentLength, in, requestBody);
2375f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        } else if (chunked) {
2385f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            hasBody = true;
2395f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            while (true) {
2405f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
2415f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                if (chunkSize == 0) {
2425f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    readEmptyLine(in);
2435f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    break;
2445f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                }
2455f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                chunkSizes.add(chunkSize);
2465f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                transfer(chunkSize, in, requestBody);
2475f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                readEmptyLine(in);
2485f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2495f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2505f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2515f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (request.startsWith("GET ")) {
2525f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (hasBody) {
2535f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                throw new IllegalArgumentException("GET requests should not have a body!");
2545f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2555f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        } else if (request.startsWith("POST ")) {
2565f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (!hasBody) {
2575f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                throw new IllegalArgumentException("POST requests must have a body!");
2585f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2595f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        } else {
2605f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            throw new UnsupportedOperationException("Unexpected method: " + request);
2615f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2625f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2635f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return new RecordedRequest(request, headers, chunkSizes,
2645f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
2655f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
2665f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2675f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
2685f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Returns a response to satisfy {@code request}.
2695f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
2705f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
2715f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (responseQueue.isEmpty()) {
2725f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            throw new IllegalStateException("Unexpected request: " + request);
2735f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2745f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        return responseQueue.take();
2755f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
2765f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2775f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
2785f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
2795f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
2805f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2815f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        // Send headers
2825f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
2835f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        for (String header : response.getHeaders()) {
2845f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            out.write((header + "\r\n").getBytes(ASCII));
2855f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2865f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
2875f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                Log.i(LOG_TAG, "Closing connection after header" + header);
2885f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                break;
2895f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
2905f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
2915f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2925f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        // Send actual body data
2935f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (!doCloseConnectionAfterHeader) {
2945f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            out.write(("\r\n").getBytes(ASCII));
2955f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
2965f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            InputStream body = response.getBody();
2975f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            final int READ_BLOCK_SIZE = 10000;  // process blocks this size
2985f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            byte[] currentBlock = new byte[READ_BLOCK_SIZE];
2995f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int currentBlockSize = 0;
3005f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int writtenSoFar = 0;
3015f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3025f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            boolean shouldPause = response.getShouldPause();
3035f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            boolean shouldClose = response.getShouldClose();
3045f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int pause = response.getPauseConnectionAfterXBytes();
3055f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int close = response.getCloseConnectionAfterXBytes();
3065f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3075f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            // Don't bother pausing if it's set to pause -after- the connection should be dropped
3085f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (shouldPause && shouldClose && (pause > close)) {
3095f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                shouldPause = false;
3105f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
3115f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3125f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            // Process each block we read in...
3135f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            while ((currentBlockSize = body.read(currentBlock)) != -1) {
3145f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                int startIndex = 0;
3155f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                int writeLength = currentBlockSize;
3165f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3175f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                // handle the case of pausing
3185f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
3195f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writeLength = pause - writtenSoFar;
3205f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    out.write(currentBlock, 0, writeLength);
3215f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    out.flush();
3225f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writtenSoFar += writeLength;
3235f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3245f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    // now pause...
3255f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    try {
3265f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
3275f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        // Wait until someone tells us to resume sending...
3285f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        synchronized(downloadPauseLock) {
3295f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            while (!downloadResume) {
3305f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                                downloadPauseLock.wait();
3315f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            }
3325f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            // reset resume back to false
3335f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                            downloadResume = false;
3345f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        }
3355f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    } catch (InterruptedException e) {
3365f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                        Log.e(LOG_TAG, "Server was interrupted during pause in download.");
3375f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    }
3385f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3395f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    startIndex = writeLength;
3405f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writeLength = currentBlockSize - writeLength;
3415f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                }
3425f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3435f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                // handle the case of closing the connection
3445f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                if (shouldClose && (writtenSoFar + writeLength > close)) {
3455f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writeLength = close - writtenSoFar;
3465f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    out.write(currentBlock, startIndex, writeLength);
3475f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    writtenSoFar += writeLength;
3485f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
3495f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                    break;
3505f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                }
3515f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                out.write(currentBlock, startIndex, writeLength);
3525f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                writtenSoFar += writeLength;
3535f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
3545f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
3555f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        out.flush();
3565f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
3575f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3585f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
3595f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Transfer bytes from {@code in} to {@code out} until either {@code length}
3605f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * bytes have been transferred or {@code in} is exhausted.
3615f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
3625f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
3635f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        byte[] buffer = new byte[1024];
3645f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        while (length > 0) {
3655f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int count = in.read(buffer, 0, Math.min(buffer.length, length));
3665f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (count == -1) {
3675f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                return;
3685f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
3695f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            out.write(buffer, 0, count);
3705f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            length -= count;
3715f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
3725f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
3735f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3745f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
3755f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Returns the text from {@code in} until the next "\r\n", or null if
3765f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * {@code in} is exhausted.
3775f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
3785f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private String readAsciiUntilCrlf(InputStream in) throws IOException {
3795f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        StringBuilder builder = new StringBuilder();
3805f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        while (true) {
3815f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            int c = in.read();
3825f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
3835f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                builder.deleteCharAt(builder.length() - 1);
3845f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                return builder.toString();
3855f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            } else if (c == -1) {
3865f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                return builder.toString();
3875f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            } else {
3885f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                builder.append((char) c);
3895f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
3905f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
3915f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
3925f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
3935f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private void readEmptyLine(InputStream in) throws IOException {
3945f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        String line = readAsciiUntilCrlf(in);
3955f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        if (!line.equals("")) {
3965f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            throw new IllegalStateException("Expected empty but was: " + line);
3975f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
3985f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
3995f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
4005f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
4015f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * An output stream that drops data after bodyLimit bytes.
4025f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
4035f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    private class TruncatingOutputStream extends ByteArrayOutputStream {
4045f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        private int numBytesReceived = 0;
4055f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        @Override public void write(byte[] buffer, int offset, int len) {
4065f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            numBytesReceived += len;
4075f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            super.write(buffer, offset, Math.min(len, bodyLimit - count));
4085f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
4095f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        @Override public void write(int oneByte) {
4105f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            numBytesReceived++;
4115f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            if (count < bodyLimit) {
4125f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen                super.write(oneByte);
4135f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            }
4145f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
4155f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
4165f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen
4175f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    /**
4185f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     * Trigger the server to resume sending the download
4195f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen     */
4205f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    public void doResumeDownload() {
4215f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        synchronized (downloadPauseLock) {
4225f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            downloadResume = true;
4235f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen            downloadPauseLock.notifyAll();
4245f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen        }
4255f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen    }
4265f53bca55b2c9e217dee12bff8ce55e168829783Neal Nguyen}
427