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 17 package coretestutils.http; 18 19 import static coretestutils.http.MockWebServer.ASCII; 20 21 import java.io.ByteArrayInputStream; 22 import java.io.ByteArrayOutputStream; 23 import java.io.File; 24 import java.io.FileInputStream; 25 import java.io.FileNotFoundException; 26 import java.io.InputStream; 27 import java.io.IOException; 28 import java.io.UnsupportedEncodingException; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 34 import android.util.Log; 35 36 /** 37 * A scripted response to be replayed by the mock web server. 38 */ 39 public class MockResponse { 40 private static final byte[] EMPTY_BODY = new byte[0]; 41 static final String LOG_TAG = "coretestutils.http.MockResponse"; 42 43 private String status = "HTTP/1.1 200 OK"; 44 private Map<String, String> headers = new HashMap<String, String>(); 45 private byte[] body = EMPTY_BODY; 46 private boolean closeConnectionAfter = false; 47 private String closeConnectionAfterHeader = null; 48 private int closeConnectionAfterXBytes = -1; 49 private int pauseConnectionAfterXBytes = -1; 50 private File bodyExternalFile = null; 51 52 public MockResponse() { 53 addHeader("Content-Length", 0); 54 } 55 56 /** 57 * Returns the HTTP response line, such as "HTTP/1.1 200 OK". 58 */ 59 public String getStatus() { 60 return status; 61 } 62 63 public MockResponse setResponseCode(int code) { 64 this.status = "HTTP/1.1 " + code + " OK"; 65 return this; 66 } 67 68 /** 69 * Returns the HTTP headers, such as "Content-Length: 0". 70 */ 71 public List<String> getHeaders() { 72 List<String> headerStrings = new ArrayList<String>(); 73 for (String header : headers.keySet()) { 74 headerStrings.add(header + ": " + headers.get(header)); 75 } 76 return headerStrings; 77 } 78 79 public MockResponse addHeader(String header, String value) { 80 headers.put(header.toLowerCase(), value); 81 return this; 82 } 83 84 public MockResponse addHeader(String header, long value) { 85 return addHeader(header, Long.toString(value)); 86 } 87 88 public MockResponse removeHeader(String header) { 89 headers.remove(header.toLowerCase()); 90 return this; 91 } 92 93 /** 94 * Returns true if the body should come from an external file, false otherwise. 95 */ 96 private boolean bodyIsExternal() { 97 return bodyExternalFile != null; 98 } 99 100 /** 101 * Returns an input stream containing the raw HTTP payload. 102 */ 103 public InputStream getBody() { 104 if (bodyIsExternal()) { 105 try { 106 return new FileInputStream(bodyExternalFile); 107 } catch (FileNotFoundException e) { 108 Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath()); 109 } 110 } 111 return new ByteArrayInputStream(this.body); 112 } 113 114 public MockResponse setBody(File body) { 115 addHeader("Content-Length", body.length()); 116 this.bodyExternalFile = body; 117 return this; 118 } 119 120 public MockResponse setBody(byte[] body) { 121 addHeader("Content-Length", body.length); 122 this.body = body; 123 return this; 124 } 125 126 public MockResponse setBody(String body) { 127 try { 128 return setBody(body.getBytes(ASCII)); 129 } catch (UnsupportedEncodingException e) { 130 throw new AssertionError(); 131 } 132 } 133 134 /** 135 * Sets the body as chunked. 136 * 137 * Currently chunked body is not supported for external files as bodies. 138 */ 139 public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException { 140 addHeader("Transfer-encoding", "chunked"); 141 142 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 143 int pos = 0; 144 while (pos < body.length) { 145 int chunkSize = Math.min(body.length - pos, maxChunkSize); 146 bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII)); 147 bytesOut.write("\r\n".getBytes(ASCII)); 148 bytesOut.write(body, pos, chunkSize); 149 bytesOut.write("\r\n".getBytes(ASCII)); 150 pos += chunkSize; 151 } 152 bytesOut.write("0\r\n".getBytes(ASCII)); 153 this.body = bytesOut.toByteArray(); 154 return this; 155 } 156 157 public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException { 158 return setChunkedBody(body.getBytes(ASCII), maxChunkSize); 159 } 160 161 @Override public String toString() { 162 return status; 163 } 164 165 public boolean shouldCloseConnectionAfter() { 166 return closeConnectionAfter; 167 } 168 169 public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) { 170 this.closeConnectionAfter = closeConnectionAfter; 171 return this; 172 } 173 174 /** 175 * Sets the header after which sending the server should close the connection. 176 */ 177 public MockResponse setCloseConnectionAfterHeader(String header) { 178 closeConnectionAfterHeader = header; 179 setCloseConnectionAfter(true); 180 return this; 181 } 182 183 /** 184 * Returns the header after which sending the server should close the connection. 185 */ 186 public String getCloseConnectionAfterHeader() { 187 return closeConnectionAfterHeader; 188 } 189 190 /** 191 * Sets the number of bytes in the body to send before which the server should close the 192 * connection. Set to -1 to unset and send the entire body (default). 193 */ 194 public MockResponse setCloseConnectionAfterXBytes(int position) { 195 closeConnectionAfterXBytes = position; 196 setCloseConnectionAfter(true); 197 return this; 198 } 199 200 /** 201 * Returns the number of bytes in the body to send before which the server should close the 202 * connection. Returns -1 if the entire body should be sent (default). 203 */ 204 public int getCloseConnectionAfterXBytes() { 205 return closeConnectionAfterXBytes; 206 } 207 208 /** 209 * Sets the number of bytes in the body to send before which the server should pause the 210 * connection (stalls in sending data). Only one pause per response is supported. 211 * Set to -1 to unset pausing (default). 212 */ 213 public MockResponse setPauseConnectionAfterXBytes(int position) { 214 pauseConnectionAfterXBytes = position; 215 return this; 216 } 217 218 /** 219 * Returns the number of bytes in the body to send before which the server should pause the 220 * connection (stalls in sending data). (Returns -1 if it should not pause). 221 */ 222 public int getPauseConnectionAfterXBytes() { 223 return pauseConnectionAfterXBytes; 224 } 225 226 /** 227 * Returns true if this response is flagged to pause the connection mid-stream, false otherwise 228 */ 229 public boolean getShouldPause() { 230 return (pauseConnectionAfterXBytes != -1); 231 } 232 233 /** 234 * Returns true if this response is flagged to close the connection mid-stream, false otherwise 235 */ 236 public boolean getShouldClose() { 237 return (closeConnectionAfterXBytes != -1); 238 } 239 } 240