Home | History | Annotate | Download | only in http
      1 package com.xtremelabs.robolectric.tester.org.apache.http;
      2 
      3 import com.xtremelabs.robolectric.Robolectric;
      4 import com.xtremelabs.robolectric.shadows.HttpResponseGenerator;
      5 import org.apache.http.*;
      6 import org.apache.http.client.RequestDirector;
      7 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
      8 import org.apache.http.conn.ConnectTimeoutException;
      9 import org.apache.http.params.HttpConnectionParams;
     10 import org.apache.http.params.HttpParams;
     11 import org.apache.http.protocol.HttpContext;
     12 
     13 import java.io.IOException;
     14 import java.net.URI;
     15 import java.util.ArrayList;
     16 import java.util.HashMap;
     17 import java.util.List;
     18 import java.util.Map;
     19 import java.util.regex.Pattern;
     20 
     21 public class FakeHttpLayer {
     22     List<HttpResponseGenerator> pendingHttpResponses = new ArrayList<HttpResponseGenerator>();
     23     List<HttpRequestInfo> httpRequestInfos = new ArrayList<HttpRequestInfo>();
     24     List<HttpEntityStub.ResponseRule> httpResponseRules = new ArrayList<HttpEntityStub.ResponseRule>();
     25     HttpResponse defaultHttpResponse;
     26     private HttpResponse defaultResponse;
     27     private boolean interceptHttpRequests = true;
     28 
     29     public HttpRequestInfo getLastSentHttpRequestInfo() {
     30         List<HttpRequestInfo> requestInfos = Robolectric.getFakeHttpLayer().getSentHttpRequestInfos();
     31         if (requestInfos.isEmpty()) {
     32             return null;
     33         }
     34         return requestInfos.get(requestInfos.size() - 1);
     35     }
     36 
     37     public void addPendingHttpResponse(int statusCode, String responseBody, Header... headers) {
     38         addPendingHttpResponse(new TestHttpResponse(statusCode, responseBody, headers));
     39     }
     40 
     41     public void addPendingHttpResponse(final HttpResponse httpResponse) {
     42         addPendingHttpResponse(new HttpResponseGenerator() {
     43             @Override
     44             public HttpResponse getResponse(HttpRequest request) {
     45                 return httpResponse;
     46             }
     47         });
     48     }
     49 
     50     public void addPendingHttpResponse(HttpResponseGenerator httpResponseGenerator) {
     51         pendingHttpResponses.add(httpResponseGenerator);
     52     }
     53 
     54     public void addHttpResponseRule(String method, String uri, HttpResponse response) {
     55         addHttpResponseRule(new DefaultRequestMatcher(method, uri), response);
     56     }
     57 
     58     public void addHttpResponseRule(String uri, HttpResponse response) {
     59         addHttpResponseRule(new UriRequestMatcher(uri), response);
     60     }
     61 
     62     public void addHttpResponseRule(String uri, String response) {
     63         addHttpResponseRule(new UriRequestMatcher(uri), new TestHttpResponse(200, response));
     64     }
     65 
     66     public void addHttpResponseRule(RequestMatcher requestMatcher, HttpResponse response) {
     67         addHttpResponseRule(new RequestMatcherResponseRule(requestMatcher, response));
     68     }
     69 
     70     /**
     71      * Add a response rule.
     72      *
     73      * @param requestMatcher Request matcher
     74      * @param responses      A list of responses that are returned to matching requests in order from first to last.
     75      */
     76     public void addHttpResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses) {
     77         addHttpResponseRule(new RequestMatcherResponseRule(requestMatcher, responses));
     78     }
     79 
     80     public void addHttpResponseRule(HttpEntityStub.ResponseRule responseRule) {
     81         httpResponseRules.add(0, responseRule);
     82     }
     83 
     84     public void setDefaultHttpResponse(HttpResponse defaultHttpResponse) {
     85         this.defaultHttpResponse = defaultHttpResponse;
     86     }
     87 
     88     public void setDefaultHttpResponse(int statusCode, String responseBody) {
     89         setDefaultHttpResponse(new TestHttpResponse(statusCode, responseBody));
     90     }
     91 
     92     private HttpResponse findResponse(HttpRequest httpRequest) throws HttpException, IOException {
     93         if (!pendingHttpResponses.isEmpty()) {
     94             return pendingHttpResponses.remove(0).getResponse(httpRequest);
     95         }
     96 
     97         for (HttpEntityStub.ResponseRule httpResponseRule : httpResponseRules) {
     98             if (httpResponseRule.matches(httpRequest)) {
     99                 return httpResponseRule.getResponse();
    100             }
    101         }
    102 
    103         return defaultHttpResponse;
    104     }
    105 
    106     public HttpResponse emulateRequest(HttpHost httpHost, HttpRequest httpRequest, HttpContext httpContext, RequestDirector requestDirector) throws HttpException, IOException {
    107         HttpResponse httpResponse = findResponse(httpRequest);
    108 
    109         if (httpResponse == null) {
    110             throw new RuntimeException("Unexpected call to execute, no pending responses are available. See Robolectric.addPendingResponse(). Request was: " +
    111                     httpRequest.getRequestLine().getMethod() + " " + httpRequest.getRequestLine().getUri());
    112         } else {
    113             HttpParams params = httpResponse.getParams();
    114 
    115             if (HttpConnectionParams.getConnectionTimeout(params) < 0) {
    116                 throw new ConnectTimeoutException("Socket is not connected");
    117             } else if (HttpConnectionParams.getSoTimeout(params) < 0) {
    118                 throw new ConnectTimeoutException("The operation timed out");
    119             }
    120         }
    121 
    122         httpRequestInfos.add(new HttpRequestInfo(httpRequest, httpHost, httpContext, requestDirector));
    123 
    124         return httpResponse;
    125     }
    126 
    127     public boolean hasPendingResponses() {
    128         return !pendingHttpResponses.isEmpty();
    129     }
    130 
    131     public boolean hasRequestInfos() {
    132         return !httpRequestInfos.isEmpty();
    133     }
    134 
    135     public void clearRequestInfos() {
    136         httpRequestInfos.clear();
    137     }
    138 
    139     public boolean hasResponseRules() {
    140         return !httpResponseRules.isEmpty();
    141     }
    142 
    143     public boolean hasRequestMatchingRule(RequestMatcher rule) {
    144         for (HttpRequestInfo requestInfo : httpRequestInfos) {
    145             if (rule.matches(requestInfo.httpRequest)) {
    146                 return true;
    147             }
    148         }
    149         return false;
    150     }
    151 
    152     public HttpResponse getDefaultResponse() {
    153         return defaultResponse;
    154     }
    155 
    156     public HttpRequestInfo getSentHttpRequestInfo(int index) {
    157         return httpRequestInfos.get(index);
    158     }
    159 
    160     public List<HttpRequestInfo> getSentHttpRequestInfos() {
    161         return new ArrayList<HttpRequestInfo>(httpRequestInfos);
    162     }
    163 
    164     public void clearHttpResponseRules() {
    165         httpResponseRules.clear();
    166     }
    167 
    168     public void clearPendingHttpResponses() {
    169         pendingHttpResponses.clear();
    170     }
    171 
    172     /**
    173      * You can disable Robolectric's fake HTTP layer temporarily
    174      * by calling this method.
    175      * @param interceptHttpRequests whether all HTTP requests should be
    176      *                              intercepted (true by default)
    177      */
    178     public void interceptHttpRequests(boolean interceptHttpRequests) {
    179         this.interceptHttpRequests = interceptHttpRequests;
    180     }
    181 
    182     public boolean isInterceptingHttpRequests() {
    183         return interceptHttpRequests;
    184     }
    185 
    186     public static class RequestMatcherResponseRule implements HttpEntityStub.ResponseRule {
    187         private RequestMatcher requestMatcher;
    188         private HttpResponse responseToGive;
    189         private IOException ioException;
    190         private HttpException httpException;
    191         private List<? extends HttpResponse> responses;
    192 
    193         public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpResponse responseToGive) {
    194             this.requestMatcher = requestMatcher;
    195             this.responseToGive = responseToGive;
    196         }
    197 
    198         public RequestMatcherResponseRule(RequestMatcher requestMatcher, IOException ioException) {
    199             this.requestMatcher = requestMatcher;
    200             this.ioException = ioException;
    201         }
    202 
    203         public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpException httpException) {
    204             this.requestMatcher = requestMatcher;
    205             this.httpException = httpException;
    206         }
    207 
    208         public RequestMatcherResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses) {
    209             this.requestMatcher = requestMatcher;
    210             this.responses = responses;
    211         }
    212 
    213         @Override
    214         public boolean matches(HttpRequest request) {
    215             return requestMatcher.matches(request);
    216         }
    217 
    218         @Override
    219         public HttpResponse getResponse() throws HttpException, IOException {
    220             if (httpException != null) throw httpException;
    221             if (ioException != null) throw ioException;
    222             if (responseToGive != null) {
    223                 return responseToGive;
    224             } else {
    225                 if (responses.isEmpty()) {
    226                     throw new RuntimeException("No more responses left to give");
    227                 }
    228                 return responses.remove(0);
    229             }
    230         }
    231     }
    232 
    233     public static class DefaultRequestMatcher implements RequestMatcher {
    234         private String method;
    235         private String uri;
    236 
    237         public DefaultRequestMatcher(String method, String uri) {
    238             this.method = method;
    239             this.uri = uri;
    240         }
    241 
    242         @Override
    243         public boolean matches(HttpRequest request) {
    244             return request.getRequestLine().getMethod().equals(method) &&
    245                     request.getRequestLine().getUri().equals(uri);
    246         }
    247     }
    248 
    249     public static class UriRequestMatcher implements RequestMatcher {
    250         private String uri;
    251 
    252         public UriRequestMatcher(String uri) {
    253             this.uri = uri;
    254         }
    255 
    256         @Override
    257         public boolean matches(HttpRequest request) {
    258             return request.getRequestLine().getUri().equals(uri);
    259         }
    260     }
    261 
    262     public static class RequestMatcherBuilder implements RequestMatcher {
    263         private String method, hostname, path;
    264         private boolean noParams;
    265         private Map<String, String> params = new HashMap<String, String>();
    266         private Map<String, String> headers = new HashMap<String, String>();
    267         private PostBodyMatcher postBodyMatcher;
    268 
    269         public interface PostBodyMatcher {
    270             /**
    271              * Hint: you can use EntityUtils.toString(actualPostBody) to help you implement your matches method.
    272              *
    273              * @param actualPostBody The post body of the actual request that we are matching against.
    274              * @return true if you consider the body to match
    275              * @throws IOException Get turned into a RuntimeException to cause your test to fail.
    276              */
    277             boolean matches(HttpEntity actualPostBody) throws IOException;
    278         }
    279 
    280         public RequestMatcherBuilder method(String method) {
    281             this.method = method;
    282             return this;
    283         }
    284 
    285         public RequestMatcherBuilder host(String hostname) {
    286             this.hostname = hostname;
    287             return this;
    288         }
    289 
    290         public RequestMatcherBuilder path(String path) {
    291             if (path.startsWith("/")) {
    292                 throw new RuntimeException("Path should not start with '/'");
    293             }
    294             this.path = "/" + path;
    295             return this;
    296         }
    297 
    298         public RequestMatcherBuilder param(String name, String value) {
    299             params.put(name, value);
    300             return this;
    301         }
    302 
    303         public RequestMatcherBuilder noParams() {
    304             noParams = true;
    305             return this;
    306         }
    307 
    308         public RequestMatcherBuilder postBody(PostBodyMatcher postBodyMatcher) {
    309             this.postBodyMatcher = postBodyMatcher;
    310             return this;
    311         }
    312 
    313         public RequestMatcherBuilder header(String name, String value) {
    314             headers.put(name, value);
    315             return this;
    316         }
    317 
    318         @Override
    319         public boolean matches(HttpRequest request) {
    320             URI uri = URI.create(request.getRequestLine().getUri());
    321             if (method != null && !method.equals(request.getRequestLine().getMethod())) {
    322                 return false;
    323             }
    324             if (hostname != null && !hostname.equals(uri.getHost())) {
    325                 return false;
    326             }
    327             if (path != null && !path.equals(uri.getRawPath())) {
    328                 return false;
    329             }
    330             if (noParams && !uri.getRawQuery().equals(null)) {
    331                 return false;
    332             }
    333             if (params.size() > 0) {
    334                 Map<String, String> requestParams = ParamsParser.parseParams(request);
    335                 if (!requestParams.equals(params)) {
    336                     return false;
    337                 }
    338             }
    339             if (headers.size() > 0) {
    340                 Map<String, String> actualRequestHeaders = new HashMap<String, String>();
    341                 for (Header header : request.getAllHeaders()) {
    342                     actualRequestHeaders.put(header.getName(), header.getValue());
    343                 }
    344                 if (!headers.equals(actualRequestHeaders)) {
    345                     return false;
    346                 }
    347             }
    348             if (postBodyMatcher != null) {
    349                 if (!(request instanceof HttpEntityEnclosingRequestBase)) {
    350                     return false;
    351                 }
    352                 HttpEntityEnclosingRequestBase postOrPut = (HttpEntityEnclosingRequestBase) request;
    353                 try {
    354                     if (!postBodyMatcher.matches(postOrPut.getEntity())) {
    355                         return false;
    356                     }
    357                 } catch (IOException e) {
    358                     throw new RuntimeException(e);
    359                 }
    360             }
    361             return true;
    362         }
    363 
    364         String getHostname() {
    365             return hostname;
    366         }
    367 
    368         String getPath() {
    369             return path;
    370         }
    371 
    372         String getParam(String key) {
    373             return params.get(key);
    374         }
    375 
    376         String getHeader(String key) {
    377             return headers.get(key);
    378         }
    379 
    380         boolean isNoParams() {
    381             return noParams;
    382         }
    383 
    384         String getMethod() {
    385             return method;
    386         }
    387     }
    388 
    389     public static class UriRegexMatcher implements RequestMatcher {
    390         private String method;
    391         private final Pattern uriRegex;
    392 
    393         public UriRegexMatcher(String method, String uriRegex) {
    394             this.method = method;
    395             this.uriRegex = Pattern.compile(uriRegex);
    396         }
    397 
    398         @Override
    399         public boolean matches(HttpRequest request) {
    400             return request.getRequestLine().getMethod().equals(method) &&
    401                     uriRegex.matcher(request.getRequestLine().getUri()).matches();
    402         }
    403     }
    404 }
    405