Skip to content

使用 HttpForwardHandler 转发所有请求 ​

ForwardHandler 简介 ​

ForwardHandler 是 Tio-Boot 中用于请求转发的处理器,通常用于与第三方系统的集成。当请求在经过 Tio-Boot 的其他处理器(如 httpRequestInterceptor、httpRequestRouter、httpControllerRouter)后仍未能处理并生成响应时,ForwardHandler 将接管该请求并转发到指定的目标系统(如其他 API 服务或微服务)。

Tio-Boot 中请求处理顺序 ​

在 Tio-Boot 中,HTTP 请求按照如下顺序依次进行处理:

  1. httpRequestInterceptor:请求拦截器,用于执行请求的预处理工作,比如用户鉴权、权限验证和日志记录等操作。
  2. httpRequestRouter:请求路由器,根据请求的 URL 路径进行路由,决定将请求转发到相应的业务逻辑进行处理。
  3. httpGroovyRoutes:支持 Groovy 路由的处理器,可执行动态的 Groovy 脚本来响应请求。
  4. httpControllerRouter:控制器路由器,将请求交由具体的 Controller 进行业务处理。
  5. forwardHandler:请求转发处理器,当上面的步骤均未处理请求时,forwardHandler 会将请求转发到外部的第三方系统处理。
  6. staticResourceHandler:静态资源处理器,负责处理诸如 CSS、JavaScript、图片等静态资源请求。
  7. notFoundHandler:处理 404 未找到的情况,若请求未能找到对应的处理路径,将返回 404 响应。

ForwardHandler 的用途 ​

ForwardHandler 主要用于以下场景:

  • 与外部 API 集成:当请求无法在 Tio-Boot 内部处理时,可将其转发到其他系统进行处理。
  • 扩展已有系统:通过转发处理扩展现有系统的处理能力,避免对现有系统进行大幅度的改动。

当请求经过 Tio-Boot 的常规处理器后依然未返回 HttpResponse,则请求会被 ForwardHandler 接管并转发给外部系统。例如,将请求转发到微服务网关或其他 API 服务器以继续处理业务逻辑。

示例:使用 ForwardHandler 集成第三方系统 ​

下列示例展示了如何通过 ForwardHandler 将未处理的请求转发至第三方系统。在此示例中,未被 Tio-Boot 处理的请求将被转发至 http://192.168.3.9:8080 进行处理。

代码示例 ​

1. 定义转发处理器 ​

该类实现了 HttpRequestHandler 接口,负责将请求转发至指定的第三方系统。TioHttpProxy.reverseProxy 方法用于将请求转发至目标服务器并将返回的响应写入当前请求的 HttpResponse 中。

java
package nexus.io.maxkb.httphandler;

import nexus.io.jfinal.aop.Aop;
import nexus.io.maxkb.service.AppForwardRequestService;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.boot.http.forward.TioHttpProxy;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.handler.HttpRequestHandler;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyRequestForwardHandler implements HttpRequestHandler {

  // 定义目标服务器 URL
  private String remoteServerUrl = "http://192.168.3.9:8080";

  @Override
  public HttpResponse handle(HttpRequest httpRequest) throws Exception {
    // 获取当前请求的 HttpResponse 对象
    HttpResponse httpResponse = TioRequestContext.getResponse();

    // 获取转发请求服务,处理自定义的逻辑,如请求头的处理或参数转换等
    AppForwardRequestService forwardRequestService = Aop.get(AppForwardRequestService.class);

    // 记录转发的请求信息,方便调试
    log.info("forward:{},{}", httpRequest.getRequestLine().toString(), httpRequest.logstr());

    // 使用 TioHttpProxy 将请求转发到远程服务器
    TioHttpProxy.reverseProxy(remoteServerUrl, httpRequest, httpResponse, forwardRequestService);

    // 返回最终的 HttpResponse
    return httpResponse;
  }
}

2. 在服务器配置中设置 ForwardHandler ​

通过在 Tio-Boot 的配置类中注册自定义的 ForwardHandler,可以将未处理的请求转发到指定的服务器。

java
package nexus.io.maxkb.config;

import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.maxkb.httphandler.MyRequestForwardHandler;
import nexus.io.tio.boot.server.TioBootServer;

@AConfiguration
public class TioServerConfig {

  // 初始化配置
  @Initialization
  public void config() {
    // 设置自定义的转发处理器
    TioBootServer.me().setForwardHandler(new MyRequestForwardHandler());
  }
}

代码说明 ​

  • MyRequestForwardHandler:该类实现了 HttpRequestHandler 接口,负责接收未被 Tio-Boot 内置处理器处理的请求,并通过 TioHttpProxy.reverseProxy 方法将其转发到目标服务器。
  • AppForwardRequestService:这是一个自定义的请求服务类,可以根据需求对转发请求进行处理,比如调整请求头、记录请求信息或处理响应体。此类中的 saveRequest 和 saveResponse 方法用于将请求和响应数据保存到数据库,以便后续分析或排查问题。
  • 日志记录:通过 log.info 记录每个请求的转发情况,便于在开发和生产环境中进行调试。

