如何借助LittleProxy截断特定URI的HTTP响应体后半部分?
问题描述
现有一段LittleProxy示例代码,可在拦截并回发响应时替换HTTP响应头:
pom.xml依赖
<dependency> <groupId>org.littleshoot</groupId> <artifactId>littleproxy</artifactId> <version>1.1.2</version> </dependency>
主类代码
import org.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.HttpProxyServerBootstrap; import org.littleshoot.proxy.extras.SelfSignedMitmManager; import org.littleshoot.proxy.impl.DefaultHttpProxyServer; public class LittleProxyExample { public static void main(String[] args) { // 配置并启动代理服务器 HttpProxyServer proxyServer = org.littleshoot.proxy.impl.DefaultHttpProxyServer.bootstrap() .withPort(8081) // 指定代理工作端口 .withManInTheMiddle(new SelfSignedMitmManager()) // 添加MITM处理器 .withFiltersSource(new RequestInterceptor()) .start(); System.out.println("Proxy server started on port 8081"); } }
拦截器代码
package com.example.proxy; import io.netty.buffer.CompositeByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; import org.littleshoot.proxy.HttpFiltersAdapter; import org.littleshoot.proxy.HttpFiltersSourceAdapter; import java.nio.charset.StandardCharsets; public class RequestInterceptor extends HttpFiltersSourceAdapter { @Override public HttpFiltersAdapter filterRequest(HttpRequest originalRequest) { return new HttpFiltersAdapter(originalRequest) { @Override public HttpResponse clientToProxyRequest(HttpObject httpObject) { if (httpObject instanceof HttpRequest) { HttpRequest request = (HttpRequest) httpObject; // 替换请求头 request.headers().set("User-Agent", "LittleProxy/Example"); // 打印请求URI System.out.println("Modified Request: " + request.uri()); } return null; // 继续处理请求 } @Override public HttpObject serverToProxyResponse(HttpObject httpObject) { // 在此处修改响应头 HttpResponse response = (HttpResponse) httpObject; response.headers().set("Test", "Test"); return super.serverToProxyResponse(response); } }; } @Override public int getMaximumRequestBufferSizeInBytes() { return 10 * 1024 * 1024; } @Override public int getMaximumResponseBufferSizeInBytes() { return 10 * 1024 * 1024; } }
现在需要实现:针对特定URI截断响应体的后半部分,以此测试客户端收到损坏响应时的表现,但HttpResponse没有直接操作响应体的API,该如何实现?
解决方案
要实现特定URI的响应体截断,需要利用Netty的ByteBuf操作,同时在serverToProxyResponse方法中区分完整响应和分块响应,还要记录当前请求的URI来判断是否需要执行截断。
修改后的拦截器代码
package com.example.proxy; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.*; import org.littleshoot.proxy.HttpFiltersAdapter; import org.littleshoot.proxy.HttpFiltersSourceAdapter; import java.nio.charset.StandardCharsets; public class RequestInterceptor extends HttpFiltersSourceAdapter { // 定义需要截断的目标URI(可根据需求调整) private static final String TARGET_URI = "/test/corrupt-response"; @Override public HttpFiltersAdapter filterRequest(HttpRequest originalRequest) { // 保存当前请求的URI,用于后续响应判断 String requestUri = originalRequest.uri(); return new HttpFiltersAdapter(originalRequest) { @Override public HttpResponse clientToProxyRequest(HttpObject httpObject) { if (httpObject instanceof HttpRequest) { HttpRequest request = (HttpRequest) httpObject; request.headers().set("User-Agent", "LittleProxy/Example"); System.out.println("Modified Request: " + request.uri()); } return null; } @Override public HttpObject serverToProxyResponse(HttpObject httpObject) { // 仅对目标URI执行截断操作 if (!requestUri.contains(TARGET_URI)) { return super.serverToProxyResponse(httpObject); } // 处理完整响应(非分块) if (httpObject instanceof FullHttpResponse) { FullHttpResponse fullResponse = (FullHttpResponse) httpObject; ByteBuf content = fullResponse.content(); // 截断响应体:保留前半部分(例如取长度的1/2) int originalLength = content.readableBytes(); if (originalLength > 0) { int truncatedLength = originalLength / 2; // 创建新的ByteBuf,仅保留前truncatedLength字节 ByteBuf truncatedContent = Unpooled.copiedBuffer(content.readBytes(truncatedLength)); // 替换原响应体 fullResponse.content().clear(); fullResponse.content().writeBytes(truncatedContent); // 必须更新Content-Length头,否则客户端会等待剩余数据 fullResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, truncatedContent.readableBytes()); truncatedContent.release(); } return fullResponse; } // 处理分块响应(Chunked) if (httpObject instanceof HttpContent) { HttpContent content = (HttpContent) httpObject; ByteBuf contentBuf = content.content(); // 截断当前分块的内容(同样取前半部分) int originalLength = contentBuf.readableBytes(); if (originalLength > 0) { int truncatedLength = originalLength / 2; ByteBuf truncatedContent = Unpooled.copiedBuffer(contentBuf.readBytes(truncatedLength)); content.content().clear(); content.content().writeBytes(truncatedContent); truncatedContent.release(); } // 如果是最后一个分块,直接返回截断后的内容,不添加结束标记(故意损坏响应结构) if (content instanceof LastHttpContent) { return content; // 不返回LastHttpContent的结束标记,让响应结构不完整 } return content; } return super.serverToProxyResponse(httpObject); } }; } @Override public int getMaximumRequestBufferSizeInBytes() { return 10 * 1024 * 1024; } @Override public int getMaximumResponseBufferSizeInBytes() { return 10 * 1024 * 1024; } }
关键说明
- URI匹配:通过保存请求时的URI,在响应阶段判断是否需要执行截断操作,确保仅针对目标URI生效。
- 完整响应处理:对于
FullHttpResponse,直接操作其ByteBuf内容,截断后更新Content-Length头(如果不更新,客户端会一直等待剩余数据,而非立即处理损坏响应)。 - 分块响应处理:对于分块传输的响应,截断每个分块的内容,并且在最后一个分块时不返回标准的结束标记,故意让响应结构不完整,触发客户端异常。
- ByteBuf操作:使用Netty的
Unpooled.copiedBuffer创建截断后的内容,操作完成后记得释放ByteBuf避免内存泄漏。
内容的提问来源于stack exchange,提问作者Eljah
相关产品推荐
相关产品推荐

