如何在Java中获取HTTP请求与响应的原始报文?
使用java.net.http捕获HTTP请求与响应原始报文的方案
首先明确:没有完全无需手动编写代码的方式,但有几种简洁的实现方案,不用从头造轮子,下面分别说明:
1. 用JDK内置日志直接打印(几乎零代码修改)
java.net.http本身支持通过日志配置输出原始请求和响应,适合调试场景,不需要改业务代码:
- 如果用Java Util Logging,在
logging.properties里加:
# 打印请求/响应的头部和内容,可选值还有trace、errors等 jdk.httpclient.HttpClient.log=headers,content
- 如果用Logback等框架,在配置文件里加:
<logger name="jdk.httpclient.HttpClient" level="DEBUG"/>
这种方式的输出是日志形式,要是想把原始报文存成字符串,得自己处理日志内容。
2. 自定义BodyHandler捕获响应原始报文
如果要在代码里直接拿到响应的原始字符串,可以结合TeeInputStream自定义BodyHandler,同时读取响应内容并保存原始报文:
HttpRequest httpsRequest = getHttpsRequest(url, parameters, method, headers); HttpClient client = HttpClient.newHttpClient(); // 自定义BodyHandler捕获完整响应报文 HttpResponse<String> httpResponse = client.send(httpsRequest, responseInfo -> { // 先拼接响应状态行和头部 StringBuilder rawResponse = new StringBuilder(); rawResponse.append(responseInfo.version()) .append(" ") .append(responseInfo.statusCode()) .append(" ") .append(responseInfo.reasonPhrase()) .append("\r\n"); responseInfo.headers().map().forEach((key, values) -> { values.forEach(val -> rawResponse.append(key).append(": ").append(val).append("\r\n")); }); rawResponse.append("\r\n"); return HttpResponse.BodySubscribers.mapping( HttpResponse.BodySubscribers.ofInputStream(), inputStream -> { try { // 用TeeInputStream同时读取内容并写入字节流,保留原始报文 ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (TeeInputStream teeStream = new TeeInputStream(inputStream, baos)) { String body = new String(teeStream.readAllBytes(), UTF_8); rawResponse.append(body); return body; } finally { // 这里可以把rawResponse.toString()存到变量或者打印 System.out.println("原始响应报文:\n" + rawResponse); } } catch (IOException e) { throw new UncheckedIOException(e); } } ); });
3. 用HttpClient Filter同时捕获请求和响应
要是需要同时抓请求和响应的原始报文,可以给HttpClient加Filter:
HttpClient client = HttpClient.newBuilder() .filter((request, next) -> { // 拼接请求的状态行和头部 StringBuilder rawRequest = new StringBuilder(); rawRequest.append(request.method()) .append(" ") .append(request.uri()) .append(" ") .append(request.version()) .append("\r\n"); request.headers().map().forEach((key, values) -> { values.forEach(val -> rawRequest.append(key).append(": ").append(val).append("\r\n")); }); rawRequest.append("\r\n"); // 如果是带请求体的POST/PUT等,需要先缓存请求体(因为流只能读一次) // 这里示例简化处理GET请求,POST场景需要额外处理请求体 System.out.println("原始请求报文:\n" + rawRequest); return next.exchange(request).thenApply(response -> { // 这里可以结合上面的BodyHandler方式处理响应报文 return response; }); }) .build(); HttpRequest httpsRequest = getHttpsRequest(url, parameters, method, headers); HttpResponse<String> httpResponse = client.send(httpsRequest, HttpResponse.BodyHandlers.ofString(UTF_8));
注意:处理带请求体的请求时,要先把请求体缓存下来,再重新构建请求,因为原始请求体流只能读取一次。
方案选择
- 调试用优先选日志配置,几乎不用改代码;
- 要在代码里拿响应报文用自定义BodyHandler+TeeInputStream;
- 要同时抓请求和响应选HttpClient Filter,注意请求体的流复用问题。
内容的提问来源于stack exchange,提问作者Hadas
相关产品推荐
相关产品推荐