TioHttpProxy.reverseProxy 方法 ​

该方法的签名如下:

java
public static void reverseProxy(String targetUrl, HttpRequest httpRequest, HttpResponse httpResponse, RequestProxyCallback callback)
  • targetUrl:目标服务器的 URL。
  • httpRequest:当前的 HttpRequest 对象,包含了请求的所有信息。
  • httpResponse:当前的 HttpResponse 对象,用于返回响应给客户端。
  • callback:用于在请求和响应处理时执行自定义操作的回调接口。

转发的特点:

  • 支持 GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS,请求体按收到的原始字节转发,JSON、表单、文件上传和二进制数据都原样到达目标服务器。
  • 自动设置 X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host,去掉 Connection、Host 等逐跳请求头。
  • 目标服务器返回 SSE(text/event-stream)或没有长度的分块响应时,收到一段就发给客户端一段,AI 对话等流式接口可以直接转发;客户端断开时取消对目标服务器的请求。
  • 目标服务器无法连接时返回 502。

需要自己决定失败后的处理(例如换一台服务器重试)时,使用 TioHttpProxy.forward(url, request, response),url 是包含路径和查询参数的完整地址,连接失败时抛出 IOException。

ForwardHandler 返回 null 或状态码 404 的响应时,请求继续交给静态资源处理,仍然没有结果时返回 404。

按服务名转发、路由与过滤器、WebSocket 转发见 网关 和 HTTP 与 WebSocket 反向代理。

自定义的请求处理服务 ​

AppForwardRequestService 实现了 RequestProxyCallback 接口,提供自定义的请求和响应保存逻辑。

java
package nexus.io.maxkb.service;

import java.util.Map;
import nexus.io.db.activerecord.Db;
import nexus.io.db.activerecord.Row;
import nexus.io.tio.boot.http.forward.RequestProxyCallback;
import nexus.io.tio.http.common.HeaderName;
import nexus.io.tio.http.common.HeaderValue;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.RequestLine;
import nexus.io.tio.utils.hutool.ZipUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AppForwardRequestService implements RequestProxyCallback {

  // 保存请求信息
  public void saveRequest(long id, String ip, HttpRequest httpRequest) {
    StringBuffer stringBuffer = new StringBuffer();
    RequestLine requestLine = httpRequest.getRequestLine();
    stringBuffer.append(requestLine.toString()).append("\n");

    Map<String, String> headers = httpRequest.getHeaders();
    String contentType = httpRequest.getContentType();

    // 处理不同类型的请求体
    if (contentType != null) {
      if (contentType.startsWith("application/json")) {
        stringBuffer.append(httpRequest.getBodyString());
      } else if (contentType.startsWith("application/x-www-form-urlencoded")) {
        Map<String, Object[]> params = httpRequest.getParams();
        for (Map.Entry<String, Object[]> e : params.entrySet()) {
          stringBuffer.append(e.getKey() + ": " + e.getValue()[0]).append("\n");
        }
      } else if (contentType.startsWith("application/from-data")) {
        Map<String, Object[]> params = httpRequest.getParams();
        for (Map.Entry<String, Object[]> e : params.entrySet()) {
          Object value = e.getValue()[0];
          if (value instanceof String) {
            stringBuffer.append(e.getKey()).append(":").append(e.getValue()[0]).append("\n");
          } else {
            stringBuffer.append(e.getKey()).append(":").append("binary \n");
          }
        }
      }
    }

    // 将请求信息保存到数据库
    Row row = Row.by("id", id).set("ip", ip).set("method", requestLine.getMethod().toString())
        .set("uri", requestLine.getPath()).set("request_header", headers)
        .set("request_body", stringBuffer.toString());

    boolean saveResult = Db.save("sys_http

_forward_statistics", row, new String[] { "request_header" });
    if (!saveResult) {
      log.error("Failed to save request information to database: {}", id);
    }
  }

  // 保存响应信息
  @Override
  public void saveResponse(long id, long elapsed, int statusCode, Map<HeaderName, HeaderValue> headers,
      HeaderValue contentEncoding, byte[] body) {
    Row row = Row.by("id", id).set("elapsed", elapsed).set("response_status", statusCode);

    if (body != null && body.length > 0) {
      if (HeaderValue.Content_Encoding.gzip.equals(contentEncoding)) {
        row.set("response_body", new String(ZipUtil.unGzip(body)));
      } else {
        row.set("response_body", new String(body));
      }
    }

    boolean updateResult = Db.update("sys_http_forward_statistics", row);
    if (!updateResult) {
      log.error("Failed to update response information in database: {}", id);
    }
  }
}

结论 ​

通过以上的代码和示例,展示了如何在 Tio-Boot 项目中使用 ForwardHandler 来将请求转发至第三方系统,并对请求和响应数据进行自定义处理。该方案适用于与外部系统集成以及扩展现有项目的处理能力。

MIT Licensed