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 getLastSentHttpRequestInfo()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 addPendingHttpResponse(int statusCode, String responseBody, Header... headers)37 public void addPendingHttpResponse(int statusCode, String responseBody, Header... headers) { 38 addPendingHttpResponse(new TestHttpResponse(statusCode, responseBody, headers)); 39 } 40 addPendingHttpResponse(final HttpResponse httpResponse)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 addPendingHttpResponse(HttpResponseGenerator httpResponseGenerator)50 public void addPendingHttpResponse(HttpResponseGenerator httpResponseGenerator) { 51 pendingHttpResponses.add(httpResponseGenerator); 52 } 53 addHttpResponseRule(String method, String uri, HttpResponse response)54 public void addHttpResponseRule(String method, String uri, HttpResponse response) { 55 addHttpResponseRule(new DefaultRequestMatcher(method, uri), response); 56 } 57 addHttpResponseRule(String uri, HttpResponse response)58 public void addHttpResponseRule(String uri, HttpResponse response) { 59 addHttpResponseRule(new UriRequestMatcher(uri), response); 60 } 61 addHttpResponseRule(String uri, String response)62 public void addHttpResponseRule(String uri, String response) { 63 addHttpResponseRule(new UriRequestMatcher(uri), new TestHttpResponse(200, response)); 64 } 65 addHttpResponseRule(RequestMatcher requestMatcher, HttpResponse response)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 */ addHttpResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses)76 public void addHttpResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses) { 77 addHttpResponseRule(new RequestMatcherResponseRule(requestMatcher, responses)); 78 } 79 addHttpResponseRule(HttpEntityStub.ResponseRule responseRule)80 public void addHttpResponseRule(HttpEntityStub.ResponseRule responseRule) { 81 httpResponseRules.add(0, responseRule); 82 } 83 setDefaultHttpResponse(HttpResponse defaultHttpResponse)84 public void setDefaultHttpResponse(HttpResponse defaultHttpResponse) { 85 this.defaultHttpResponse = defaultHttpResponse; 86 } 87 setDefaultHttpResponse(int statusCode, String responseBody)88 public void setDefaultHttpResponse(int statusCode, String responseBody) { 89 setDefaultHttpResponse(new TestHttpResponse(statusCode, responseBody)); 90 } 91 findResponse(HttpRequest httpRequest)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 emulateRequest(HttpHost httpHost, HttpRequest httpRequest, HttpContext httpContext, RequestDirector requestDirector)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 hasPendingResponses()127 public boolean hasPendingResponses() { 128 return !pendingHttpResponses.isEmpty(); 129 } 130 hasRequestInfos()131 public boolean hasRequestInfos() { 132 return !httpRequestInfos.isEmpty(); 133 } 134 clearRequestInfos()135 public void clearRequestInfos() { 136 httpRequestInfos.clear(); 137 } 138 hasResponseRules()139 public boolean hasResponseRules() { 140 return !httpResponseRules.isEmpty(); 141 } 142 hasRequestMatchingRule(RequestMatcher rule)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 getDefaultResponse()152 public HttpResponse getDefaultResponse() { 153 return defaultResponse; 154 } 155 getSentHttpRequestInfo(int index)156 public HttpRequestInfo getSentHttpRequestInfo(int index) { 157 return httpRequestInfos.get(index); 158 } 159 getSentHttpRequestInfos()160 public List<HttpRequestInfo> getSentHttpRequestInfos() { 161 return new ArrayList<HttpRequestInfo>(httpRequestInfos); 162 } 163 clearHttpResponseRules()164 public void clearHttpResponseRules() { 165 httpResponseRules.clear(); 166 } 167 clearPendingHttpResponses()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 */ interceptHttpRequests(boolean interceptHttpRequests)178 public void interceptHttpRequests(boolean interceptHttpRequests) { 179 this.interceptHttpRequests = interceptHttpRequests; 180 } 181 isInterceptingHttpRequests()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 RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpResponse responseToGive)193 public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpResponse responseToGive) { 194 this.requestMatcher = requestMatcher; 195 this.responseToGive = responseToGive; 196 } 197 RequestMatcherResponseRule(RequestMatcher requestMatcher, IOException ioException)198 public RequestMatcherResponseRule(RequestMatcher requestMatcher, IOException ioException) { 199 this.requestMatcher = requestMatcher; 200 this.ioException = ioException; 201 } 202 RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpException httpException)203 public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpException httpException) { 204 this.requestMatcher = requestMatcher; 205 this.httpException = httpException; 206 } 207 RequestMatcherResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses)208 public RequestMatcherResponseRule(RequestMatcher requestMatcher, List<? extends HttpResponse> responses) { 209 this.requestMatcher = requestMatcher; 210 this.responses = responses; 211 } 212 213 @Override matches(HttpRequest request)214 public boolean matches(HttpRequest request) { 215 return requestMatcher.matches(request); 216 } 217 218 @Override getResponse()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 DefaultRequestMatcher(String method, String uri)237 public DefaultRequestMatcher(String method, String uri) { 238 this.method = method; 239 this.uri = uri; 240 } 241 242 @Override matches(HttpRequest request)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 UriRequestMatcher(String uri)252 public UriRequestMatcher(String uri) { 253 this.uri = uri; 254 } 255 256 @Override matches(HttpRequest request)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 */ matches(HttpEntity actualPostBody)277 boolean matches(HttpEntity actualPostBody) throws IOException; 278 } 279 method(String method)280 public RequestMatcherBuilder method(String method) { 281 this.method = method; 282 return this; 283 } 284 host(String hostname)285 public RequestMatcherBuilder host(String hostname) { 286 this.hostname = hostname; 287 return this; 288 } 289 path(String path)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 param(String name, String value)298 public RequestMatcherBuilder param(String name, String value) { 299 params.put(name, value); 300 return this; 301 } 302 noParams()303 public RequestMatcherBuilder noParams() { 304 noParams = true; 305 return this; 306 } 307 postBody(PostBodyMatcher postBodyMatcher)308 public RequestMatcherBuilder postBody(PostBodyMatcher postBodyMatcher) { 309 this.postBodyMatcher = postBodyMatcher; 310 return this; 311 } 312 header(String name, String value)313 public RequestMatcherBuilder header(String name, String value) { 314 headers.put(name, value); 315 return this; 316 } 317 318 @Override matches(HttpRequest request)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 getHostname()364 String getHostname() { 365 return hostname; 366 } 367 getPath()368 String getPath() { 369 return path; 370 } 371 getParam(String key)372 String getParam(String key) { 373 return params.get(key); 374 } 375 getHeader(String key)376 String getHeader(String key) { 377 return headers.get(key); 378 } 379 isNoParams()380 boolean isNoParams() { 381 return noParams; 382 } 383 getMethod()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 UriRegexMatcher(String method, String uriRegex)393 public UriRegexMatcher(String method, String uriRegex) { 394 this.method = method; 395 this.uriRegex = Pattern.compile(uriRegex); 396 } 397 398 @Override matches(HttpRequest request)399 public boolean matches(HttpRequest request) { 400 return request.getRequestLine().getMethod().equals(method) && 401 uriRegex.matcher(request.getRequestLine().getUri()).matches(); 402 } 403 } 404 } 405