• 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.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