第三方框架整合

Vert.x Http Proxy

Vert.x Http Proxy 是一个基于 Vert.x 构建的高性能反向代理组件,旨在提供可复用的反向代理逻辑,使开发者能专注于更高层面的业务编排。

本模块处于 技术预览 (Tech Preview) 阶段,这意味着 API 在后续版本间可能会发生变更。

使用 Vert.x Http Proxy

若要在项目中使用 Vert.x Http Proxy,请将以下依赖项添加到构建描述文件的 dependencies 配置节中:

  • Maven(在 pom.xml 中):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-http-proxy</artifactId>
  <version>5.2.1</version>
</dependency>
  • Gradle(在 build.gradle 文件中):

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

反向代理服务器

使用 Vert.x Http Proxy 实现反向代理需要满足以下两个要素:

  1. 代理服务器 (Proxy Server):接收客户端(User-Agent)的请求,并将其透明转发给 源站服务器 (Origin Server)。

  2. 源站服务器 (Origin Server):处理来自 代理服务器 的请求并作出相应响应。

你可以创建一个监听 8080 端口的 代理服务器 并配置反向代理核心逻辑:

HttpClient proxyClient = vertx.createHttpClient();

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

HttpServer proxyServer = vertx.createHttpServer();

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

所有来自客户端的请求都将被便捷地转发至 源站服务器。

源站服务器路由选择

你可以像上面一样创建一个将所有流量代理到单一后端服务器的代理。

此外,你还可以设置一个源站选择器(Origin Selector),根据请求上下文动态将流量路由到指定服务器:

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

如果需要极高的灵活性,你甚至可以指定一个自定义函数来为源站创建客户端请求:

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

请求头转发机制

代理服务会自动转发端到端请求头(End-to-end headers),而跳级传输头(Hop-by-hop headers,如 Connection、Keep-Alive 等)会被安全忽略。

请求 Authority(主机标识)

默认情况下,代理请求的 Authority 标识(HTTP/1.1 中的 Host 请求头,或 HTTP/2 中的 :authority 伪请求头)由 HTTP 客户端根据源站服务器地址自动设置。

源站服务器可能依赖你在被代理的请求上携带 x-forwarded-* 或 forwarded 头。 例如,源站可能需要使用这些值来计算新创建的 HTTP 资源的重定向或访问 Location。

即便如此,你依然可以手动覆盖请求的 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);
  }
});

当请求 Authority 被显式覆盖时,系统会自动在发送给源站服务器的请求中添加 x-forwarded-host 头,其值为客户端原始的 Authority。

转发头支持(Forwarded Headers)

作为反向代理,通常需要通过 HTTP 请求头将原始客户端的信息传递给源站。 该代理既支持事实上的行业标准 X-Forwarded-* 请求头,也支持 RFC 7239 标准的 Forwarded 头。

默认情况下,转发头处于禁用状态。 你可以通过配置 ForwardedHeadersOptions 来启用它们:

ProxyOptions options = new ProxyOptions()
  .setForwardedHeadersOptions(new ForwardedHeadersOptions()
    .setEnabled(true));

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

你可以精准控制需要附加哪些请求头:

ProxyOptions options = new ProxyOptions()
  .setForwardedHeadersOptions(new ForwardedHeadersOptions()
    .setEnabled(true)
    .setForwardFor(true)
    .setForwardProto(true)
    .setForwardHost(true)
    .setForwardPort(false));  // 不转发端口

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

或者,你可以使用 RFC 7239 标准化的 Forwarded 头来替代非标准的 X-Forwarded-* 请求头:

ProxyOptions options = new ProxyOptions()
  .setForwardedHeadersOptions(new ForwardedHeadersOptions()
    .setEnabled(true)
    .setUseRfc7239(true));  // 采用 RFC 7239 Forwarded 头

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

支持的请求头包括:

  • X-Forwarded-For:包含客户端真实 IP 地址。如果存在多层代理链,代理会追加客户端 IP 以保留完整链路。

  • X-Forwarded-Proto:包含原始请求协议方案(http 或 https)。

  • X-Forwarded-Host:包含原始请求的主机名(Host)。

  • X-Forwarded-Port:包含原始请求的端口号。

  • Forwarded:RFC 7239 标准头,将上述信息以结构化格式组合:for=<client-ip>;proto=<scheme>;host=<host>。

