HttpForwardHandler

ForwardHandler 简介

ForwardHandler 是 Tio-Boot 中用于请求转发的处理器,通常用于与第三方系统的集成。当请求在经过 Tio-Boot 的其他处理器(如 httpRequestInterceptorhttpRequestRouterhttpControllerRouter)后仍未能处理并生成响应时,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. 定义转发处理器

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

package com.litongjava.maxkb.httphandler;

import com.litongjava.jfinal.aop.Aop;
import com.litongjava.maxkb.service.AppForwardRequestService;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.boot.http.forward.TioHttpProxy;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.handler.IHttpRequestHandler;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyRequestForwardHandler implements IHttpRequestHandler {

  // 定义目标服务器 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, true, forwardRequestService);

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

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

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

package com.litongjava.maxkb.config;

import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.maxkb.httphandler.MyRequestForwardHandler;
import com.litongjava.tio.boot.server.TioBootServer;

@AConfiguration
public class TioServerConfig {

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

代码说明

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

TioHttpProxy.reverseProxy 方法

该方法的签名如下:

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

自定义的请求处理服务

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

package com.litongjava.maxkb.service;

import java.util.Map;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Record;
import com.litongjava.tio.boot.http.forward.RequestProxyCallback;
import com.litongjava.tio.http.common.HeaderName;
import com.litongjava.tio.http.common.HeaderValue;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.RequestLine;
import com.litongjava.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");
          }
        }
      }
    }

    // 将请求信息保存到数据库
    Record record = Record.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", record, 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) {
    Record record = Record.by("id", id).set("elapsed", elapsed).set("response_status", statusCode);

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

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

结论

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