# Vert.x Http Proxy

Vert.x Http Proxy is a reverse proxy based on Vert.x, it aims to implement reusable reverse proxy logic to focus on higher concerns.

| This module has Tech Preview status, this means the API can change between versions.

## Using Vert.x Http Proxy

To use Vert.x Http Proxy, add the following dependency to the dependencies section of your build descriptor:

- Maven (in your `pom.xml`):

    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-http-proxy</artifactId>
      <version>5.0.2</version>
    </dependency>

- Gradle (in your `build.gradle` file):

    dependencies {
      compile 'io.vertx:vertx-http-proxy:5.0.2'
    }

## Reverse proxy server

In order to accomplish a reverse proxy with Vert.x Http Proxy you need the following:

1. Proxy Server that handles user-agent requests and forward them to the origin server

2. Origin Server that handles requests from the proxy server and respond accordingly

You can create a proxy server that listens to port `8080` and implement reverse proxy logic

    HttpClient proxyClient = vertx.createHttpClient();

    HttpProxy proxy = HttpProxy.reverseProxy(proxyClient);
    proxy.origin(7070, "origin");

    HttpServer proxyServer = vertx.createHttpServer();

    proxyServer.requestHandler(proxy).listen(8080);

All user-agent requests are forwarded to the origin server conveniently.

### Origin server routing

You can create a proxy that forwards all the traffic to a single server like seen before.

You can set an origin selector to route the traffic to a given server:

    proxy.origin(OriginRequestProvider.selector(proxyContext -> resolveOriginAddress(proxyContext)));

You can set a function to create the client request to the origin server for ultimate flexibility:

    proxy.origin((proxyContext) -> proxyContext.client().request(resolveOriginOptions(proxyContext)));

### Headers forwarding

End-to-end headers are forwarded by the proxy, hop-by-hop headers are ignored.

#### Request authority

By default, the proxy request authority (`Host` header for HTTP/1.1, `:authority` pseudo header for HTTP/2) is set by the HTTP client according to the origin server address.

| The origin server may need you to set the `x-forwarded-*` or `forwarded` headers on the proxied request. For example, it might use the values to compute a created HTTP resource location.

Nevertheless, you can override the request authority:

    proxy.addInterceptor(new ProxyInterceptor() {
      @Override
      public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
        ProxyRequest proxyRequest = context.request();
        proxyRequest.setAuthority(HostAndPort.create("example.com", 80));
        return ProxyInterceptor.super.handleProxyRequest(context);
      }
    });

When the request authority is overridden, the `x-forwarded-host` header is automatically set on the request to the origin server with the original authority value.

### WebSockets

The proxy supports WebSocket by default.

WebSocket handshake requests are forwarded to the origin server (including the `connection` header) and the handshake happens between the user agent and the origin server.

You can configure WebSocket support with `setSupportWebSocket`.

### Proxy caching

By default the proxy does not cache response and ignores most cache directives, you can enable caching by setting the cache options.

    HttpProxy proxy = HttpProxy.reverseProxy(new ProxyOptions().setCacheOptions(new CacheOptions()), proxyClient);

### Proxy interception

Interception is a powerful way to extend the proxy withg new features.

You can implement `handleProxyRequest` to perform any operation on the proxy request

    proxy.addInterceptor(new ProxyInterceptor() {
      @Override
      public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
        ProxyRequest proxyRequest = context.request();

        filter(proxyRequest.headers());

        // Continue the interception chain
        return context.sendRequest();
      }
    });

Likewise with the proxy response

    proxy.addInterceptor(new ProxyInterceptor() {
      @Override
      public Future<Void> handleProxyResponse(ProxyContext context) {
        ProxyResponse proxyResponse = context.response();

        filter(proxyResponse.headers());

        // Continue the interception chain
        return context.sendResponse();
      }
    });

#### Body filtering

