@Override
  protected HttpParams createHttpParams() {
    HttpParams params = new SyncBasicHttpParams();
    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
    HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
    HttpConnectionParams.setTcpNoDelay(params, true);
    HttpConnectionParams.setSocketBufferSize(params, 8192);

    // determine the release version from packaged version info
    final VersionInfo vi =
        VersionInfo.loadVersionInfo("org.apache.http.client", getClass().getClassLoader());
    final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
    HttpProtocolParams.setUserAgent(params, "Apache-HttpClient/" + release + " (java 1.5)");

    return params;
  }
예제 #2
0
public class HttpServer extends Thread {
  /** Subclass of HttpService which adds some additional hooks to all responses */
  static class MyHttpService extends HttpService {
    MyHttpService(HttpProcessor proc, UriHttpRequestHandlerMapper registry) {
      super(proc, registry);
    }

    @Override
    protected void doService(HttpRequest request, HttpResponse response, HttpContext context)
        throws HttpException, IOException {
      response.addHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate"); // Disable caching
      // Normalize the URI
      super.doService(request, response, context);
    }
  }

  /**
   * Subclass of UriHttpRequestHandlerMapper which normalizes a URI and matches only its path. This
   * is because we ignore the host part (we don't use the {@code Host} header)
   */
  static class MyRequestHandlerMapper extends UriHttpRequestHandlerMapper {
    @Override
    protected String getRequestPath(final HttpRequest request) {
      String s = request.getRequestLine().getUri();
      try {
        URI uri = new URI(s);
        return uri.getPath();
      } catch (URISyntaxException ex) {
        return s;
      } catch (IllegalArgumentException ex) {
        return s;
      }
    }
  }

  private volatile boolean shouldRun = true;
  private final DefaultBHttpServerConnectionFactory connectionFactory;
  private final HttpService httpService;
  private final UriHttpRequestHandlerMapper registry;
  private final Set<Worker> allWorkers = new HashSet<Worker>();
  private static final String serverString =
      String.format(
          "CouchbaseMock/%s (mcd; views) httpcomponents/%s",
          Info.getVersion(), VersionInfo.loadVersionInfo("org.apache.http", null).getRelease());

  private ServerSocketChannel listener;

  public static final String CX_SOCKET = "couchbase.mock.http.socket";
  public static final String CX_AUTH = "couchbase.mock.http.auth";

  /**
   * Creates a new server. To make the server respond to requests, invoke the {@link
   * #bind(java.net.InetSocketAddress)} method to make it use a socket, and then invoke the {@link
   * #start()} method to start it up in the background.
   *
   * <p>Use {@link #register(String, org.apache.http.protocol.HttpRequestHandler)} to add handlers
   * which respond to various URL paths
   */
  public HttpServer() {
    this.connectionFactory = new DefaultBHttpServerConnectionFactory();
    this.registry = new MyRequestHandlerMapper();

    HttpProcessor httpProcessor =
        HttpProcessorBuilder.create()
            .add(new ResponseServer(serverString))
            .add(new ResponseContent())
            .add(new ResponseConnControl())
            .build();

    this.httpService = new MyHttpService(httpProcessor, registry);
    // Register the unknown handler
    register(
        "*",
        new HttpRequestHandler() {
          @Override
          public void handle(HttpRequest request, HttpResponse response, HttpContext context)
              throws HttpException, IOException {
            response.setStatusCode(HttpStatus.SC_NOT_FOUND);
          }
        });
  }

  /**
   * Set the server's listening address
   *
   * @param address The address the server should listen on
   * @throws IOException if a new socket could not be created
   * @see {@link #bind(java.nio.channels.ServerSocketChannel)}
   */
  public void bind(InetSocketAddress address) throws IOException {
    if (listener != null) {
      listener.close();
      listener = null;
    }
    listener = ServerSocketChannel.open();
    listener.socket().bind(address);
  }

  /**
   * Set the server's listening socket.
   *
   * @param newSock An existing listening socket.
   * @see {@link #bind(java.net.InetSocketAddress)}
   */
  public void bind(ServerSocketChannel newSock) {
    listener = newSock;
  }

