1/*
2 * Copyright (C) 2010 The Android Open Source Project
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 tests.http;
18
19import java.io.ByteArrayOutputStream;
20import java.io.IOException;
21import java.io.UnsupportedEncodingException;
22import java.util.ArrayList;
23import java.util.List;
24import static tests.http.MockWebServer.ASCII;
25
26/**
27 * A scripted response to be replayed by the mock web server.
28 */
29public class MockResponse implements Cloneable {
30    private static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
31    private static final String EMPTY_BODY_HEADER = "Content-Length: 0";
32    private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
33    private static final byte[] EMPTY_BODY = new byte[0];
34
35    private String status = "HTTP/1.1 200 OK";
36    private List<String> headers = new ArrayList<String>();
37    private byte[] body = EMPTY_BODY;
38    private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
39
40    public MockResponse() {
41        headers.add(EMPTY_BODY_HEADER);
42    }
43
44    @Override public MockResponse clone() {
45        try {
46            MockResponse result = (MockResponse) super.clone();
47            result.headers = new ArrayList<String>(result.headers);
48            return result;
49        } catch (CloneNotSupportedException e) {
50            throw new AssertionError();
51        }
52    }
53
54    /**
55     * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
56     */
57    public String getStatus() {
58        return status;
59    }
60
61    public MockResponse setResponseCode(int code) {
62        this.status = "HTTP/1.1 " + code + " OK";
63        return this;
64    }
65
66    public MockResponse setStatus(String status) {
67        this.status = status;
68        return this;
69    }
70
71    /**
72     * Returns the HTTP headers, such as "Content-Length: 0".
73     */
74    public List<String> getHeaders() {
75        return headers;
76    }
77
78    public MockResponse clearHeaders() {
79        headers.clear();
80        return this;
81    }
82
83    public MockResponse addHeader(String header) {
84        headers.add(header);
85        return this;
86    }
87
88    /**
89     * Returns an input stream containing the raw HTTP payload.
90     */
91    public byte[] getBody() {
92        return body;
93    }
94
95    public MockResponse setBody(byte[] body) {
96        if (this.body == EMPTY_BODY) {
97            headers.remove(EMPTY_BODY_HEADER);
98        }
99        this.headers.add("Content-Length: " + body.length);
100        this.body = body;
101        return this;
102    }
103
104    public MockResponse setBody(String body) {
105        try {
106            return setBody(body.getBytes(ASCII));
107        } catch (UnsupportedEncodingException e) {
108            throw new AssertionError();
109        }
110    }
111
112    public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
113        headers.remove(EMPTY_BODY_HEADER);
114        headers.add(CHUNKED_BODY_HEADER);
115
116        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
117        int pos = 0;
118        while (pos < body.length) {
119            int chunkSize = Math.min(body.length - pos, maxChunkSize);
120            bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
121            bytesOut.write("\r\n".getBytes(ASCII));
122            bytesOut.write(body, pos, chunkSize);
123            bytesOut.write("\r\n".getBytes(ASCII));
124            pos += chunkSize;
125        }
126        bytesOut.write("0\r\n\r\n".getBytes(ASCII)); // last chunk + empty trailer + crlf
127        this.body = bytesOut.toByteArray();
128        return this;
129    }
130
131    public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
132        return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
133    }
134
135    public SocketPolicy getSocketPolicy() {
136        return socketPolicy;
137    }
138
139    public MockResponse setSocketPolicy(SocketPolicy socketPolicy) {
140        this.socketPolicy = socketPolicy;
141        return this;
142    }
143
144    @Override public String toString() {
145        return status;
146    }
147}
148