源站服务器可利用 X-Forwarded-* 与 Forwarded 头确定反向代理背后的真实客户端 IP、协议及主机名。这对于生成绝对 URL、执行安全鉴权及访问审计日志尤为重要。

WebSocket 支持

反向代理默认原生支持 WebSocket。

WebSocket 握手请求会透明转发至源站服务器(包括 Connection 请求头),实际握手发生在客户端与源站服务器之间。

你可以通过 setSupportWebSocket 配置是否开启 WebSocket 支持。

代理缓存

默认情况下,代理不会缓存响应,且会忽略大部分缓存指令。你可以通过配置缓存选项来开启响应缓存功能:

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

代理拦截器(Proxy Interception)

拦截器是为反向代理扩展定制化功能的强大机制。

你可以实现 handleProxyRequest 对代理请求执行任意拦截与修改:

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

    filter(proxyRequest.headers());

    // 继续执行后续拦截链
    return context.sendRequest();
  }
});

针对代理响应的处理也是完全对称的:

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

    filter(proxyResponse.headers());

    // 继续执行后续拦截链
    return context.sendResponse();
  }
});

请求体/响应体过滤

你可以通过将原始 Body 替换为新对象来过滤传输体:

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

    // 构建过滤处理后的响应体
    Body filteredBody = filter(proxyResponse.getBody());

    // 设置给当前响应
    proxyResponse.setBody(filteredBody);

    // 继续执行后续拦截链
    return context.sendResponse();
  }
});

拦截流转控制

sendRequest 和 sendResponse 会继续推进当前的拦截链,并将最终结果发送给源站服务器或客户端。

你也可以更改流转控制,例如在无需请求源站的情况下,直接向客户端返回短路响应:

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

    ProxyRequest proxyRequest = context.request();

    // 释放底层占用的资源
    proxyRequest.release();

    // 创建并填充即时响应
    ProxyResponse proxyResponse = proxyRequest.response()
      .setStatusCode(200)
      .putHeader("content-type", "text/plain")
      .setBody(Body.body(Buffer.buffer("Hello World")));

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

可定制拦截器构建器

你可以使用 ProxyInterceptor.builder 快速构建修改请求/响应头及主体的拦截器:

  • 请求路径(Path)

  • 查询参数(Query params)

  • 请求头与响应头

  • 请求体/响应体数据转换

此类拦截器可通过 ProxyInterceptorBuilder 链式创建与配置。

请求头拦截

你可以使用拦截器执行针对请求和响应头的常见修改操作:

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

修改请求头的方法可以多次调用,各项操作将严格按照配置顺序依次执行。

请参阅 ProxyInterceptorBuilder 了解所有可用方法的完整详情。

查询参数拦截

你可以应用拦截器来更新或移除 URL 查询参数:

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

修改查询参数的方法同样支持多次链式调用,规则按配置顺序依次生效。

更多细节请参考 ProxyInterceptorBuilder。

Body 转换器(Body Transformer)

你可以使用 BodyTransformer 对传输体进行数据格式转换。

内置的一组常用转换器能够大幅简化创建过程:

BodyTransformer transformer = BodyTransformers.transform(
  MediaType.APPLICATION_JSON,
  MediaType.APPLICATION_OCTET_STREAM,
  buffer -> {
    // 执行数据转换操作
    return buffer;
  }
);

然后通过构建器将转换器组合为代理拦截器:

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

BodyTransformers 提供了针对常见数据类型的开箱即用转换,例如 JsonObject:

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

BodyTransformers 中提供的大多数转换均是同步执行并会缓冲字节。默认最大缓冲大小为 256KB,你可以按需自定义缓冲上限:

proxy.addInterceptor(
  ProxyInterceptor
    .builder()
    .transformingResponseBody(
      BodyTransformers.jsonObject(
        // 自定义最大缓冲字节数
        128 * 1024,
        jsonObject -> removeSomeFields(jsonObject)
      )
    ).build());

其他受支持的转换类型请查阅 BodyTransformers。

你也可以实现自定义的 BodyTransformer 接口,以完美贴合业务场景需求。

拦截器与 WebSocket 协议升级

默认情况下,拦截器在 WebSocket 握手升级过程中不会被调用。

如果需要在 WebSocket 握手阶段同样执行某个拦截器,请使用带布尔参数的重载方法 addInterceptor:

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