  /**
   * Register a path with a handler
   *
   * @param pattern The path to register
   * @param handler The handler to handle the path
   * @see {@link org.apache.http.protocol.UriHttpRequestHandlerMapper}
   */
  public void register(String pattern, HttpRequestHandler handler) {
    registry.register(pattern, handler);
    registry.register(pattern + "/", handler);
  }

  /**
   * Unregister a given path. Further requests to paths matching the specified pattern will result
   * in a 404 being delivered to the client
   *
   * @param pattern The pattern to unregister. Must have previously been registered via {@link
   *     #register(String, org.apache.http.protocol.HttpRequestHandler)}
   */
  public void unregister(String pattern) {
    registry.unregister(pattern);
    registry.unregister(pattern + "/");
  }

  class Worker extends Thread {
    final HttpServerConnection htConn;
    final Socket rawSocket;
    private volatile boolean closeRequested = false;

    Worker(HttpServerConnection htConn, Socket rawSocket) {
      this.htConn = htConn;
      this.rawSocket = rawSocket;
      setName("Mock Http Worker: " + rawSocket.getRemoteSocketAddress());
    }

    void stopSocket() {
      closeRequested = true;
      try {
        this.rawSocket.close();
      } catch (IOException ex) {
        //
      }
    }

    private void bail() {
      this.stopSocket();
    }

    public void doReadLoop() {
      HttpContext context = new BasicHttpContext();
      context.setAttribute(CX_SOCKET, rawSocket);

      while (!Thread.interrupted() && this.htConn.isOpen() && HttpServer.this.shouldRun) {
        // Clear the context from any auth settings; since this is done
        // anew on each connection..
        context.removeAttribute(CX_AUTH);

        try {
          HttpServer.this.httpService.handleRequest(htConn, context);
        } catch (ConnectionClosedException ex_closed) {
          break;
        } catch (IOException ex) {
          if (!closeRequested) {
            ex.printStackTrace();
          }
          break;
        } catch (HttpException ex) {
          ex.printStackTrace();
          break;
        } catch (ResponseHandledException ex) {
          break;
        }
      }
      bail();
    }

    @Override
    public void run() {
      try {
        doReadLoop();
      } finally {
        synchronized (HttpServer.this.allWorkers) {
          HttpServer.this.allWorkers.remove(this);
        }
        bail();
      }
    }
  }

  @Override
  public void run() {
    setName("Mock HTTP Listener: " + listener.socket().getInetAddress());
    while (shouldRun) {
      Socket incoming;
      try {
        incoming = listener.accept().socket();
        HttpServerConnection conn = connectionFactory.createConnection(incoming);
        Worker worker = new Worker(conn, incoming);

        synchronized (allWorkers) {
          allWorkers.add(worker);
        }
        worker.start();

      } catch (IOException ex) {
        if (shouldRun) {
          ex.printStackTrace();
        }
      }
    }
  }

