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