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