diff options
author | Jeff Sharkey <jsharkey@android.com> | 2012-03-29 13:38:24 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2012-03-29 15:10:31 -0700 |
commit | ae6856b0fca5215f45619dd031a7e7beae7bd8cc (patch) | |
tree | f54a7fcfc663db7a22e57f3f81d77f1c98f70fa9 /tests/src/tests/http | |
parent | db4c69fbb558efe193ec81d593fc3868268ba2cf (diff) | |
download | android_packages_providers_DownloadProvider-ae6856b0fca5215f45619dd031a7e7beae7bd8cc.tar.gz android_packages_providers_DownloadProvider-ae6856b0fca5215f45619dd031a7e7beae7bd8cc.tar.bz2 android_packages_providers_DownloadProvider-ae6856b0fca5215f45619dd031a7e7beae7bd8cc.zip |
Migrate to shared MockWebServer.
Bug: 4726601
Change-Id: Ibe537bd5c2a092dbf974360cd454751881f7f4ea
Diffstat (limited to 'tests/src/tests/http')
-rw-r--r-- | tests/src/tests/http/MockResponse.java | 147 | ||||
-rw-r--r-- | tests/src/tests/http/MockWebServer.java | 356 | ||||
-rw-r--r-- | tests/src/tests/http/RecordedRequest.java | 93 |
3 files changed, 0 insertions, 596 deletions
diff --git a/tests/src/tests/http/MockResponse.java b/tests/src/tests/http/MockResponse.java deleted file mode 100644 index aec5490c..00000000 --- a/tests/src/tests/http/MockResponse.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package tests.http; - -import static tests.http.MockWebServer.ASCII; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A scripted response to be replayed by the mock web server. - */ -public class MockResponse { - private static final byte[] EMPTY_BODY = new byte[0]; - - private String status = "HTTP/1.1 200 OK"; - private Map<String, String> headers = new HashMap<String, String>(); - private byte[] body = EMPTY_BODY; - private boolean closeConnectionAfter = false; - private int numPackets = 0; - - public MockResponse() { - addHeader("Content-Length", 0); - } - - /** - * Returns the HTTP response line, such as "HTTP/1.1 200 OK". - */ - public String getStatus() { - return status; - } - - public MockResponse setResponseCode(int code) { - this.status = "HTTP/1.1 " + code + " OK"; - return this; - } - - /** - * Returns the HTTP headers, such as "Content-Length: 0". - */ - public List<String> getHeaders() { - List<String> headerStrings = new ArrayList<String>(); - for (String header : headers.keySet()) { - headerStrings.add(header + ": " + headers.get(header)); - } - return headerStrings; - } - - public MockResponse addHeader(String header, String value) { - headers.put(header.toLowerCase(), value); - return this; - } - - public MockResponse addHeader(String header, long value) { - return addHeader(header, Long.toString(value)); - } - - public MockResponse removeHeader(String header) { - headers.remove(header.toLowerCase()); - return this; - } - - /** - * Returns an input stream containing the raw HTTP payload. - */ - public byte[] getBody() { - return body; - } - - public MockResponse setBody(byte[] body) { - addHeader("Content-Length", body.length); - this.body = body; - return this; - } - - public MockResponse setBody(String body) { - try { - return setBody(body.getBytes(ASCII)); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } - } - - public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException { - addHeader("Transfer-encoding", "chunked"); - - ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); - int pos = 0; - while (pos < body.length) { - int chunkSize = Math.min(body.length - pos, maxChunkSize); - bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII)); - bytesOut.write("\r\n".getBytes(ASCII)); - bytesOut.write(body, pos, chunkSize); - bytesOut.write("\r\n".getBytes(ASCII)); - pos += chunkSize; - } - bytesOut.write("0\r\n".getBytes(ASCII)); - this.body = bytesOut.toByteArray(); - return this; - } - - public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException { - return setChunkedBody(body.getBytes(ASCII), maxChunkSize); - } - - @Override public String toString() { - return status; - } - - public boolean shouldCloseConnectionAfter() { - return closeConnectionAfter; - } - - public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) { - this.closeConnectionAfter = closeConnectionAfter; - return this; - } - - public int getNumPackets() { - return numPackets; - } - - public MockResponse setNumPackets(int numPackets) { - this.numPackets = numPackets; - return this; - } - -} diff --git a/tests/src/tests/http/MockWebServer.java b/tests/src/tests/http/MockWebServer.java deleted file mode 100644 index 6096783d..00000000 --- a/tests/src/tests/http/MockWebServer.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package tests.http; - -import android.text.TextUtils; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URL; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * A scriptable web server. Callers supply canned responses and the server - * replays them upon request in sequence. - * - * TODO: merge with the version from libcore/support/src/tests/java once it's in. - */ -public final class MockWebServer { - static final String ASCII = "US-ASCII"; - - private final BlockingQueue<RecordedRequest> requestQueue - = new LinkedBlockingQueue<RecordedRequest>(); - private final BlockingQueue<MockResponse> responseQueue - = new LinkedBlockingQueue<MockResponse>(); - private int bodyLimit = Integer.MAX_VALUE; - private final ExecutorService executor = Executors.newCachedThreadPool(); - // keep Futures around so we can rethrow any exceptions thrown by Callables - private final Queue<Future<?>> futures = new LinkedList<Future<?>>(); - - private int port = -1; - private ServerSocket serverSocket; - - public int getPort() { - if (port == -1) { - throw new IllegalStateException("Cannot retrieve port before calling play()"); - } - return port; - } - - /** - * Returns a URL for connecting to this server. - * - * @param path the request path, such as "/". - */ - public URL getUrl(String path) throws MalformedURLException { - return new URL("http://localhost:" + getPort() + path); - } - - /** - * Sets the number of bytes of the POST body to keep in memory to the given - * limit. - */ - public void setBodyLimit(int maxBodyLength) { - this.bodyLimit = maxBodyLength; - } - - public void enqueue(MockResponse response) { - responseQueue.add(response); - } - - /** - * Awaits the next HTTP request, removes it, and returns it. Callers should - * use this to verify the request sent was as intended. - */ - public RecordedRequest takeRequest() throws InterruptedException { - return requestQueue.take(); - } - - public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException { - return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); - } - - public List<RecordedRequest> drainRequests() { - List<RecordedRequest> requests = new ArrayList<RecordedRequest>(); - requestQueue.drainTo(requests); - return requests; - } - - /** - * Starts the server, serves all enqueued requests, and shuts the server - * down. - */ - public void play() throws IOException { - serverSocket = new ServerSocket(0); - serverSocket.setReuseAddress(true); - port = serverSocket.getLocalPort(); - submitCallable(new Callable<Void>() { - public Void call() throws Exception { - int count = 0; - while (true) { - if (count > 0 && responseQueue.isEmpty()) { - serverSocket.close(); - executor.shutdown(); - return null; - } - - serveConnection(serverSocket.accept()); - count++; - } - } - }); - } - - /** - * shutdown the webserver - */ - public void shutdown() throws IOException { - responseQueue.clear(); - serverSocket.close(); - executor.shutdown(); - } - - private void serveConnection(final Socket s) { - submitCallable(new Callable<Void>() { - public Void call() throws Exception { - InputStream in = new BufferedInputStream(s.getInputStream()); - OutputStream out = new BufferedOutputStream(s.getOutputStream()); - - int sequenceNumber = 0; - while (true) { - RecordedRequest request = readRequest(in, sequenceNumber); - if (request == null) { - if (sequenceNumber == 0) { - throw new IllegalStateException("Connection without any request!"); - } else { - break; - } - } - requestQueue.add(request); - MockResponse response = sendResponse(out, request); - if (response.shouldCloseConnectionAfter()) { - break; - } - sequenceNumber++; - } - - in.close(); - out.close(); - return null; - } - }); - } - - private void submitCallable(Callable<?> callable) { - Future<?> future = executor.submit(callable); - futures.add(future); - } - - /** - * Check for and raise any exceptions that have been thrown by child threads. Will not block on - * children still running. - * @throws ExecutionException for the first child thread that threw an exception - */ - public void checkForExceptions() throws ExecutionException, InterruptedException { - final int originalSize = futures.size(); - for (int i = 0; i < originalSize; i++) { - Future<?> future = futures.remove(); - try { - future.get(0, TimeUnit.SECONDS); - } catch (TimeoutException e) { - futures.add(future); // still running - } - } - } - - /** - * @param sequenceNumber the index of this request on this connection. - */ - private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { - String request = readAsciiUntilCrlf(in); - if (request.equals("")) { - return null; // end of data; no more requests - } - - List<String> headers = new ArrayList<String>(); - int contentLength = -1; - boolean chunked = false; - String header; - while (!(header = readAsciiUntilCrlf(in)).equals("")) { - headers.add(header); - String lowercaseHeader = header.toLowerCase(); - if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { - contentLength = Integer.parseInt(header.substring(15).trim()); - } - if (lowercaseHeader.startsWith("transfer-encoding:") && - lowercaseHeader.substring(18).trim().equals("chunked")) { - chunked = true; - } - } - - boolean hasBody = false; - TruncatingOutputStream requestBody = new TruncatingOutputStream(); - List<Integer> chunkSizes = new ArrayList<Integer>(); - if (contentLength != -1) { - hasBody = true; - transfer(contentLength, in, requestBody); - } else if (chunked) { - hasBody = true; - while (true) { - int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); - if (chunkSize == 0) { - readEmptyLine(in); - break; - } - chunkSizes.add(chunkSize); - transfer(chunkSize, in, requestBody); - readEmptyLine(in); - } - } - - if (request.startsWith("GET ")) { - if (hasBody) { - throw new IllegalArgumentException("GET requests should not have a body!"); - } - } else if (request.startsWith("POST ")) { - if (!hasBody) { - throw new IllegalArgumentException("POST requests must have a body!"); - } - } else { - throw new UnsupportedOperationException("Unexpected method: " + request); - } - return new RecordedRequest(request, headers, chunkSizes, - requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); - } - - /** - * Returns a response to satisfy {@code request}. - */ - private MockResponse sendResponse(OutputStream out, RecordedRequest request) - throws InterruptedException, IOException { - if (responseQueue.isEmpty()) { - throw new IllegalStateException("Unexpected request: " + request); - } - MockResponse response = responseQueue.take(); - writeResponse(out, response, false); - if (response.getNumPackets() > 0) { - // there are continuing packets to send as part of this response. - for (int i = 0; i < response.getNumPackets(); i++) { - writeResponse(out, response, true); - // delay sending next continuing response just a little bit - Thread.sleep(100); - } - } - return response; - } - - private void writeResponse(OutputStream out, MockResponse response, - boolean continuingPacket) throws IOException { - if (continuingPacket) { - // this is a continuing response - just send the body - no headers, status - out.write(response.getBody()); - out.flush(); - return; - } - out.write((response.getStatus() + "\r\n").getBytes(ASCII)); - for (String header : response.getHeaders()) { - out.write((header + "\r\n").getBytes(ASCII)); - } - out.write(("\r\n").getBytes(ASCII)); - out.write(response.getBody()); - out.flush(); - } - - /** - * Transfer bytes from {@code in} to {@code out} until either {@code length} - * bytes have been transferred or {@code in} is exhausted. - */ - private void transfer(int length, InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - while (length > 0) { - int count = in.read(buffer, 0, Math.min(buffer.length, length)); - if (count == -1) { - return; - } - out.write(buffer, 0, count); - length -= count; - } - } - - /** - * Returns the text from {@code in} until the next "\r\n", or null if - * {@code in} is exhausted. - */ - private String readAsciiUntilCrlf(InputStream in) throws IOException { - StringBuilder builder = new StringBuilder(); - while (true) { - int c = in.read(); - if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); - } else if (c == -1) { - return builder.toString(); - } else { - builder.append((char) c); - } - } - } - - private void readEmptyLine(InputStream in) throws IOException { - String line = readAsciiUntilCrlf(in); - if (!line.equals("")) { - throw new IllegalStateException("Expected empty but was: " + line); - } - } - - /** - * An output stream that drops data after bodyLimit bytes. - */ - private class TruncatingOutputStream extends ByteArrayOutputStream { - private int numBytesReceived = 0; - @Override public void write(byte[] buffer, int offset, int len) { - numBytesReceived += len; - super.write(buffer, offset, Math.min(len, bodyLimit - count)); - } - @Override public void write(int oneByte) { - numBytesReceived++; - if (count < bodyLimit) { - super.write(oneByte); - } - } - } -} diff --git a/tests/src/tests/http/RecordedRequest.java b/tests/src/tests/http/RecordedRequest.java deleted file mode 100644 index 6b67af20..00000000 --- a/tests/src/tests/http/RecordedRequest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package tests.http; - -import java.util.List; - -/** - * An HTTP request that came into the mock web server. - */ -public final class RecordedRequest { - private final String requestLine; - private final List<String> headers; - private final List<Integer> chunkSizes; - private final int bodySize; - private final byte[] body; - private final int sequenceNumber; - - RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes, - int bodySize, byte[] body, int sequenceNumber) { - this.requestLine = requestLine; - this.headers = headers; - this.chunkSizes = chunkSizes; - this.bodySize = bodySize; - this.body = body; - this.sequenceNumber = sequenceNumber; - } - - public String getRequestLine() { - return requestLine; - } - - public List<String> getHeaders() { - return headers; - } - - /** - * Returns the sizes of the chunks of this request's body, or an empty list - * if the request's body was empty or unchunked. - */ - public List<Integer> getChunkSizes() { - return chunkSizes; - } - - /** - * Returns the total size of the body of this POST request (before - * truncation). - */ - public int getBodySize() { - return bodySize; - } - - /** - * Returns the body of this POST request. This may be truncated. - */ - public byte[] getBody() { - return body; - } - - /** - * Returns the index of this request on its HTTP connection. Since a single - * HTTP connection may serve multiple requests, each request is assigned its - * own sequence number. - */ - public int getSequenceNumber() { - return sequenceNumber; - } - - @Override public String toString() { - return requestLine; - } - - public String getMethod() { - return getRequestLine().split(" ")[0]; - } - - public String getPath() { - return getRequestLine().split(" ")[1]; - } -} |