All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.httpclient.FakeHttpLayer Maven / Gradle / Ivy

package org.robolectric.shadows.httpclient;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.RequestDirector;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;

public class FakeHttpLayer {
  private final List pendingHttpResponses = new ArrayList<>();
  private final List httpRequestInfos = new ArrayList<>();
  private final List httpResponses = new ArrayList<>();
  private final List httpResponseRules = new ArrayList<>();
  private HttpResponse defaultHttpResponse;
  private boolean interceptHttpRequests = true;
  private boolean logHttpRequests = false;
  private List httpResposeContent = new ArrayList<>();
  private boolean interceptResponseContent;

  public HttpRequestInfo getLastSentHttpRequestInfo() {
    List requestInfos = getSentHttpRequestInfos();
    if (requestInfos.isEmpty()) {
      return null;
    }
    return requestInfos.get(requestInfos.size() - 1);
  }

  public void addPendingHttpResponse(int statusCode, String responseBody, Header... headers) {
    addPendingHttpResponse(new TestHttpResponse(statusCode, responseBody, headers));
  }

  public void addPendingHttpResponse(final HttpResponse httpResponse) {
    addPendingHttpResponse(
        new HttpResponseGenerator() {
          @Override
          public HttpResponse getResponse(HttpRequest request) {
            return httpResponse;
          }
        });
  }

  public void addPendingHttpResponse(HttpResponseGenerator httpResponseGenerator) {
    pendingHttpResponses.add(httpResponseGenerator);
  }

  public void addHttpResponseRule(String method, String uri, HttpResponse response) {
    addHttpResponseRule(new DefaultRequestMatcher(method, uri), response);
  }

  public void addHttpResponseRule(String uri, HttpResponse response) {
    addHttpResponseRule(new UriRequestMatcher(uri), response);
  }

  public void addHttpResponseRule(String uri, String response) {
    addHttpResponseRule(new UriRequestMatcher(uri), new TestHttpResponse(200, response));
  }

  public void addHttpResponseRule(RequestMatcher requestMatcher, HttpResponse response) {
    addHttpResponseRule(new RequestMatcherResponseRule(requestMatcher, response));
  }

  /**
   * Add a response rule.
   *
   * @param requestMatcher Request matcher
   * @param responses A list of responses that are returned to matching requests in order from first
   *     to last.
   */
  public void addHttpResponseRule(
      RequestMatcher requestMatcher, List responses) {
    addHttpResponseRule(new RequestMatcherResponseRule(requestMatcher, responses));
  }

  public void addHttpResponseRule(HttpEntityStub.ResponseRule responseRule) {
    httpResponseRules.add(0, responseRule);
  }

  public void setDefaultHttpResponse(HttpResponse defaultHttpResponse) {
    this.defaultHttpResponse = defaultHttpResponse;
  }

  public void setDefaultHttpResponse(int statusCode, String responseBody) {
    setDefaultHttpResponse(new TestHttpResponse(statusCode, responseBody));
  }

  private HttpResponse findResponse(HttpRequest httpRequest) throws HttpException, IOException {
    if (!pendingHttpResponses.isEmpty()) {
      return pendingHttpResponses.remove(0).getResponse(httpRequest);
    }

    for (HttpEntityStub.ResponseRule httpResponseRule : httpResponseRules) {
      if (httpResponseRule.matches(httpRequest)) {
        return httpResponseRule.getResponse();
      }
    }

    System.err.println("Unexpected HTTP call " + httpRequest.getRequestLine());

    return defaultHttpResponse;
  }

  public HttpResponse emulateRequest(
      HttpHost httpHost,
      HttpRequest httpRequest,
      HttpContext httpContext,
      RequestDirector requestDirector)
      throws HttpException, IOException {
    if (logHttpRequests) {
      System.out.println("  <-- " + httpRequest.getRequestLine());
    }
    HttpResponse httpResponse = findResponse(httpRequest);
    if (logHttpRequests) {
      System.out.println(
          "  --> " + (httpResponse == null ? null : httpResponse.getStatusLine().getStatusCode()));
    }

    if (httpResponse == null) {
      throw new RuntimeException(
          "Unexpected call to execute, no pending responses are available. See"
              + " Robolectric.addPendingResponse(). Request was: "
              + httpRequest.getRequestLine().getMethod()
              + " "
              + httpRequest.getRequestLine().getUri());
    } else {
      HttpParams params = httpResponse.getParams();

      if (HttpConnectionParams.getConnectionTimeout(params) < 0) {
        throw new ConnectTimeoutException("Socket is not connected");
      } else if (HttpConnectionParams.getSoTimeout(params) < 0) {
        throw new ConnectTimeoutException("The operation timed out");
      }
    }

    addRequestInfo(new HttpRequestInfo(httpRequest, httpHost, httpContext, requestDirector));
    addHttpResponse(httpResponse);
    return httpResponse;
  }

