Integration

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.0</version>
</dependency>
  • Gradle (in your build.gradle file):

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

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

As a transparent proxy, the request authority (Host header for HTTP/1.1, :authority pseudo header for HTTP/2) is preserved.

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 set on the request to the origin server with the original authority value.

changing the request authority can have undesirable side effects and can affect the proxied web server that might rely on the original request authority to handle cookies, URL redirects and such.

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);