blob: 665d85abd2da067644dc4b8ebb32f55b02a479cc [file] [log] [blame]
/*
* 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 + "}";
}
}