14c54154c83a5b3516d81903140b31349f196cbe2Steve Howard/*
24c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * Copyright (C) 2010 The Android Open Source Project
34c54154c83a5b3516d81903140b31349f196cbe2Steve Howard *
44c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * Licensed under the Apache License, Version 2.0 (the "License");
54c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * you may not use this file except in compliance with the License.
64c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * You may obtain a copy of the License at
74c54154c83a5b3516d81903140b31349f196cbe2Steve Howard *
84c54154c83a5b3516d81903140b31349f196cbe2Steve Howard *      http://www.apache.org/licenses/LICENSE-2.0
94c54154c83a5b3516d81903140b31349f196cbe2Steve Howard *
104c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * Unless required by applicable law or agreed to in writing, software
114c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * distributed under the License is distributed on an "AS IS" BASIS,
124c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * See the License for the specific language governing permissions and
144c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * limitations under the License.
154c54154c83a5b3516d81903140b31349f196cbe2Steve Howard */
164c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
174c54154c83a5b3516d81903140b31349f196cbe2Steve Howardpackage tests.http;
184c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
194c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.BufferedInputStream;
204c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.BufferedOutputStream;
214c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.ByteArrayOutputStream;
224c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.IOException;
234c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.InputStream;
244c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.io.OutputStream;
254c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.net.MalformedURLException;
264c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.net.ServerSocket;
274c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.net.Socket;
284c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.net.URL;
294c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.ArrayList;
3023357198c440e6872d3aef3e608295db7f8273bcSteve Howardimport java.util.LinkedList;
314c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.List;
3223357198c440e6872d3aef3e608295db7f8273bcSteve Howardimport java.util.Queue;
334c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.concurrent.BlockingQueue;
344c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.concurrent.Callable;
3523357198c440e6872d3aef3e608295db7f8273bcSteve Howardimport java.util.concurrent.ExecutionException;
364c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.concurrent.ExecutorService;
374c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.concurrent.Executors;
3823357198c440e6872d3aef3e608295db7f8273bcSteve Howardimport java.util.concurrent.Future;
394c54154c83a5b3516d81903140b31349f196cbe2Steve Howardimport java.util.concurrent.LinkedBlockingQueue;
40d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howardimport java.util.concurrent.TimeUnit;
4123357198c440e6872d3aef3e608295db7f8273bcSteve Howardimport java.util.concurrent.TimeoutException;
424c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
434c54154c83a5b3516d81903140b31349f196cbe2Steve Howard/**
444c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * A scriptable web server. Callers supply canned responses and the server
454c54154c83a5b3516d81903140b31349f196cbe2Steve Howard * replays them upon request in sequence.
46d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard *
47d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard * TODO: merge with the version from libcore/support/src/tests/java once it's in.
484c54154c83a5b3516d81903140b31349f196cbe2Steve Howard */
494c54154c83a5b3516d81903140b31349f196cbe2Steve Howardpublic final class MockWebServer {
504c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    static final String ASCII = "US-ASCII";
514c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
524c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private final BlockingQueue<RecordedRequest> requestQueue
534c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            = new LinkedBlockingQueue<RecordedRequest>();
544c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private final BlockingQueue<MockResponse> responseQueue
55d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard            = new LinkedBlockingQueue<MockResponse>();
564c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private int bodyLimit = Integer.MAX_VALUE;
574c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private final ExecutorService executor = Executors.newCachedThreadPool();
5823357198c440e6872d3aef3e608295db7f8273bcSteve Howard    // keep Futures around so we can rethrow any exceptions thrown by Callables
5923357198c440e6872d3aef3e608295db7f8273bcSteve Howard    private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
604c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
614c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private int port = -1;
624c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
634c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public int getPort() {
644c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        if (port == -1) {
654c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            throw new IllegalStateException("Cannot retrieve port before calling play()");
664c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
674c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        return port;
684c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
694c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
704c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
714c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Returns a URL for connecting to this server.
724c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     *
734c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * @param path the request path, such as "/".
744c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
754c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public URL getUrl(String path) throws MalformedURLException {
764c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        return new URL("http://localhost:" + getPort() + path);
774c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
784c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
794c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
804c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Sets the number of bytes of the POST body to keep in memory to the given
814c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * limit.
824c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
834c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public void setBodyLimit(int maxBodyLength) {
844c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        this.bodyLimit = maxBodyLength;
854c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
864c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
874c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public void enqueue(MockResponse response) {
884c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        responseQueue.add(response);
894c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
904c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
914c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
924c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Awaits the next HTTP request, removes it, and returns it. Callers should
934c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * use this to verify the request sent was as intended.
944c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
954c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public RecordedRequest takeRequest() throws InterruptedException {
964c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        return requestQueue.take();
974c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
984c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
99d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard    public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
100d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
101d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard    }
102d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard
103d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard    public List<RecordedRequest> drainRequests() {
104d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
105d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        requestQueue.drainTo(requests);
106d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        return requests;
107d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard    }
108d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard
1094c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
1104c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Starts the server, serves all enqueued requests, and shuts the server
1114c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * down.
1124c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
1134c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    public void play() throws IOException {
1144c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        final ServerSocket ss = new ServerSocket(0);
1154c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        ss.setReuseAddress(true);
1164c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        port = ss.getLocalPort();
11723357198c440e6872d3aef3e608295db7f8273bcSteve Howard        submitCallable(new Callable<Void>() {
1184c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            public Void call() throws Exception {
1194c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                int count = 0;
1204c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                while (true) {
1214c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    if (count > 0 && responseQueue.isEmpty()) {
1224c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        ss.close();
1234c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        executor.shutdown();
1244c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        return null;
1254c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    }
1264c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
1274c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    serveConnection(ss.accept());
1284c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    count++;
1294c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                }
1304c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
1314c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        });
1324c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
1334c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
1344c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private void serveConnection(final Socket s) {
13523357198c440e6872d3aef3e608295db7f8273bcSteve Howard        submitCallable(new Callable<Void>() {
1364c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            public Void call() throws Exception {
1374c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                InputStream in = new BufferedInputStream(s.getInputStream());
1384c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                OutputStream out = new BufferedOutputStream(s.getOutputStream());
1394c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
1404c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                int sequenceNumber = 0;
1414c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                while (true) {
1424c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    RecordedRequest request = readRequest(in, sequenceNumber);
1434c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    if (request == null) {
1444c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        if (sequenceNumber == 0) {
1454c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                            throw new IllegalStateException("Connection without any request!");
1464c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        } else {
1474c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                            break;
1484c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                        }
1494c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    }
1504c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    requestQueue.add(request);
151d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard                    MockResponse response = computeResponse(request);
152d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard                    writeResponse(out, response);
153d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard                    if (response.shouldCloseConnectionAfter()) {
154d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard                        break;
155d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard                    }
1564c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    sequenceNumber++;
1574c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                }
1584c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
1594c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                in.close();
1604c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                out.close();
1614c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                return null;
1624c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
1634c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        });
1644c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
1654c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
16623357198c440e6872d3aef3e608295db7f8273bcSteve Howard    private void submitCallable(Callable<?> callable) {
16723357198c440e6872d3aef3e608295db7f8273bcSteve Howard        Future<?> future = executor.submit(callable);
16823357198c440e6872d3aef3e608295db7f8273bcSteve Howard        futures.add(future);
16923357198c440e6872d3aef3e608295db7f8273bcSteve Howard    }
17023357198c440e6872d3aef3e608295db7f8273bcSteve Howard
17123357198c440e6872d3aef3e608295db7f8273bcSteve Howard    /**
17223357198c440e6872d3aef3e608295db7f8273bcSteve Howard     * Check for and raise any exceptions that have been thrown by child threads.  Will not block on
17323357198c440e6872d3aef3e608295db7f8273bcSteve Howard     * children still running.
17423357198c440e6872d3aef3e608295db7f8273bcSteve Howard     * @throws ExecutionException for the first child thread that threw an exception
17523357198c440e6872d3aef3e608295db7f8273bcSteve Howard     */
17623357198c440e6872d3aef3e608295db7f8273bcSteve Howard    public void checkForExceptions() throws ExecutionException, InterruptedException {
17723357198c440e6872d3aef3e608295db7f8273bcSteve Howard        final int originalSize = futures.size();
17823357198c440e6872d3aef3e608295db7f8273bcSteve Howard        for (int i = 0; i < originalSize; i++) {
17923357198c440e6872d3aef3e608295db7f8273bcSteve Howard            Future<?> future = futures.remove();
18023357198c440e6872d3aef3e608295db7f8273bcSteve Howard            try {
18123357198c440e6872d3aef3e608295db7f8273bcSteve Howard                future.get(0, TimeUnit.SECONDS);
18223357198c440e6872d3aef3e608295db7f8273bcSteve Howard            } catch (TimeoutException e) {
18323357198c440e6872d3aef3e608295db7f8273bcSteve Howard                futures.add(future); // still running
18423357198c440e6872d3aef3e608295db7f8273bcSteve Howard            }
18523357198c440e6872d3aef3e608295db7f8273bcSteve Howard        }
18623357198c440e6872d3aef3e608295db7f8273bcSteve Howard    }
18723357198c440e6872d3aef3e608295db7f8273bcSteve Howard
1884c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
1894c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * @param sequenceNumber the index of this request on this connection.
1904c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
1914c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
1924c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        String request = readAsciiUntilCrlf(in);
193d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        if (request.equals("")) {
1944c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            return null; // end of data; no more requests
1954c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
1964c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
1974c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        List<String> headers = new ArrayList<String>();
1984c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        int contentLength = -1;
1994c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        boolean chunked = false;
2004c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        String header;
201d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        while (!(header = readAsciiUntilCrlf(in)).equals("")) {
2024c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            headers.add(header);
2034c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            String lowercaseHeader = header.toLowerCase();
2044c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
2054c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                contentLength = Integer.parseInt(header.substring(15).trim());
2064c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2074c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (lowercaseHeader.startsWith("transfer-encoding:") &&
2084c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    lowercaseHeader.substring(18).trim().equals("chunked")) {
2094c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                chunked = true;
2104c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2114c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2124c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2134c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        boolean hasBody = false;
2144c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        TruncatingOutputStream requestBody = new TruncatingOutputStream();
2154c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        List<Integer> chunkSizes = new ArrayList<Integer>();
2164c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        if (contentLength != -1) {
2174c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            hasBody = true;
2184c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            transfer(contentLength, in, requestBody);
2194c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        } else if (chunked) {
2204c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            hasBody = true;
2214c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            while (true) {
2224c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
2234c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                if (chunkSize == 0) {
2244c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    readEmptyLine(in);
2254c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                    break;
2264c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                }
2274c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                chunkSizes.add(chunkSize);
2284c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                transfer(chunkSize, in, requestBody);
2294c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                readEmptyLine(in);
2304c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2314c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2324c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2334c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        if (request.startsWith("GET ")) {
2344c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (hasBody) {
2354c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                throw new IllegalArgumentException("GET requests should not have a body!");
2364c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2374c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        } else if (request.startsWith("POST ")) {
2384c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (!hasBody) {
2394c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                throw new IllegalArgumentException("POST requests must have a body!");
2404c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2414c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        } else {
2424c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            throw new UnsupportedOperationException("Unexpected method: " + request);
2434c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2444c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2454c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        return new RecordedRequest(request, headers, chunkSizes,
2464c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
2474c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
2484c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2494c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
2504c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Returns a response to satisfy {@code request}.
2514c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
2524c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
2534c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        if (responseQueue.isEmpty()) {
2544c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            throw new IllegalStateException("Unexpected request: " + request);
2554c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2564c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        return responseQueue.take();
2574c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
2584c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2594c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private void writeResponse(OutputStream out, MockResponse response) throws IOException {
2604c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        out.write((response.getStatus() + "\r\n").getBytes(ASCII));
2614c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        for (String header : response.getHeaders()) {
2624c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            out.write((header + "\r\n").getBytes(ASCII));
2634c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2644c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        out.write(("\r\n").getBytes(ASCII));
2654c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        out.write(response.getBody());
2664c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        out.flush();
2674c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
2684c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2694c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
2704c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Transfer bytes from {@code in} to {@code out} until either {@code length}
2714c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * bytes have been transferred or {@code in} is exhausted.
2724c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
2734c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private void transfer(int length, InputStream in, OutputStream out) throws IOException {
2744c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        byte[] buffer = new byte[1024];
2754c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        while (length > 0) {
2764c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            int count = in.read(buffer, 0, Math.min(buffer.length, length));
2774c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (count == -1) {
2784c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                return;
2794c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
2804c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            out.write(buffer, 0, count);
2814c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            length -= count;
2824c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
2834c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
2844c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
2854c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
2864c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * Returns the text from {@code in} until the next "\r\n", or null if
2874c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * {@code in} is exhausted.
2884c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
2894c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private String readAsciiUntilCrlf(InputStream in) throws IOException {
2904c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        StringBuilder builder = new StringBuilder();
2914c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        while (true) {
2924c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            int c = in.read();
2934c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
2944c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                builder.deleteCharAt(builder.length() - 1);
2954c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                return builder.toString();
2964c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            } else if (c == -1) {
2974c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                return builder.toString();
2984c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            } else {
2994c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                builder.append((char) c);
3004c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
3014c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
3024c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
3034c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
3044c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private void readEmptyLine(InputStream in) throws IOException {
3054c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        String line = readAsciiUntilCrlf(in);
306d6f9b5e72a135365f2358d79b3ea3c9f7cb99c8eSteve Howard        if (!line.equals("")) {
3074c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            throw new IllegalStateException("Expected empty but was: " + line);
3084c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
3094c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
3104c54154c83a5b3516d81903140b31349f196cbe2Steve Howard
3114c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    /**
3124c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     * An output stream that drops data after bodyLimit bytes.
3134c54154c83a5b3516d81903140b31349f196cbe2Steve Howard     */
3144c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    private class TruncatingOutputStream extends ByteArrayOutputStream {
3154c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        private int numBytesReceived = 0;
3164c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        @Override public void write(byte[] buffer, int offset, int len) {
3174c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            numBytesReceived += len;
3184c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            super.write(buffer, offset, Math.min(len, bodyLimit - count));
3194c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
3204c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        @Override public void write(int oneByte) {
3214c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            numBytesReceived++;
3224c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            if (count < bodyLimit) {
3234c54154c83a5b3516d81903140b31349f196cbe2Steve Howard                super.write(oneByte);
3244c54154c83a5b3516d81903140b31349f196cbe2Steve Howard            }
3254c54154c83a5b3516d81903140b31349f196cbe2Steve Howard        }
3264c54154c83a5b3516d81903140b31349f196cbe2Steve Howard    }
3274c54154c83a5b3516d81903140b31349f196cbe2Steve Howard}
328