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
17package com.google.mockwebserver;
18
19import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.UnsupportedEncodingException;
24import java.util.ArrayList;
25import java.util.Iterator;
26import java.util.List;
27import java.util.concurrent.TimeUnit;
28
29import static java.nio.charset.StandardCharsets.US_ASCII;
30
31/**
32 * A scripted response to be replayed by the mock web server.
33 */
34public final class MockResponse implements Cloneable {
35    private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
36
37    private String status = "HTTP/1.1 200 OK";
38    private List<String> headers = new ArrayList<String>();
39
40    /** The response body content, or null if {@code bodyStream} is set. */
41    private byte[] body;
42    /** The response body content, or null if {@code body} is set. */
43    private InputStream bodyStream;
44
45    private int throttleBytesPerPeriod = Integer.MAX_VALUE;
46    private long throttlePeriod = 1;
47    private TimeUnit throttleUnit = TimeUnit.SECONDS;
48
49    private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
50
51    private int bodyDelayTimeMs = 0;
52
53    /**
54     * Creates a new mock response with an empty body.
55     */
56    public MockResponse() {
57        setBody(new byte[0]);
58    }
59
60    @Override public MockResponse clone() {
61        try {
62            MockResponse result = (MockResponse) super.clone();
63            result.headers = new ArrayList<String>(result.headers);
64            return result;
65        } catch (CloneNotSupportedException e) {
66            throw new AssertionError();
67        }
68    }
69
70    /**
71     * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
72     */
73    public String getStatus() {
74        return status;
75    }
76
77    public MockResponse setResponseCode(int code) {
78        this.status = "HTTP/1.1 " + code + " OK";
79        return this;
80    }
81
82    public MockResponse setStatus(String status) {
83        this.status = status;
84        return this;
85    }
86
87    /**
88     * Returns the HTTP headers, such as "Content-Length: 0".
89     */
90    public List<String> getHeaders() {
91        return headers;
92    }
93
94    /**
95     * Removes all HTTP headers including any "Content-Length" and
96     * "Transfer-encoding" headers that were added by default.
97     */
98    public MockResponse clearHeaders() {
99        headers.clear();
100        return this;
101    }
102
103    /**
104     * Adds {@code header} as an HTTP header. For well-formed HTTP {@code
105     * header} should contain a name followed by a colon and a value.
106     */
107    public MockResponse addHeader(String header) {
108        headers.add(header);
109        return this;
110    }
111
112    /**
113     * Adds a new header with the name and value. This may be used to add
114     * multiple headers with the same name.
115     */
116    public MockResponse addHeader(String name, Object value) {
117        return addHeader(name + ": " + String.valueOf(value));
118    }
119
120    /**
121     * Removes all headers named {@code name}, then adds a new header with the
122     * name and value.
123     */
124    public MockResponse setHeader(String name, Object value) {
125        removeHeader(name);
126        return addHeader(name, value);
127    }
128
129    /**
130     * Removes all headers named {@code name}.
131     */
132    public MockResponse removeHeader(String name) {
133        name += ":";
134        for (Iterator<String> i = headers.iterator(); i.hasNext(); ) {
135            String header = i.next();
136            if (name.regionMatches(true, 0, header, 0, name.length())) {
137                i.remove();
138            }
139        }
140        return this;
141    }
142
143    /**
144     * Returns the raw HTTP payload, or null if this response is streamed.
145     */
146    public byte[] getBody() {
147        return body;
148    }
149
150    /**
151     * Returns an input stream containing the raw HTTP payload.
152     */
153    InputStream getBodyStream() {
154        return bodyStream != null ? bodyStream : new ByteArrayInputStream(body);
155    }
156
157    public MockResponse setBody(byte[] body) {
158        setHeader("Content-Length", body.length);
159        this.body = body;
160        this.bodyStream = null;
161        return this;
162    }
163
164    public MockResponse setBody(InputStream bodyStream, long bodyLength) {
165        setHeader("Content-Length", bodyLength);
166        this.body = null;
167        this.bodyStream = bodyStream;
168        return this;
169    }
170
171    /**
172     * Sets the response body to the UTF-8 encoded bytes of {@code body}.
173     */
174    public MockResponse setBody(String body) {
175        try {
176            return setBody(body.getBytes("UTF-8"));
177        } catch (UnsupportedEncodingException e) {
178            throw new AssertionError();
179        }
180    }
181
182    /**
183     * Sets the response body to {@code body}, chunked every {@code
184     * maxChunkSize} bytes.
185     */
186    public MockResponse setChunkedBody(byte[] body, int maxChunkSize) {
187        removeHeader("Content-Length");
188        headers.add(CHUNKED_BODY_HEADER);
189
190        try {
191            ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
192            int pos = 0;
193            while (pos < body.length) {
194                int chunkSize = Math.min(body.length - pos, maxChunkSize);
195                bytesOut.write(Integer.toHexString(chunkSize).getBytes(US_ASCII));
196                bytesOut.write("\r\n".getBytes(US_ASCII));
197                bytesOut.write(body, pos, chunkSize);
198                bytesOut.write("\r\n".getBytes(US_ASCII));
199                pos += chunkSize;
200            }
201            bytesOut.write("0\r\n\r\n".getBytes(US_ASCII)); // last chunk + empty trailer + crlf
202
203            this.body = bytesOut.toByteArray();
204            return this;
205        } catch (IOException e) {
206            throw new AssertionError(); // In-memory I/O doesn't throw IOExceptions.
207        }
208    }
209
210    /**
211     * Sets the response body to the UTF-8 encoded bytes of {@code body},
212     * chunked every {@code maxChunkSize} bytes.
213     */
214    public MockResponse setChunkedBody(String body, int maxChunkSize) {
215        try {
216            return setChunkedBody(body.getBytes("UTF-8"), maxChunkSize);
217        } catch (UnsupportedEncodingException e) {
218            throw new AssertionError();
219        }
220    }
221
222    public SocketPolicy getSocketPolicy() {
223        return socketPolicy;
224    }
225
226    public MockResponse setSocketPolicy(SocketPolicy socketPolicy) {
227        this.socketPolicy = socketPolicy;
228        return this;
229    }
230
231    /**
232     * Throttles the response body writer to sleep for the given period after each
233     * series of {@code bytesPerPeriod} bytes are written. Use this to simulate
234     * network behavior.
235     */
236    public MockResponse throttleBody(int bytesPerPeriod, long period, TimeUnit unit) {
237        this.throttleBytesPerPeriod = bytesPerPeriod;
238        this.throttlePeriod = period;
239        this.throttleUnit = unit;
240        return this;
241    }
242
243    public int getThrottleBytesPerPeriod() {
244        return throttleBytesPerPeriod;
245    }
246
247    public long getThrottlePeriod() {
248        return throttlePeriod;
249    }
250
251    public TimeUnit getThrottleUnit() {
252        return throttleUnit;
253    }
254
255    /**
256     * Set the delayed time of the response body to {@code delay}. This applies to the
257     * response body only; response headers are not affected.
258     */
259    public MockResponse setBodyDelayTimeMs(int delay) {
260        bodyDelayTimeMs = delay;
261        return this;
262    }
263
264    public int getBodyDelayTimeMs() {
265        return bodyDelayTimeMs;
266    }
267
268    @Override public String toString() {
269        return "MockResponse{" + status + "}";
270    }
271}
272