You need to enable JavaScript to run this app. 如何将HttpServletRequest与HttpServletResponse对象转发至其他服务
优惠活动
大模型
产品
解决方案
定价
更多

如何将HttpServletRequest与HttpServletResponse对象转发至其他服务

实现方案

分为业务服务侧过滤器改造、通用日志存储服务开发两部分,核心注意点是HttpServletRequest/HttpServletResponse为Servlet容器绑定的本地对象,无法直接跨服务传输,需先提取可序列化原始字段后再发送。

1. 业务服务侧Servlet过滤器实现

1.1 核心改造逻辑

  • 用可重复读取的Wrapper包装请求响应对象,避免读取请求/响应体后业务逻辑无法获取数据,Spring Web环境可直接使用自带的ContentCachingRequestWrapper、ContentCachingResponseWrapper,无Spring依赖可自行实现缓存请求/响应体的Wrapper类
  • 过滤器优先级设为最高,确保所有请求都能被拦截
  • 业务逻辑执行完成后再提取响应相关数据,异步发送到通用服务,不要阻塞业务主线程
  • 增加降级逻辑:通用服务不可用时直接丢弃日志/本地暂存,不影响正常业务请求

1.2 参考代码

// 日志传输DTO,可序列化
@Data
@Builder
public class AccessLogRawDTO implements Serializable {
    // 业务服务标识,用于通用服务区分日志来源
    private String serviceCode;
    // 请求原始数据
    private Map<String, String> requestHeaders;
    private String requestUri;
    private String requestMethod;
    private byte[] requestBody;
    private String remoteAddr;
    // 响应原始数据
    private Integer responseStatus;
    private Map<String, String> responseHeaders;
    private byte[] responseBody;
    // 请求时间戳
    private Long timestamp;
}
// 过滤器实现
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class AccessLogFilter implements Filter {
    private final AsyncLogSender asyncLogSender;
    // 最大存储的请求/响应体长度,超过则截断
    private static final int MAX_BODY_LENGTH = 1024 * 1024;

    public AccessLogFilter(AsyncLogSender asyncLogSender) {
        this.asyncLogSender = asyncLogSender;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper reqWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper respWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);
        try {
            // 正常执行业务逻辑
            chain.doFilter(reqWrapper, respWrapper);
        } finally {
            // 组装原始日志DTO
            AccessLogRawDTO.AccessLogRawDTOBuilder dtoBuilder = AccessLogRawDTO.builder()
                    .serviceCode("your-service-code")
                    .requestHeaders(Collections.list(reqWrapper.getHeaderNames())
                            .stream()
                            .collect(Collectors.toMap(k -> k, reqWrapper::getHeader)))
                    .requestUri(reqWrapper.getRequestURI())
                    .requestMethod(reqWrapper.getMethod())
                    .remoteAddr(reqWrapper.getRemoteAddr())
                    .responseStatus(respWrapper.getStatusCode())
                    .responseHeaders(respWrapper.getHeaderNames()
                            .stream()
                            .collect(Collectors.toMap(k -> k, respWrapper::getHeader)))
                    .timestamp(System.currentTimeMillis());
            // 截断过长的请求/响应体
            byte[] reqBody = reqWrapper.getContentAsByteArray();
            dtoBuilder.requestBody(reqBody.length > MAX_BODY_LENGTH ? Arrays.copyOf(reqBody, MAX_BODY_LENGTH) : reqBody);
            byte[] respBody = respWrapper.getContentAsByteArray();
            dtoBuilder.responseBody(respBody.length > MAX_BODY_LENGTH ? Arrays.copyOf(respBody, MAX_BODY_LENGTH) : respBody);
            // 异步发送日志到通用服务
            asyncLogSender.sendAsync(dtoBuilder.build());
            // 把缓存的响应内容写回给客户端,必须执行
            respWrapper.copyBodyToResponse();
        }
    }
}

2. 通用日志存储服务实现

2.1 API设计

对外提供HTTP POST接口,路径可定义为/api/v1/access-log/collect,接收参数为上述AccessLogRawDTO,接口返回成功/失败标识即可。
如果日志量级较大,可替换为Kafka等消息队列接收日志,实现削峰填谷,避免突发流量打垮存储服务。

2.2 字段提取与存储逻辑

所有字段清洗、提取逻辑全部放在通用服务内完成,业务侧无需感知:

  • 提取请求头中的traceId、用户标识、设备信息等自定义字段
  • 对URI做标准化格式化,提取路径参数、业务标识
  • 对状态码做分类打标,比如2xx为成功、4xx为客户端错误、5xx为服务端错误
  • 敏感字段脱敏:对请求/响应体中的密码、手机号、身份证号等敏感信息做替换处理
  • 处理完成后将结构化数据存入MongoDB,可按服务标识、日期做集合拆分,提升查询性能

2.3 兼容扩展

预留自定义字段扩展位,不同业务服务如果有特殊日志字段需要存储,可放在DTO的扩展字段中,通用服务无需修改代码即可兼容存储。

内容的提问来源于stack exchange,提问作者Aditya Joshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.12 04:15:00