Home | History | Annotate | Download | only in http
      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