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 package com.squareup.okhttp.mockwebserver; 17 18 import com.squareup.okhttp.Headers; 19 import com.squareup.okhttp.internal.Internal; 20 import com.squareup.okhttp.internal.framed.Settings; 21 import com.squareup.okhttp.ws.WebSocketListener; 22 import java.util.ArrayList; 23 import java.util.List; 24 import java.util.concurrent.TimeUnit; 25 import okio.Buffer; 26 27 /** A scripted response to be replayed by the mock web server. */ 28 public final class MockResponse implements Cloneable { 29 private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked"; 30 31 private String status = "HTTP/1.1 200 OK"; 32 private Headers.Builder headers = new Headers.Builder(); 33 34 private Buffer body; 35 36 private long throttleBytesPerPeriod = Long.MAX_VALUE; 37 private long throttlePeriodAmount = 1; 38 private TimeUnit throttlePeriodUnit = TimeUnit.SECONDS; 39 40 private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN; 41 42 private long bodyDelayAmount = 0; 43 private TimeUnit bodyDelayUnit = TimeUnit.MILLISECONDS; 44 45 private List<PushPromise> promises = new ArrayList<>(); 46 private Settings settings; 47 private WebSocketListener webSocketListener; 48 49 /** Creates a new mock response with an empty body. */ MockResponse()50 public MockResponse() { 51 setHeader("Content-Length", 0); 52 } 53 clone()54 @Override public MockResponse clone() { 55 try { 56 MockResponse result = (MockResponse) super.clone(); 57 result.headers = headers.build().newBuilder(); 58 result.promises = new ArrayList<>(promises); 59 return result; 60 } catch (CloneNotSupportedException e) { 61 throw new AssertionError(); 62 } 63 } 64 65 /** Returns the HTTP response line, such as "HTTP/1.1 200 OK". */ getStatus()66 public String getStatus() { 67 return status; 68 } 69 setResponseCode(int code)70 public MockResponse setResponseCode(int code) { 71 return setStatus("HTTP/1.1 " + code + " OK"); 72 } 73 setStatus(String status)74 public MockResponse setStatus(String status) { 75 this.status = status; 76 return this; 77 } 78 79 /** Returns the HTTP headers, such as "Content-Length: 0". */ getHeaders()80 public Headers getHeaders() { 81 return headers.build(); 82 } 83 84 /** 85 * Removes all HTTP headers including any "Content-Length" and 86 * "Transfer-encoding" headers that were added by default. 87 */ clearHeaders()88 public MockResponse clearHeaders() { 89 headers = new Headers.Builder(); 90 return this; 91 } 92 93 /** 94 * Adds {@code header} as an HTTP header. For well-formed HTTP {@code header} 95 * should contain a name followed by a colon and a value. 96 */ addHeader(String header)97 public MockResponse addHeader(String header) { 98 headers.add(header); 99 return this; 100 } 101 102 /** 103 * Adds a new header with the name and value. This may be used to add multiple 104 * headers with the same name. 105 */ addHeader(String name, Object value)106 public MockResponse addHeader(String name, Object value) { 107 headers.add(name, String.valueOf(value)); 108 return this; 109 } 110 111 /** 112 * Adds a new header with the name and value. This may be used to add multiple 113 * headers with the same name. Unlike {@link #addHeader(String, Object)} this 114 * does not validate the name and value. 115 */ addHeaderLenient(String name, Object value)116 public MockResponse addHeaderLenient(String name, Object value) { 117 Internal.instance.addLenient(headers, name, String.valueOf(value)); 118 return this; 119 } 120 121 /** 122 * Removes all headers named {@code name}, then adds a new header with the 123 * name and value. 124 */ setHeader(String name, Object value)125 public MockResponse setHeader(String name, Object value) { 126 removeHeader(name); 127 return addHeader(name, value); 128 } 129 130 /** Replaces all headers with those specified in {@code headers}. */ setHeaders(Headers headers)131 public MockResponse setHeaders(Headers headers) { 132 this.headers = headers.newBuilder(); 133 return this; 134 } 135 136 /** Removes all headers named {@code name}. */ removeHeader(String name)137 public MockResponse removeHeader(String name) { 138 headers.removeAll(name); 139 return this; 140 } 141 142 /** Returns a copy of the raw HTTP payload. */ getBody()143 public Buffer getBody() { 144 return body != null ? body.clone() : null; 145 } 146 setBody(Buffer body)147 public MockResponse setBody(Buffer body) { 148 setHeader("Content-Length", body.size()); 149 this.body = body.clone(); // Defensive copy. 150 return this; 151 } 152 153 /** Sets the response body to the UTF-8 encoded bytes of {@code body}. */ setBody(String body)154 public MockResponse setBody(String body) { 155 return setBody(new Buffer().writeUtf8(body)); 156 } 157 158 /** 159 * Sets the response body to {@code body}, chunked every {@code maxChunkSize} 160 * bytes. 161 */ setChunkedBody(Buffer body, int maxChunkSize)162 public MockResponse setChunkedBody(Buffer body, int maxChunkSize) { 163 removeHeader("Content-Length"); 164 headers.add(CHUNKED_BODY_HEADER); 165 166 Buffer bytesOut = new Buffer(); 167 while (!body.exhausted()) { 168 long chunkSize = Math.min(body.size(), maxChunkSize); 169 bytesOut.writeHexadecimalUnsignedLong(chunkSize); 170 bytesOut.writeUtf8("\r\n"); 171 bytesOut.write(body, chunkSize); 172 bytesOut.writeUtf8("\r\n"); 173 } 174 bytesOut.writeUtf8("0\r\n\r\n"); // Last chunk + empty trailer + CRLF. 175 176 this.body = bytesOut; 177 return this; 178 } 179 180 /** 181 * Sets the response body to the UTF-8 encoded bytes of {@code body}, chunked 182 * every {@code maxChunkSize} bytes. 183 */ setChunkedBody(String body, int maxChunkSize)184 public MockResponse setChunkedBody(String body, int maxChunkSize) { 185 return setChunkedBody(new Buffer().writeUtf8(body), maxChunkSize); 186 } 187 getSocketPolicy()188 public SocketPolicy getSocketPolicy() { 189 return socketPolicy; 190 } 191 setSocketPolicy(SocketPolicy socketPolicy)192 public MockResponse setSocketPolicy(SocketPolicy socketPolicy) { 193 this.socketPolicy = socketPolicy; 194 return this; 195 } 196 197 /** 198 * Throttles the response body writer to sleep for the given period after each 199 * series of {@code bytesPerPeriod} bytes are written. Use this to simulate 200 * network behavior. 201 */ throttleBody(long bytesPerPeriod, long period, TimeUnit unit)202 public MockResponse throttleBody(long bytesPerPeriod, long period, TimeUnit unit) { 203 this.throttleBytesPerPeriod = bytesPerPeriod; 204 this.throttlePeriodAmount = period; 205 this.throttlePeriodUnit = unit; 206 return this; 207 } 208 getThrottleBytesPerPeriod()209 public long getThrottleBytesPerPeriod() { 210 return throttleBytesPerPeriod; 211 } 212 getThrottlePeriod(TimeUnit unit)213 public long getThrottlePeriod(TimeUnit unit) { 214 return unit.convert(throttlePeriodAmount, throttlePeriodUnit); 215 } 216 217 /** 218 * Set the delayed time of the response body to {@code delay}. This applies to the 219 * response body only; response headers are not affected. 220 */ setBodyDelay(long delay, TimeUnit unit)221 public MockResponse setBodyDelay(long delay, TimeUnit unit) { 222 bodyDelayAmount = delay; 223 bodyDelayUnit = unit; 224 return this; 225 } 226 getBodyDelay(TimeUnit unit)227 public long getBodyDelay(TimeUnit unit) { 228 return unit.convert(bodyDelayAmount, bodyDelayUnit); 229 } 230 231 /** 232 * When {@link MockWebServer#setProtocols(java.util.List) protocols} 233 * include {@linkplain com.squareup.okhttp.Protocol#HTTP_2}, this attaches a 234 * pushed stream to this response. 235 */ withPush(PushPromise promise)236 public MockResponse withPush(PushPromise promise) { 237 this.promises.add(promise); 238 return this; 239 } 240 241 /** Returns the streams the server will push with this response. */ getPushPromises()242 public List<PushPromise> getPushPromises() { 243 return promises; 244 } 245 246 /** 247 * When {@linkplain MockWebServer#setProtocols(java.util.List) protocols} 248 * include {@linkplain com.squareup.okhttp.Protocol#HTTP_2 HTTP/2}, this 249 * pushes {@code settings} before writing the response. 250 */ withSettings(Settings settings)251 public MockResponse withSettings(Settings settings) { 252 this.settings = settings; 253 return this; 254 } 255 getSettings()256 public Settings getSettings() { 257 return settings; 258 } 259 260 /** 261 * Attempts to perform a web socket upgrade on the connection. This will overwrite any previously 262 * set status or body. 263 */ withWebSocketUpgrade(WebSocketListener listener)264 public MockResponse withWebSocketUpgrade(WebSocketListener listener) { 265 setStatus("HTTP/1.1 101 Switching Protocols"); 266 setHeader("Connection", "Upgrade"); 267 setHeader("Upgrade", "websocket"); 268 body = null; 269 webSocketListener = listener; 270 return this; 271 } 272 getWebSocketListener()273 public WebSocketListener getWebSocketListener() { 274 return webSocketListener; 275 } 276 toString()277 @Override public String toString() { 278 return status; 279 } 280 } 281