  /** Shut down the HTTP server and all its workers, and close the listener socket. */
  public void stopServer() {
    shouldRun = false;
    try {
      listener.close();
    } catch (IOException ex) {
      // Don't care
    }
    while (true) {
      synchronized (allWorkers) {
        if (allWorkers.isEmpty()) {
          break;
        }
        for (Worker w : allWorkers) {
          w.stopSocket();
          w.interrupt();
        }
      }
    }

    try {
      listener.close();
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}
/**
 * Dropwizard Apache Connector.
 *
 * <p>It's a custom version of Jersey's {@link org.glassfish.jersey.client.spi.Connector} that uses
 * Apache's {@link org.apache.http.client.HttpClient} as an HTTP transport implementation.
 *
 * <p>It uses a pre-configured HTTP client by {@link io.dropwizard.client.HttpClientBuilder} rather
 * then creates a client from the Jersey configuration.
 *
 * <p>This approach affords to use the extended configuration of the Apache HttpClient in Dropwizard
 * with a fluent interface of JerseyClient.
 */
public class DropwizardApacheConnector implements Connector {

  private static final String APACHE_HTTP_CLIENT_VERSION =
      VersionInfo.loadVersionInfo(
              "org.apache.http.client", DropwizardApacheConnector.class.getClassLoader())
          .getRelease();

  /** Actual HTTP client */
  private final CloseableHttpClient client;
  /** Default HttpUriRequestConfig */
  private final RequestConfig defaultRequestConfig;

  /** Should a chunked encoding be used in POST requests */
  private final boolean chunkedEncodingEnabled;

  public DropwizardApacheConnector(
      CloseableHttpClient client,
      RequestConfig defaultRequestConfig,
      boolean chunkedEncodingEnabled) {
    this.client = client;
    this.defaultRequestConfig = defaultRequestConfig;
    this.chunkedEncodingEnabled = chunkedEncodingEnabled;
  }

  /** {@inheritDoc} */
  @Override
  public ClientResponse apply(ClientRequest jerseyRequest) {
    try {
      final HttpUriRequest apacheRequest = buildApacheRequest(jerseyRequest);
      final CloseableHttpResponse apacheResponse = client.execute(apacheRequest);

      final StatusLine statusLine = apacheResponse.getStatusLine();
      final Response.StatusType status =
          Statuses.from(statusLine.getStatusCode(), firstNonNull(statusLine.getReasonPhrase(), ""));

      final ClientResponse jerseyResponse = new ClientResponse(status, jerseyRequest);
      for (Header header : apacheResponse.getAllHeaders()) {
        final List<String> headerValues = jerseyResponse.getHeaders().get(header.getName());
        if (headerValues == null) {
          jerseyResponse.getHeaders().put(header.getName(), Lists.newArrayList(header.getValue()));
        } else {
          headerValues.add(header.getValue());
        }
      }

      final HttpEntity httpEntity = apacheResponse.getEntity();
      jerseyResponse.setEntityStream(
          httpEntity != null ? httpEntity.getContent() : new ByteArrayInputStream(new byte[0]));

      return jerseyResponse;
    } catch (Exception e) {
      throw new ProcessingException(e);
    }
  }

  /**
   * Build a new Apache's {@link org.apache.http.client.methods.HttpUriRequest} from Jersey's {@link
   * org.glassfish.jersey.client.ClientRequest}
   *
   * <p>Convert a method, URI, body, headers and override a user-agent if necessary
   *
   * @param jerseyRequest representation of an HTTP request in Jersey
   * @return a new {@link org.apache.http.client.methods.HttpUriRequest}
   */
  private HttpUriRequest buildApacheRequest(ClientRequest jerseyRequest) {
    final RequestBuilder builder =
        RequestBuilder.create(jerseyRequest.getMethod())
            .setUri(jerseyRequest.getUri())
            .setEntity(getHttpEntity(jerseyRequest));
    for (String headerName : jerseyRequest.getHeaders().keySet()) {
      builder.addHeader(headerName, jerseyRequest.getHeaderString(headerName));
    }

    final Optional<RequestConfig> requestConfig = addJerseyRequestConfig(jerseyRequest);
    requestConfig.ifPresent(builder::setConfig);

    return builder.build();
  }

  private Optional<RequestConfig> addJerseyRequestConfig(ClientRequest clientRequest) {
    final Integer timeout =
        clientRequest.resolveProperty(ClientProperties.READ_TIMEOUT, Integer.class);
    final Integer connectTimeout =
        clientRequest.resolveProperty(ClientProperties.CONNECT_TIMEOUT, Integer.class);
    final Boolean followRedirects =
        clientRequest.resolveProperty(ClientProperties.FOLLOW_REDIRECTS, Boolean.class);

    if (timeout != null || connectTimeout != null || followRedirects != null) {
      final RequestConfig.Builder requestConfig = RequestConfig.copy(defaultRequestConfig);

      if (timeout != null) {
        requestConfig.setSocketTimeout(timeout);
      }

      if (connectTimeout != null) {
        requestConfig.setConnectTimeout(connectTimeout);
      }

      if (followRedirects != null) {
        requestConfig.setRedirectsEnabled(followRedirects);
      }

      return Optional.of(requestConfig.build());
    }

    return Optional.empty();
  }

  /**
   * Get an Apache's {@link org.apache.http.HttpEntity} from Jersey's {@link
   * org.glassfish.jersey.client.ClientRequest}
   *
   * <p>Create a custom HTTP entity, because Jersey doesn't provide a request stream or a byte
   * buffer.
   *
   * @param jerseyRequest representation of an HTTP request in Jersey
   * @return a correct {@link org.apache.http.HttpEntity} implementation
   */
  private HttpEntity getHttpEntity(ClientRequest jerseyRequest) {
    if (jerseyRequest.getEntity() == null) {
      return null;
    }

    return chunkedEncodingEnabled
        ? new JerseyRequestHttpEntity(jerseyRequest)
        : new BufferedJerseyRequestHttpEntity(jerseyRequest);
  }

  /** {@inheritDoc} */
  @Override
  public Future<?> apply(final ClientRequest request, final AsyncConnectorCallback callback) {
    // Simulate an asynchronous execution
    return MoreExecutors.newDirectExecutorService()
        .submit(
            () -> {
              try {
                callback.response(apply(request));
              } catch (Exception e) {
                callback.failure(e);
              }
            });
  }

  /** {@inheritDoc} */
  @Override
  public String getName() {
    return "Apache-HttpClient/" + APACHE_HTTP_CLIENT_VERSION;
  }

  /** {@inheritDoc} */
  @Override
  public void close() {
    // Should not close the client here, because it's managed by the Dropwizard environment
  }

  /**
   * A custom {@link org.apache.http.entity.AbstractHttpEntity} that uses a Jersey request as a
   * content source. It's chunked because we don't know the content length beforehand.
   */
  private static class JerseyRequestHttpEntity extends AbstractHttpEntity {

    private ClientRequest clientRequest;

    private JerseyRequestHttpEntity(ClientRequest clientRequest) {
      this.clientRequest = clientRequest;
      setChunked(true);
    }

    /** {@inheritDoc} */
    @Override
    public boolean isRepeatable() {
      return false;
    }

    /** {@inheritDoc} */
    @Override
    public long getContentLength() {
      return -1;
    }

    /**
     * {@inheritDoc}
     *
     * <p>This method isn't supported at will throw an {@link
     * java.lang.UnsupportedOperationException} if invoked.
     */
    @Override
    public InputStream getContent() throws IOException {
      // Shouldn't be called
      throw new UnsupportedOperationException("Reading from the entity is not supported");
    }

    /** {@inheritDoc} */
    @Override
    public void writeTo(final OutputStream outputStream) throws IOException {
      clientRequest.setStreamProvider(contentLength -> outputStream);
      clientRequest.writeEntity();
    }

    /** {@inheritDoc} */
    @Override
    public boolean isStreaming() {
      return false;
    }
  }

  /**
   * A custom {@link org.apache.http.entity.AbstractHttpEntity} that uses a Jersey request as a
   * content source.
   *
   * <p>In contrast to {@link
   * io.dropwizard.client.DropwizardApacheConnector.JerseyRequestHttpEntity} its contents are
   * buffered on initialization.
   */
  private static class BufferedJerseyRequestHttpEntity extends AbstractHttpEntity {

    private static final int BUFFER_INITIAL_SIZE = 512;
    private byte[] buffer;

    private BufferedJerseyRequestHttpEntity(ClientRequest clientRequest) {
      final ByteArrayOutputStream stream = new ByteArrayOutputStream(BUFFER_INITIAL_SIZE);
      clientRequest.setStreamProvider(contentLength -> stream);
      try {
        clientRequest.writeEntity();
      } catch (IOException e) {
        throw new ProcessingException(LocalizationMessages.ERROR_BUFFERING_ENTITY(), e);
      }
      buffer = stream.toByteArray();
      setChunked(false);
    }

    /** {@inheritDoc} */
    @Override
    public boolean isRepeatable() {
      return true;
    }

    /** {@inheritDoc} */
    @Override
    public long getContentLength() {
      return buffer.length;
    }

    /**
     * {@inheritDoc}
     *
     * <p>This method isn't supported at will throw an {@link
     * java.lang.UnsupportedOperationException} if invoked.
     */
    @Override
    public InputStream getContent() throws IOException {
      // Shouldn't be called
      throw new UnsupportedOperationException("Reading from the entity is not supported");
    }

    /** {@inheritDoc} */
    @Override
    public void writeTo(OutputStream outstream) throws IOException {
      outstream.write(buffer);
      outstream.flush();
    }

    /** {@inheritDoc} */
    @Override
    public boolean isStreaming() {
      return false;
    }
  }
}
예제 #4
0
 public String getVersion() {
   VersionInfo vinfo =
       VersionInfo.loadVersionInfo(
           "org.apache.http", Thread.currentThread().getContextClassLoader());
   return vinfo.getRelease();
 }