| /* |
| * Copyright (C) 2011 Google Inc. |
| * |
| * 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 com.google.mockwebserver; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| |
| import static java.nio.charset.StandardCharsets.US_ASCII; |
| |
| /** |
| * A scripted response to be replayed by the mock web server. |
| */ |
| public final class MockResponse implements Cloneable { |
| private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked"; |
| |
| private String status = "HTTP/1.1 200 OK"; |
| private List<String> headers = new ArrayList<String>(); |
| |
| /** The response body content, or null if {@code bodyStream} is set. */ |
| private byte[] body; |
| /** The response body content, or null if {@code body} is set. */ |
| private InputStream bodyStream; |
| |
| private int throttleBytesPerPeriod = Integer.MAX_VALUE; |
| private long throttlePeriod = 1; |
| private TimeUnit throttleUnit = TimeUnit.SECONDS; |
| |
| private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN; |
| |
| private int bodyDelayTimeMs = 0; |
| |
| /** |
| * Creates a new mock response with an empty body. |
| */ |
| public MockResponse() { |
| setBody(new byte[0]); |
| } |
| |
| @Override public MockResponse clone() { |
| try { |
| MockResponse result = (MockResponse) super.clone(); |
| result.headers = new ArrayList<String>(result.headers); |
| return result; |
| } catch (CloneNotSupportedException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| public MockResponse setStatus(String status) { |
| this.status = status; |
| return this; |
| } |
| |
| /** |
| * Returns the HTTP headers, such as "Content-Length: 0". |
| */ |
| public List<String> getHeaders() { |
| return headers; |
| } |
| |
| /** |
| * Removes all HTTP headers including any "Content-Length" and |
| * "Transfer-encoding" headers that were added by default. |
| */ |
| public MockResponse clearHeaders() { |
| headers.clear(); |
| return this; |
| } |
| |
| /** |
| * Adds {@code header} as an HTTP header. For well-formed HTTP {@code |
| * header} should contain a name followed by a colon and a value. |
| */ |
| public MockResponse addHeader(String header) { |
| headers.add(header); |
| return this; |
| } |
| |
| /** |
| * Adds a new header with the name and value. This may be used to add |
| * multiple headers with the same name. |
| */ |
| public MockResponse addHeader(String name, Object value) { |
| return addHeader(name + ": " + String.valueOf(value)); |
| } |
| |
| /** |
| * Removes all headers named {@code name}, then adds a new header with the |
| * name and value. |
| */ |
| public MockResponse setHeader(String name, Object value) { |
| removeHeader(name); |
| return addHeader(name, value); |
| } |
| |
| /** |
| * Removes all headers named {@code name}. |
| */ |
| public MockResponse removeHeader(String name) { |
| name += ":"; |
| for (Iterator<String> i = headers.iterator(); i.hasNext(); ) { |
| String header = i.next(); |
| if (name.regionMatches(true, 0, header, 0, name.length())) { |
| i.remove(); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * Returns the raw HTTP payload, or null if this response is streamed. |
| */ |
| public byte[] getBody() { |
| return body; |
| } |
| |
| /** |
| * Returns an input stream containing the raw HTTP payload. |
| */ |
| InputStream getBodyStream() { |
| return bodyStream != null ? bodyStream : new ByteArrayInputStream(body); |
| } |
| |
| public MockResponse setBody(byte[] body) { |
| setHeader("Content-Length", body.length); |
| this.body = body; |
| this.bodyStream = null; |
| return this; |
| } |
| |
| public MockResponse setBody(InputStream bodyStream, long bodyLength) { |
| setHeader("Content-Length", bodyLength); |
| this.body = null; |
| this.bodyStream = bodyStream; |
| return this; |
| } |
| |
| /** |
| * Sets the response body to the UTF-8 encoded bytes of {@code body}. |
| */ |
| public MockResponse setBody(String body) { |
| try { |
| return setBody(body.getBytes("UTF-8")); |
| } catch (UnsupportedEncodingException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * Sets the response body to {@code body}, chunked every {@code |
| * maxChunkSize} bytes. |
| */ |
| public MockResponse setChunkedBody(byte[] body, int maxChunkSize) { |
| removeHeader("Content-Length"); |
| headers.add(CHUNKED_BODY_HEADER); |
| |
| try { |
| 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(US_ASCII)); |
| bytesOut.write("\r\n".getBytes(US_ASCII)); |
| bytesOut.write(body, pos, chunkSize); |
| bytesOut.write("\r\n".getBytes(US_ASCII)); |
| pos += chunkSize; |
| } |
| bytesOut.write("0\r\n\r\n".getBytes(US_ASCII)); // last chunk + empty trailer + crlf |
| |
| this.body = bytesOut.toByteArray(); |
| return this; |
| } catch (IOException e) { |
| throw new AssertionError(); // In-memory I/O doesn't throw IOExceptions. |
| } |
| } |
| |
| /** |
| * Sets the response body to the UTF-8 encoded bytes of {@code body}, |
| * chunked every {@code maxChunkSize} bytes. |
| */ |
| public MockResponse setChunkedBody(String body, int maxChunkSize) { |
| try { |
| return setChunkedBody(body.getBytes("UTF-8"), maxChunkSize); |
| } catch (UnsupportedEncodingException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| public SocketPolicy getSocketPolicy() { |
| return socketPolicy; |
| } |
| |
| public MockResponse setSocketPolicy(SocketPolicy socketPolicy) { |
| this.socketPolicy = socketPolicy; |
| return this; |
| } |
| |
| /** |
| * Throttles the response body writer to sleep for the given period after each |
| * series of {@code bytesPerPeriod} bytes are written. Use this to simulate |
| * network behavior. |
| */ |
| public MockResponse throttleBody(int bytesPerPeriod, long period, TimeUnit unit) { |
| this.throttleBytesPerPeriod = bytesPerPeriod; |
| this.throttlePeriod = period; |
| this.throttleUnit = unit; |
| return this; |
| } |
| |
| public int getThrottleBytesPerPeriod() { |
| return throttleBytesPerPeriod; |
| } |
| |
| public long getThrottlePeriod() { |
| return throttlePeriod; |
| } |
| |
| public TimeUnit getThrottleUnit() { |
| return throttleUnit; |
| } |
| |
| /** |
| * Set the delayed time of the response body to {@code delay}. This applies to the |
| * response body only; response headers are not affected. |
| */ |
| public MockResponse setBodyDelayTimeMs(int delay) { |
| bodyDelayTimeMs = delay; |
| return this; |
| } |
| |
| public int getBodyDelayTimeMs() { |
| return bodyDelayTimeMs; |
| } |
| |
| @Override public String toString() { |
| return "MockResponse{" + status + "}"; |
| } |
| } |