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