Home | History | Annotate | Download | only in mockwebserver
      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.ws.WebSocketListener;
     20 import java.util.ArrayList;
     21 import java.util.List;
     22 import java.util.concurrent.TimeUnit;
     23 import okio.Buffer;
     24 
     25 /** A scripted response to be replayed by the mock web server. */
     26 public final class MockResponse implements Cloneable {
     27   private static final String CHUNKED_BODY_HEADER = "Transfer-encoding: chunked";
     28 
     29   private String status = "HTTP/1.1 200 OK";
     30   private Headers.Builder headers = new Headers.Builder();
     31 
     32   private Buffer body;
     33 
     34   private long throttleBytesPerPeriod = Long.MAX_VALUE;
     35   private long throttlePeriodAmount = 1;
     36   private TimeUnit throttlePeriodUnit = TimeUnit.SECONDS;
     37 
     38   private SocketPolicy socketPolicy = SocketPolicy.KEEP_OPEN;
     39 
     40   private long bodyDelayAmount = 0;
     41   private TimeUnit bodyDelayUnit = TimeUnit.MILLISECONDS;
     42 
     43   private List<PushPromise> promises = new ArrayList<>();
     44   private WebSocketListener webSocketListener;
     45 
     46   /** Creates a new mock response with an empty body. */
     47   public MockResponse() {
     48     setHeader("Content-Length", 0);
     49   }
     50 
     51   @Override public MockResponse clone() {
     52     try {
     53       MockResponse result = (MockResponse) super.clone();
     54       result.headers = headers.build().newBuilder();
     55       result.promises = new ArrayList<>(promises);
     56       return result;
     57     } catch (CloneNotSupportedException e) {
     58       throw new AssertionError();
     59     }
     60   }
     61 
     62   /** Returns the HTTP response line, such as "HTTP/1.1 200 OK". */
     63   public String getStatus() {
     64     return status;
     65   }
     66 
     67   public MockResponse setResponseCode(int code) {
     68     return setStatus("HTTP/1.1 " + code + " OK");
     69   }
     70 
     71   public MockResponse setStatus(String status) {
     72     this.status = status;
     73     return this;
     74   }
     75 
     76   /** Returns the HTTP headers, such as "Content-Length: 0". */
     77   public Headers getHeaders() {
     78     return headers.build();
     79   }
     80 
     81   /**
     82    * Removes all HTTP headers including any "Content-Length" and
     83    * "Transfer-encoding" headers that were added by default.
     84    */
     85   public MockResponse clearHeaders() {
     86     headers = new Headers.Builder();
     87     return this;
     88   }
     89 
     90   /**
     91    * Adds {@code header} as an HTTP header. For well-formed HTTP {@code header}
     92    * should contain a name followed by a colon and a value.
     93    */
     94   public MockResponse addHeader(String header) {
     95     headers.add(header);
     96     return this;
     97   }
     98 
     99   /**
    100    * Adds a new header with the name and value. This may be used to add multiple
    101    * headers with the same name.
    102    */
    103   public MockResponse addHeader(String name, Object value) {
    104     headers.add(name, String.valueOf(value));
    105     return this;
    106   }
    107 
    108   /**
    109    * Removes all headers named {@code name}, then adds a new header with the
    110    * name and value.
    111    */
    112   public MockResponse setHeader(String name, Object value) {
    113     removeHeader(name);
    114     return addHeader(name, value);
    115   }
    116 
    117   /** Replaces all headers with those specified in {@code headers}. */
    118   public MockResponse setHeaders(Headers headers) {
    119     this.headers = headers.newBuilder();
    120     return this;
    121   }
    122 
    123   /** Removes all headers named {@code name}. */
    124   public MockResponse removeHeader(String name) {
    125     headers.removeAll(name);
    126     return this;
    127   }
    128 
    129   /** Returns a copy of the raw HTTP payload. */
    130   public Buffer getBody() {
    131     return body != null ? body.clone() : null;
    132   }
    133 
    134   public MockResponse setBody(Buffer body) {
    135     setHeader("Content-Length", body.size());
    136     this.body = body.clone(); // Defensive copy.
    137     return this;
    138   }
    139 
    140   /** Sets the response body to the UTF-8 encoded bytes of {@code body}. */
    141   public MockResponse setBody(String body) {
    142     return setBody(new Buffer().writeUtf8(body));
    143   }
    144 
    145   /**
    146    * Sets the response body to {@code body}, chunked every {@code maxChunkSize}
    147    * bytes.
    148    */
    149   public MockResponse setChunkedBody(Buffer body, int maxChunkSize) {
    150     removeHeader("Content-Length");
    151     headers.add(CHUNKED_BODY_HEADER);
    152 
    153     Buffer bytesOut = new Buffer();
    154     while (!body.exhausted()) {
    155       long chunkSize = Math.min(body.size(), maxChunkSize);
    156       bytesOut.writeHexadecimalUnsignedLong(chunkSize);
    157       bytesOut.writeUtf8("\r\n");
    158       bytesOut.write(body, chunkSize);
    159       bytesOut.writeUtf8("\r\n");
    160     }
    161     bytesOut.writeUtf8("0\r\n\r\n"); // Last chunk + empty trailer + CRLF.
    162 
    163     this.body = bytesOut;
    164     return this;
    165   }
    166 
    167   /**
    168    * Sets the response body to the UTF-8 encoded bytes of {@code body}, chunked
    169    * every {@code maxChunkSize} bytes.
    170    */
    171   public MockResponse setChunkedBody(String body, int maxChunkSize) {
    172     return setChunkedBody(new Buffer().writeUtf8(body), maxChunkSize);
    173   }
    174 
    175   public SocketPolicy getSocketPolicy() {
    176     return socketPolicy;
    177   }
    178 
    179   public MockResponse setSocketPolicy(SocketPolicy socketPolicy) {
    180     this.socketPolicy = socketPolicy;
    181     return this;
    182   }
    183 
    184   /**
    185    * Throttles the response body writer to sleep for the given period after each
    186    * series of {@code bytesPerPeriod} bytes are written. Use this to simulate
    187    * network behavior.
    188    */
    189   public MockResponse throttleBody(long bytesPerPeriod, long period, TimeUnit unit) {
    190     this.throttleBytesPerPeriod = bytesPerPeriod;
    191     this.throttlePeriodAmount = period;
    192     this.throttlePeriodUnit = unit;
    193     return this;
    194   }
    195 
    196   public long getThrottleBytesPerPeriod() {
    197     return throttleBytesPerPeriod;
    198   }
    199 
    200   public long getThrottlePeriod(TimeUnit unit) {
    201     return unit.convert(throttlePeriodAmount, throttlePeriodUnit);
    202   }
    203 
    204   /**
    205    * Set the delayed time of the response body to {@code delay}. This applies to the
    206    * response body only; response headers are not affected.
    207    */
    208   public MockResponse setBodyDelay(long delay, TimeUnit unit) {
    209     bodyDelayAmount = delay;
    210     bodyDelayUnit = unit;
    211     return this;
    212   }
    213 
    214   public long getBodyDelay(TimeUnit unit) {
    215     return unit.convert(bodyDelayAmount, bodyDelayUnit);
    216   }
    217 
    218   /**
    219    * When {@link MockWebServer#setProtocols(java.util.List) protocols}
    220    * include {@linkplain com.squareup.okhttp.Protocol#HTTP_2}, this attaches a
    221    * pushed stream to this response.
    222    */
    223   public MockResponse withPush(PushPromise promise) {
    224     this.promises.add(promise);
    225     return this;
    226   }
    227 
    228   /** Returns the streams the server will push with this response. */
    229   public List<PushPromise> getPushPromises() {
    230     return promises;
    231   }
    232 
    233   /**
    234    * Attempts to perform a web socket upgrade on the connection. This will overwrite any previously
    235    * set status or body.
    236    */
    237   public MockResponse withWebSocketUpgrade(WebSocketListener listener) {
    238     setStatus("HTTP/1.1 101 Switching Protocols");
    239     setHeader("Connection", "Upgrade");
    240     setHeader("Upgrade", "websocket");
    241     body = null;
    242     webSocketListener = listener;
    243     return this;
    244   }
    245 
    246   public WebSocketListener getWebSocketListener() {
    247     return webSocketListener;
    248   }
    249 
    250   @Override public String toString() {
    251     return status;
    252   }
    253 }
    254