• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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