1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-25 16:01:38 +03:00

Use the OkHttp code style in MockWebServer

This commit is contained in:
jwilson
2013-08-05 18:59:57 -04:00
parent 84e0b4875a
commit d5ab35a272
6 changed files with 971 additions and 1053 deletions

View File

@@ -15,22 +15,20 @@
*/
package com.squareup.okhttp.mockwebserver;
/**
* Handler for mock server requests.
*/
/** Handler for mock server requests. */
public abstract class Dispatcher {
/**
* Returns a response to satisfy {@code request}. This method may block (for
* instance, to wait on a CountdownLatch).
*/
public abstract MockResponse dispatch(RecordedRequest request) throws InterruptedException;
/**
* Returns a response to satisfy {@code request}. This method may block (for
* instance, to wait on a CountdownLatch).
*/
public abstract MockResponse dispatch(RecordedRequest request) throws InterruptedException;
/**
* Returns the socket policy of the next request. Default implementation
* returns {@link SocketPolicy#KEEP_OPEN}. Mischievous implementations can
* return other values to test HTTP edge cases.
*/
public SocketPolicy peekSocketPolicy() {
return SocketPolicy.KEEP_OPEN;
}
/**
* Returns the socket policy of the next request. Default implementation
* returns {@link SocketPolicy#KEEP_OPEN}. Mischievous implementations can
* return other values to test HTTP edge cases.
*/
public SocketPolicy peekSocketPolicy() {
return SocketPolicy.KEEP_OPEN;
}
}

View File