  public boolean hasPendingResponses() {
    return !pendingHttpResponses.isEmpty();
  }

  public boolean hasRequestInfos() {
    return !httpRequestInfos.isEmpty();
  }

  public void clearRequestInfos() {
    httpRequestInfos.clear();
  }

  /**
   * This method is not supposed to be consumed by tests. This exists solely for the purpose of
   * logging real HTTP requests, so that functional/integration tests can verify if those were made,
   * without messing with the fake http layer to actually perform the http call, instead of
   * returning a mocked response.
   *
   * 

If you are just using mocked http calls, you should not even notice this method here. * * @param requestInfo Request info object to add. */ public void addRequestInfo(HttpRequestInfo requestInfo) { httpRequestInfos.add(requestInfo); } public boolean hasResponseRules() { return !httpResponseRules.isEmpty(); } public boolean hasRequestMatchingRule(RequestMatcher rule) { for (HttpRequestInfo requestInfo : httpRequestInfos) { if (rule.matches(requestInfo.httpRequest)) { return true; } } return false; } public HttpRequestInfo getSentHttpRequestInfo(int index) { return httpRequestInfos.get(index); } public HttpRequestInfo getNextSentHttpRequestInfo() { return httpRequestInfos.size() > 0 ? httpRequestInfos.remove(0) : null; } public void logHttpRequests() { logHttpRequests = true; } public void silence() { logHttpRequests = false; } public List getSentHttpRequestInfos() { return new ArrayList<>(httpRequestInfos); } public void clearHttpResponseRules() { httpResponseRules.clear(); } public void clearPendingHttpResponses() { pendingHttpResponses.clear(); } /** * This method return a list containing all the HTTP responses logged by the fake http layer, be * it mocked http responses, be it real http calls (if {code}interceptHttpRequests{/code} is set * to false). * *

It doesn't make much sense to call this method if said property is set to true, as you * yourself are providing the response, but it's here nonetheless. * * @return List of all HTTP Responses logged by the fake http layer. */ public List getHttpResponses() { return new ArrayList<>(httpResponses); } /** * As a consumer of the fake http call, you should never call this method. This should be used * solely by components that exercises http calls. * * @param response The final response received by the server */ public void addHttpResponse(HttpResponse response) { this.httpResponses.add(response); } public void addHttpResponseContent(byte[] content) { this.httpResposeContent.add(content); } public List getHttpResposeContentList() { return httpResposeContent; } /** * Helper method that returns the latest received response from the server. * * @return The latest HTTP response or null, if no responses are available */ public HttpResponse getLastHttpResponse() { if (httpResponses.isEmpty()) return null; return httpResponses.get(httpResponses.size() - 1); } /** * Call this method if you want to ensure that there's no http responses logged from this point * until the next response arrives. Helpful to ensure that the state is "clear" before actions are * executed. */ public void clearHttpResponses() { this.httpResponses.clear(); } /** * You can disable Robolectric's fake HTTP layer temporarily by calling this method. * * @param interceptHttpRequests whether all HTTP requests should be intercepted (true by default) */ public void interceptHttpRequests(boolean interceptHttpRequests) { this.interceptHttpRequests = interceptHttpRequests; } public boolean isInterceptingHttpRequests() { return interceptHttpRequests; } public void interceptResponseContent(boolean interceptResponseContent) { this.interceptResponseContent = interceptResponseContent; } public boolean isInterceptingResponseContent() { return interceptResponseContent; } public static class RequestMatcherResponseRule implements HttpEntityStub.ResponseRule { private RequestMatcher requestMatcher; private HttpResponse responseToGive; private IOException ioException; private HttpException httpException; private List responses; public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpResponse responseToGive) { this.requestMatcher = requestMatcher; this.responseToGive = responseToGive; } public RequestMatcherResponseRule(RequestMatcher requestMatcher, IOException ioException) { this.requestMatcher = requestMatcher; this.ioException = ioException; } public RequestMatcherResponseRule(RequestMatcher requestMatcher, HttpException httpException) { this.requestMatcher = requestMatcher; this.httpException = httpException; } public RequestMatcherResponseRule( RequestMatcher requestMatcher, List responses) { this.requestMatcher = requestMatcher; this.responses = responses; } @Override public boolean matches(HttpRequest request) { return requestMatcher.matches(request); } @Override public HttpResponse getResponse() throws HttpException, IOException { if (httpException != null) throw httpException; if (ioException != null) throw ioException; if (responseToGive != null) { return responseToGive; } else { if (responses.isEmpty()) { throw new RuntimeException("No more responses left to give"); } return responses.remove(0); } } } public static class DefaultRequestMatcher implements RequestMatcher { private String method; private String uri; public DefaultRequestMatcher(String method, String uri) { this.method = method; this.uri = uri; } @Override public boolean matches(HttpRequest request) { return request.getRequestLine().getMethod().equals(method) && request.getRequestLine().getUri().equals(uri); } } public static class UriRequestMatcher implements RequestMatcher { private String uri; public UriRequestMatcher(String uri) { this.uri = uri; } @Override public boolean matches(HttpRequest request) { return request.getRequestLine().getUri().equals(uri); } } public static class RequestMatcherBuilder implements RequestMatcher { private String method, hostname, path; private boolean noParams; private Map params = new HashMap<>(); private Map headers = new HashMap<>(); private PostBodyMatcher postBodyMatcher; public interface PostBodyMatcher { /** * Hint: you can use EntityUtils.toString(actualPostBody) to help you implement your matches * method. * * @param actualPostBody The post body of the actual request that we are matching against. * @return true if you consider the body to match * @throws IOException Get turned into a RuntimeException to cause your test to fail. */ boolean matches(HttpEntity actualPostBody) throws IOException; } public RequestMatcherBuilder method(String method) { this.method = method; return this; } public RequestMatcherBuilder host(String hostname) { this.hostname = hostname; return this; } public RequestMatcherBuilder path(String path) { if (path.startsWith("/")) { throw new RuntimeException("Path should not start with '/'"); } this.path = "/" + path; return this; } public RequestMatcherBuilder param(String name, String value) { params.put(name, value); return this; } public RequestMatcherBuilder noParams() { noParams = true; return this; } public RequestMatcherBuilder postBody(PostBodyMatcher postBodyMatcher) { this.postBodyMatcher = postBodyMatcher; return this; } public RequestMatcherBuilder header(String name, String value) { headers.put(name, value); return this; } @Override public boolean matches(HttpRequest request) { URI uri = URI.create(request.getRequestLine().getUri()); if (method != null && !method.equals(request.getRequestLine().getMethod())) { return false; } if (hostname != null && !hostname.equals(uri.getHost())) { return false; } if (path != null && !path.equals(uri.getRawPath())) { return false; } if (noParams && uri.getRawQuery() != null) { return false; } if (params.size() > 0) { Map requestParams = ParamsParser.parseParams(request); if (!requestParams.equals(params)) { return false; } } if (headers.size() > 0) { Map actualRequestHeaders = new HashMap<>(); for (Header header : request.getAllHeaders()) { actualRequestHeaders.put(header.getName(), header.getValue()); } if (!headers.equals(actualRequestHeaders)) { return false; } } if (postBodyMatcher != null) { if (!(request instanceof HttpEntityEnclosingRequestBase)) { return false; } HttpEntityEnclosingRequestBase postOrPut = (HttpEntityEnclosingRequestBase) request; try { if (!postBodyMatcher.matches(postOrPut.getEntity())) { return false; } } catch (IOException e) { throw new RuntimeException(e); } } return true; } public String getHostname() { return hostname; } public String getPath() { return path; } public String getParam(String key) { return params.get(key); } public String getHeader(String key) { return headers.get(key); } public boolean isNoParams() { return noParams; } public String getMethod() { return method; } } public static class UriRegexMatcher implements RequestMatcher { private String method; private final Pattern uriRegex; public UriRegexMatcher(String method, String uriRegex) { this.method = method; this.uriRegex = Pattern.compile(uriRegex); } @Override public boolean matches(HttpRequest request) { return request.getRequestLine().getMethod().equals(method) && uriRegex.matcher(request.getRequestLine().getUri()).matches(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy