You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助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;
    }
}

关键说明

  1. URI匹配:通过保存请求时的URI,在响应阶段判断是否需要执行截断操作,确保仅针对目标URI生效。
  2. 完整响应处理:对于FullHttpResponse,直接操作其ByteBuf内容,截断后更新Content-Length头(如果不更新,客户端会一直等待剩余数据,而非立即处理损坏响应)。
  3. 分块响应处理:对于分块传输的响应,截断每个分块的内容,并且在最后一个分块时不返回标准的结束标记,故意让响应结构不完整,触发客户端异常。
  4. ByteBuf操作:使用Netty的Unpooled.copiedBuffer创建截断后的内容,操作完成后记得释放ByteBuf避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:16:14