@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.mockwebserver;
import static com.squareup.okhttp.mockwebserver.MockWebServer.ASCII;
import com.squareup.okhttp.internal.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -26,215 +25,199 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A scripted response to be replayed by the mock web server.
*/
/** 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 static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
private String status = "HTTP/1.1 200 OK";
private List<String> headers = new ArrayList<String>();
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;
/** 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 bytesPerSecond = Integer.MAX_VALUE;
private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
private int bytesPerSecond = Integer.MAX_VALUE;
private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
/**
* Creates a new mock response with an empty body.
*/
public MockResponse() {
setBody(new byte[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();
}
}
@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 HTTP response line, such as "HTTP/1.1 200 OK".
*/
public String getStatus() {
return status;
/** 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();
}
}
public MockResponse setResponseCode(int code) {
this.status = "HTTP/1.1 " + code + " OK";
return this;
/**
* 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(Util.US_ASCII));
bytesOut.write("\r\n".getBytes(Util.US_ASCII));
bytesOut.write(body, pos, chunkSize);
bytesOut.write("\r\n".getBytes(Util.US_ASCII));
pos += chunkSize;
}
bytesOut.write("0\r\n\r\n".getBytes(Util.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.
}
}
public MockResponse setStatus(String status) {
this.status = status;
return this;
/**
* 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();
}
}
/**
* Returns the HTTP headers, such as "Content-Length: 0".
*/
public List<String> getHeaders() {
return headers;
}
public SocketPolicy getSocketPolicy() {
return socketPolicy;
}
/**
* Removes all HTTP headers including any "Content-Length" and
* "Transfer-encoding" headers that were added by default.
*/
public MockResponse clearHeaders() {
headers.clear();
return this;
}
public MockResponse setSocketPolicy(SocketPolicy socketPolicy) {
this.socketPolicy = socketPolicy;
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;
}
public int getBytesPerSecond() {
return bytesPerSecond;
}
/**
* 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));
}
/**
* Set simulated network speed, in bytes per second. This applies to the
* response body only; response headers are not throttled.
*/
public MockResponse setBytesPerSecond(int bytesPerSecond) {
this.bytesPerSecond = bytesPerSecond;
return this;
}
/**
* 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(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\r\n".getBytes(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;
}
public int getBytesPerSecond() {
return bytesPerSecond;
}
/**
* Set simulated network speed, in bytes per second. This applies to the
* response body only; response headers are not throttled.
*/
public MockResponse setBytesPerSecond(int bytesPerSecond) {
this.bytesPerSecond = bytesPerSecond;
return this;
}
@Override public String toString() {
return status;
}
@Override public String toString() {
return status;
}
}

View File

@@ -20,53 +20,52 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Default dispatcher that processes a script of responses. Populate the script by calling
* {@link #enqueueResponse(MockResponse)}.
* Default dispatcher that processes a script of responses. Populate the script
* by calling {@link #enqueueResponse(MockResponse)}.
*/
public class QueueDispatcher extends Dispatcher {
protected final BlockingQueue<MockResponse> responseQueue
= new LinkedBlockingQueue<MockResponse>();
private MockResponse failFastResponse;
protected final BlockingQueue<MockResponse> responseQueue
= new LinkedBlockingQueue<MockResponse>();
private MockResponse failFastResponse;
@Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
// to permit interactive/browser testing, ignore requests for favicons
final String requestLine = request.getRequestLine();
if (requestLine != null && requestLine.equals("GET /favicon.ico HTTP/1.1")) {
System.out.println("served " + requestLine);
return new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
}
if (failFastResponse != null && responseQueue.peek() == null) {
// Fail fast if there's no response queued up.
return failFastResponse;
}
return responseQueue.take();
@Override public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
// To permit interactive/browser testing, ignore requests for favicons.
final String requestLine = request.getRequestLine();
if (requestLine != null && requestLine.equals("GET /favicon.ico HTTP/1.1")) {
System.out.println("served " + requestLine);
return new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND);
}
@Override public SocketPolicy peekSocketPolicy() {
MockResponse peek = responseQueue.peek();
if (peek == null) {
return failFastResponse != null
? failFastResponse.getSocketPolicy()
: SocketPolicy.KEEP_OPEN;
}
return peek.getSocketPolicy();
if (failFastResponse != null && responseQueue.peek() == null) {
// Fail fast if there's no response queued up.
return failFastResponse;
}
public void enqueueResponse(MockResponse response) {
responseQueue.add(response);
}
return responseQueue.take();
}
public void setFailFast(boolean failFast) {
MockResponse failFastResponse = failFast
? new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
: null;
setFailFast(failFastResponse);
@Override public SocketPolicy peekSocketPolicy() {
MockResponse peek = responseQueue.peek();
if (peek == null) {
return failFastResponse != null
? failFastResponse.getSocketPolicy()
: SocketPolicy.KEEP_OPEN;
}
return peek.getSocketPolicy();
}
public void setFailFast(MockResponse failFastResponse) {
this.failFastResponse = failFastResponse;
}
public void enqueueResponse(MockResponse response) {
responseQueue.add(response);
}
public void setFailFast(boolean failFast) {
MockResponse failFastResponse = failFast
? new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
: null;
setFailFast(failFastResponse);
}
public void setFailFast(MockResponse failFastResponse) {
this.failFastResponse = failFastResponse;
}
}

View File

@@ -22,146 +22,132 @@ import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.SSLSocket;
/**
* An HTTP request that came into the mock web server.
*/
/** An HTTP request that came into the mock web server. */
public final class RecordedRequest {
private final String requestLine;
private final String method;
private final String path;
private final List<String> headers;
private final List<Integer> chunkSizes;
private final long bodySize;
private final byte[] body;
private final int sequenceNumber;
private final String sslProtocol;
private final String requestLine;
private final String method;
private final String path;
private final List<String> headers;
private final List<Integer> chunkSizes;
private final long bodySize;
private final byte[] body;
private final int sequenceNumber;
private final String sslProtocol;
public RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
long bodySize, byte[] body, int sequenceNumber, Socket socket) {
this.requestLine = requestLine;
this.headers = headers;
this.chunkSizes = chunkSizes;
this.bodySize = bodySize;
this.body = body;
this.sequenceNumber = sequenceNumber;
public RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
long bodySize, byte[] body, int sequenceNumber, Socket socket) {
this.requestLine = requestLine;
this.headers = headers;
this.chunkSizes = chunkSizes;
this.bodySize = bodySize;
this.body = body;
this.sequenceNumber = sequenceNumber;
this.sslProtocol = socket instanceof SSLSocket
? ((SSLSocket) socket).getSession().getProtocol()
: null;
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
sslProtocol = sslSocket.getSession().getProtocol();
} else {
sslProtocol = null;
}
if (requestLine != null) {
int methodEnd = requestLine.indexOf(' ');
int pathEnd = requestLine.indexOf(' ', methodEnd + 1);
this.method = requestLine.substring(0, methodEnd);
this.path = requestLine.substring(methodEnd + 1, pathEnd);
} else {
this.method = null;
this.path = null;
}
if (requestLine != null) {
int methodEnd = requestLine.indexOf(' ');
int pathEnd = requestLine.indexOf(' ', methodEnd + 1);
this.method = requestLine.substring(0, methodEnd);
this.path = requestLine.substring(methodEnd + 1, pathEnd);
} else {
this.method = null;
this.path = null;
}
}
public String getRequestLine() {
return requestLine;
}
public String getRequestLine() {
return requestLine;
}
public String getMethod() {
return method;
}
public String getMethod() {
return method;
}
public String getPath() {
return path;
}
public String getPath() {
return path;
}
/**
* Returns all headers.
*/
public List<String> getHeaders() {
return headers;
}
/** Returns all headers. */
public List<String> getHeaders() {
return headers;
}
/**
* Returns the first header named {@code name}, or null if no such header
* exists.
*/
public String getHeader(String name) {
name += ":";
for (String header : headers) {
if (name.regionMatches(true, 0, header, 0, name.length())) {
return header.substring(name.length()).trim();
}
}
return null;
/**
* Returns the first header named {@code name}, or null if no such header
* exists.
*/
public String getHeader(String name) {
name += ":";
for (String header : headers) {
if (name.regionMatches(true, 0, header, 0, name.length())) {
return header.substring(name.length()).trim();
}
}
return null;
}
/**
* Returns the headers named {@code name}.
*/
public List<String> getHeaders(String name) {
List<String> result = new ArrayList<String>();
name += ":";
for (String header : headers) {
if (name.regionMatches(true, 0, header, 0, name.length())) {
result.add(header.substring(name.length()).trim());
}
}
return result;
/** Returns the headers named {@code name}. */
public List<String> getHeaders(String name) {
List<String> result = new ArrayList<String>();
name += ":";
for (String header : headers) {
if (name.regionMatches(true, 0, header, 0, name.length())) {
result.add(header.substring(name.length()).trim());
}
}
return result;
}
/**
* 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 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 long getBodySize() {
return bodySize;
}
/**
* Returns the total size of the body of this POST request (before
* truncation).
*/
public long getBodySize() {
return bodySize;
}
/**
* Returns the body of this POST request. This may be truncated.
*/
public byte[] getBody() {
return body;
}
/** Returns the body of this POST request. This may be truncated. */
public byte[] getBody() {
return body;
}
/**
* Returns the body of this POST request decoded as a UTF-8 string.
*/
public String getUtf8Body() {
try {
return new String(body, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
/** Returns the body of this POST request decoded as a UTF-8 string. */
public String getUtf8Body() {
try {
return new String(body, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}
/**
* 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;
}
/**
* 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;
}
/**
* Returns the connection's SSL protocol like {@code TLSv1}, {@code SSLv3},
* {@code NONE} or null if the connection doesn't use SSL.
*/
public String getSslProtocol() {
return sslProtocol;
}
/**
* Returns the connection's SSL protocol like {@code TLSv1}, {@code SSLv3},
* {@code NONE} or null if the connection doesn't use SSL.
*/
public String getSslProtocol() {
return sslProtocol;
}
@Override public String toString() {
return requestLine;
}
@Override public String toString() {
return requestLine;
}
}

View File

@@ -16,54 +16,46 @@
package com.squareup.okhttp.mockwebserver;
/**
* What should be done with the incoming socket.
*/
/** What should be done with the incoming socket. */
public enum SocketPolicy {
/**
* Keep the socket open after the response. This is the default HTTP/1.1
* behavior.
*/
KEEP_OPEN,
/**
* Keep the socket open after the response. This is the default HTTP/1.1
* behavior.
*/
KEEP_OPEN,
/**
* Close the socket after the response. This is the default HTTP/1.0
* behavior.
*/
DISCONNECT_AT_END,
/**
* Close the socket after the response. This is the default HTTP/1.0
* behavior.
*/
DISCONNECT_AT_END,
/**
* Wrap the socket with SSL at the completion of this request/response
* pair. Used for CONNECT messages to tunnel SSL over an HTTP proxy.
*/
UPGRADE_TO_SSL_AT_END,
/**
* Wrap the socket with SSL at the completion of this request/response pair.
* Used for CONNECT messages to tunnel SSL over an HTTP proxy.
*/
UPGRADE_TO_SSL_AT_END,
/**
* Request immediate close of connection without even reading the
* request.
*
* <p>Use to simulate the real life case of losing connection
* because of bugger SSL server close connection when it seems
* something like a compression method or TLS extension it doesn't
* understand, instead of simply ignoring it like it should.
*/
DISCONNECT_AT_START,
/**
* Request immediate close of connection without even reading the request. Use
* to simulate buggy SSL servers closing connections in response to
* unrecognized TLS extensions.
*/
DISCONNECT_AT_START,
/**
* Don't trust the client during the SSL handshake.
*/
FAIL_HANDSHAKE,
/** Don't trust the client during the SSL handshake. */
FAIL_HANDSHAKE,
/**
* Shutdown the socket input after sending the response. For testing bad
* behavior.
*/
SHUTDOWN_INPUT_AT_END,
/**
* Shutdown the socket input after sending the response. For testing bad
* behavior.
*/
SHUTDOWN_INPUT_AT_END,
/**
* Shutdown the socket output after sending the response. For testing bad
* behavior.
*/
SHUTDOWN_OUTPUT_AT_END
/**
* Shutdown the socket output after sending the response. For testing bad
* behavior.
*/
SHUTDOWN_OUTPUT_AT_END
}