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