blob: a12abf5e959fe90d28a5820c53b49c1bbc022979 [file] [log] [blame]
/*
* Copyright (C) 2013 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 static com.google.mockwebserver.MockWebServer.ASCII;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Base scripted response to be replayed by {@link MockWebServer}.
*/
abstract class BaseMockResponse<T extends BaseMockResponse<T>> {
protected static final String CONTENT_LENGTH = "Content-Length";
private String status = "HTTP/1.1 200 OK";
private List<String> headers = new ArrayList<String>();
private int bytesPerSecond = Integer.MAX_VALUE;
private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
protected BaseMockResponse() {
}
@Override
protected Object clone() throws CloneNotSupportedException {
final BaseMockResponse<?> result = (BaseMockResponse<?>) super.clone();
result.headers = new ArrayList<String>(result.headers);
return result;
}
/**
* Returns the HTTP response line, such as "HTTP/1.1 200 OK".
*/
public String getStatus() {
return status;
}
public T setResponseCode(int code) {
this.status = "HTTP/1.1 " + code + " OK";
return self();
}
public T setStatus(String status) {
this.status = status;
return self();
}
/**
* Returns the HTTP headers, such as "Content-Length: 0".
*/
public List<String> getHeaders() {
return headers;
}
public T clearHeaders() {
headers.clear();
return self();
}
public T addHeader(String header) {
headers.add(header);
return self();
}
public T addHeader(String name, Object value) {
return addHeader(name + ": " + String.valueOf(value));
}
public T setHeader(String name, Object value) {
removeHeader(name);
return addHeader(name, value);
}
public T 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 self();
}
public SocketPolicy getSocketPolicy() {
return socketPolicy;
}
public T setSocketPolicy(SocketPolicy socketPolicy) {
this.socketPolicy = socketPolicy;
return self();
}
public int getBytesPerSecond() {
return bytesPerSecond;
}
/**
* Set simulated network speed, in bytes per second.
*/
public T setBytesPerSecond(int bytesPerSecond) {
this.bytesPerSecond = bytesPerSecond;
return self();
}
@Override public String toString() {
return status;
}
/**
* Write complete response, including all headers and the given body.
* Handles applying {@link #setBytesPerSecond(int)} limits.
*/
protected void writeResponse(InputStream body, OutputStream out) throws IOException {
out.write((getStatus() + "\r\n").getBytes(ASCII));
for (String header : getHeaders()) {
out.write((header + "\r\n").getBytes(ASCII));
}
out.write(("\r\n").getBytes(ASCII));
out.flush();
// Stream data in MTU-sized increments
final byte[] buffer = new byte[1452];
final long delayMs;
if (bytesPerSecond == Integer.MAX_VALUE) {
delayMs = 0;
} else {
delayMs = (1000 * buffer.length) / bytesPerSecond;
}
int read;
long sinceDelay = 0;
while ((read = body.read(buffer)) != -1) {
out.write(buffer, 0, read);
out.flush();
sinceDelay += read;
if (sinceDelay >= buffer.length && delayMs > 0) {
sinceDelay %= buffer.length;
try {
Thread.sleep(delayMs);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
}
}
/**
* Write complete response. Usually implemented by calling
* {@link #writeResponse(InputStream, OutputStream)} with the
* implementation-specific body.
*/
public abstract void writeResponse(OutputStream out) throws IOException;
/**
* Return concrete {@code this} to enable builder-style methods.
*/
protected abstract T self();
}