1 /* 2 * Copyright (C) 2011 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.mockwebserver; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.UnsupportedEncodingException; 24 import java.util.ArrayList; 25 import java.util.Iterator; 26 import java.util.List; 27 import java.util.concurrent.TimeUnit; 28 29 import static java.nio.charset.StandardCharsets.US_ASCII; 30 31 /** 32 * A scripted response to be replayed by the mock web server. 33 */ 34 public final class MockResponse implements Cloneable { 35 private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked"; 36 37 private String status = "HTTP/1.1 200 OK"; 38 private List<String> headers = new ArrayList<String>(); 39 40 /** The response body content, or null if {@code bodyStream} is set. */ 41 private byte[] body; 42 /** The response body content, or null if {@code body} is set. */ 43 private InputStream bodyStream; 44 45 private int throttleBytesPerPeriod = Integer.MAX_VALUE; 46 private long throttlePeriod = 1; 47 private TimeUnit throttleUnit = TimeUnit.SECONDS; 48 49 private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN; 50 51 private int bodyDelayTimeMs = 0; 52 53 /** 54 * Creates a new mock response with an empty body. 55 */ MockResponse()56 public MockResponse() { 57 setBody(new byte[0]); 58 } 59 clone()60 @Override public MockResponse clone() { 61 try { 62 MockResponse result = (MockResponse) super.clone(); 63 result.headers = new ArrayList<String>(result.headers); 64 return result; 65 } catch (CloneNotSupportedException e) { 66 throw new AssertionError(); 67 } 68 } 69 70 /** 71 * Returns the HTTP response line, such as "HTTP/1.1 200 OK". 72 */ getStatus()73 public String getStatus() { 74 return status; 75 } 76 setResponseCode(int code)77 public MockResponse setResponseCode(int code) { 78 this.status = "HTTP/1.1 " + code + " OK"; 79 return this; 80 } 81 setStatus(String status)82 public MockResponse setStatus(String status) { 83 this.status = status; 84 return this; 85 } 86 87 /** 88 * Returns the HTTP headers, such as "Content-Length: 0". 89 */ getHeaders()90 public List<String> getHeaders() { 91 return headers; 92 } 93 94 /** 95 * Removes all HTTP headers including any "Content-Length" and 96 * "Transfer-encoding" headers that were added by default. 97 */ clearHeaders()98 public MockResponse clearHeaders() { 99 headers.clear(); 100 return this; 101 } 102 103 /** 104 * Adds {@code header} as an HTTP header. For well-formed HTTP {@code 105 * header} should contain a name followed by a colon and a value. 106 */ addHeader(String header)107 public MockResponse addHeader(String header) { 108 headers.add(header); 109 return this; 110 } 111 112 /** 113 * Adds a new header with the name and value. This may be used to add 114 * multiple headers with the same name. 115 */ addHeader(String name, Object value)116 public MockResponse addHeader(String name, Object value) { 117 return addHeader(name + ": " + String.valueOf(value)); 118 } 119 120 /** 121 * Removes all headers named {@code name}, then adds a new header with the 122 * name and value. 123 */ setHeader(String name, Object value)124 public MockResponse setHeader(String name, Object value) { 125 removeHeader(name); 126 return addHeader(name, value); 127 } 128 129 /** 130 * Removes all headers named {@code name}. 131 */ removeHeader(String name)132 public MockResponse removeHeader(String name) { 133 name += ":"; 134 for (Iterator<String> i = headers.iterator(); i.hasNext(); ) { 135 String header = i.next(); 136 if (name.regionMatches(true, 0, header, 0, name.length())) { 137 i.remove(); 138 } 139 } 140 return this; 141 } 142 143 /** 144 * Returns the raw HTTP payload, or null if this response is streamed. 145 */ getBody()146 public byte[] getBody() { 147 return body; 148 } 149 150 /** 151 * Returns an input stream containing the raw HTTP payload. 152 */ getBodyStream()153 InputStream getBodyStream() { 154 return bodyStream != null ? bodyStream : new ByteArrayInputStream(body); 155 } 156 setBody(byte[] body)157 public MockResponse setBody(byte[] body) { 158 setHeader("Content-Length", body.length); 159 this.body = body; 160 this.bodyStream = null; 161 return this; 162 } 163 setBody(InputStream bodyStream, long bodyLength)164 public MockResponse setBody(InputStream bodyStream, long bodyLength) { 165 setHeader("Content-Length", bodyLength); 166 this.body = null; 167 this.bodyStream = bodyStream; 168 return this; 169 } 170 171 /** 172 * Sets the response body to the UTF-8 encoded bytes of {@code body}. 173 */ setBody(String body)174 public MockResponse setBody(String body) { 175 try { 176 return setBody(body.getBytes("UTF-8")); 177 } catch (UnsupportedEncodingException e) { 178 throw new AssertionError(); 179 } 180 } 181 182 /** 183 * Sets the response body to {@code body}, chunked every {@code 184 * maxChunkSize} bytes. 185 */ setChunkedBody(byte[] body, int maxChunkSize)186 public MockResponse setChunkedBody(byte[] body, int maxChunkSize) { 187 removeHeader("Content-Length"); 188 headers.add(CHUNKED_BODY_HEADER); 189 190 try { 191 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 192 int pos = 0; 193 while (pos < body.length) { 194 int chunkSize = Math.min(body.length - pos, maxChunkSize); 195 bytesOut.write(Integer.toHexString(chunkSize).getBytes(US_ASCII)); 196 bytesOut.write("\r\n".getBytes(US_ASCII)); 197 bytesOut.write(body, pos, chunkSize); 198 bytesOut.write("\r\n".getBytes(US_ASCII)); 199 pos += chunkSize; 200 } 201 bytesOut.write("0\r\n\r\n".getBytes(US_ASCII)); // last chunk + empty trailer + crlf 202 203 this.body = bytesOut.toByteArray(); 204 return this; 205 } catch (IOException e) { 206 throw new AssertionError(); // In-memory I/O doesn't throw IOExceptions. 207 } 208 } 209 210 /** 211 * Sets the response body to the UTF-8 encoded bytes of {@code body}, 212 * chunked every {@code maxChunkSize} bytes. 213 */ setChunkedBody(String body, int maxChunkSize)214 public MockResponse setChunkedBody(String body, int maxChunkSize) { 215 try { 216 return setChunkedBody(body.getBytes("UTF-8"), maxChunkSize); 217 } catch (UnsupportedEncodingException e) { 218 throw new AssertionError(); 219 } 220 } 221 getSocketPolicy()222 public SocketPolicy getSocketPolicy() { 223 return socketPolicy; 224 } 225 setSocketPolicy(SocketPolicy socketPolicy)226 public MockResponse setSocketPolicy(SocketPolicy socketPolicy) { 227 this.socketPolicy = socketPolicy; 228 return this; 229 } 230 231 /** 232 * Throttles the response body writer to sleep for the given period after each 233 * series of {@code bytesPerPeriod} bytes are written. Use this to simulate 234 * network behavior. 235 */ throttleBody(int bytesPerPeriod, long period, TimeUnit unit)236 public MockResponse throttleBody(int bytesPerPeriod, long period, TimeUnit unit) { 237 this.throttleBytesPerPeriod = bytesPerPeriod; 238 this.throttlePeriod = period; 239 this.throttleUnit = unit; 240 return this; 241 } 242 getThrottleBytesPerPeriod()243 public int getThrottleBytesPerPeriod() { 244 return throttleBytesPerPeriod; 245 } 246 getThrottlePeriod()247 public long getThrottlePeriod() { 248 return throttlePeriod; 249 } 250 getThrottleUnit()251 public TimeUnit getThrottleUnit() { 252 return throttleUnit; 253 } 254 255 /** 256 * Set the delayed time of the response body to {@code delay}. This applies to the 257 * response body only; response headers are not affected. 258 */ setBodyDelayTimeMs(int delay)259 public MockResponse setBodyDelayTimeMs(int delay) { 260 bodyDelayTimeMs = delay; 261 return this; 262 } 263 getBodyDelayTimeMs()264 public int getBodyDelayTimeMs() { 265 return bodyDelayTimeMs; 266 } 267 toString()268 @Override public String toString() { 269 return "MockResponse{" + status + "}"; 270 } 271 } 272