You can filter body by simply replacing the original `Body` with a new one

    proxy.addInterceptor(new ProxyInterceptor() {
      @Override
      public Future<Void> handleProxyResponse(ProxyContext context) {
        ProxyResponse proxyResponse = context.response();

        // Create a filtered body
        Body filteredBody = filter(proxyResponse.getBody());

        // And then let the response use it
        proxyResponse.setBody(filteredBody);

        // Continue the interception chain
        return context.sendResponse();
      }
    });

#### Interception control

`sendRequest` and `sendResponse` continue the current interception chain and then send the result to the origin server or the user-agent.

You can change the control, e.g. you can send a response immediately to the user-agent without even requesting the origin server

    proxy.addInterceptor(new ProxyInterceptor() {
      @Override
      public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {

        ProxyRequest proxyRequest = context.request();

        // Release the underlying resources
        proxyRequest.release();

        // Create a response and populate it
        ProxyResponse proxyResponse = proxyRequest.response()
          .setStatusCode(200)
          .putHeader("content-type", "text/plain")
          .setBody(Body.body(Buffer.buffer("Hello World")));

        return Future.succeededFuture(proxyResponse);
      }
    });

#### Customizable interceptor

You can use `ProxyInterceptor.builder` that facilitates the implementation of an interceptor that modifies the request/response heads and bodies:

- request path

- query params

- request and response headers

- body transformation

Such interceptor is created and configured with a `ProxyInterceptorBuilder`.

##### Headers interception

You can apply the interceptor to change headers from the request and response with common operations:

    proxy.addInterceptor(
      ProxyInterceptor.builder().filteringResponseHeaders(shouldRemove).build());

Headers modifying methods can be invoked several times, operations are applied in the order of configuration.

Check out `ProxyInterceptorBuilder` for details about the available methods.

##### Query params interception

You can apply the interceptor to update or remove query parameters:

    proxy.addInterceptor(
      ProxyInterceptor.builder().settingQueryParam(key, value).build());

Query params modifying methods can be invoked several times, operations are applied in the order of configuration.

You can also refer to `ProxyInterceptorBuilder` for more information.

#### Body interceptor

You can use a `BodyTransformer` to create body transformations.

A set of predefined transformations facilitates the creation of a transformer.

    BodyTransformer transformer = BodyTransformers.transform(
      MediaType.APPLICATION_JSON,
      MediaType.APPLICATION_OCTET_STREAM,
      buffer -> {
        // Apply some transformation
        return buffer;
      }
    );

A body transformer is then turned into a proxy interceptor with the builder:

    proxy.addInterceptor(
      ProxyInterceptor
        .builder()
        .transformingResponseBody(transformer)
        .build()
    );

`BodyTransformers` provides transformation for common data types, like `JsonObject`:

    proxy.addInterceptor(
      ProxyInterceptor
        .builder()
        .transformingResponseBody(
          BodyTransformers.jsonObject(
            jsonObject -> removeSomeFields(jsonObject)
          )
        ).build());

Most transformations provided in `BodyTransformers` are synchronous and buffer bytes. The default maximum amount of bytes is 256K bytes, you can provide a different amount:

    proxy.addInterceptor(
      ProxyInterceptor
        .builder()
        .transformingResponseBody(
          BodyTransformers.jsonObject(
            // Maximum amount of buffered bytes
            128 * 1024,
            jsonObject -> removeSomeFields(jsonObject)
          )
        ).build());

Please check the `BodyTransformers` for other supported transformations.

| you can also implement `BodyTransformer` contract to best adapt it to your needs.

#### Interception and WebSocket upgrades

By default, interceptors are not invoked during WebSocket upgrades.

To make an interceptor available during the WebSocket handshake, use `addInterceptor`:

    ProxyInterceptor interceptor = ProxyInterceptor.builder()
      .addingPathPrefix("/api")
      .build();
    proxy.addInterceptor(interceptor, true);