monkeyWie / proxyee

HTTP proxy server,support HTTPS&websocket.MITM impl,intercept and tamper HTTPS traffic.
MIT License
1.51k stars 566 forks source link

响应头带有range的大文件的提前终止BUG #177

Closed liuquanal closed 2 years ago

liuquanal commented 2 years ago

如果有一个很大的xml文件,返回的header里面带有range,那么proxyee的FullResponseIntercept里的两个内置处理器,就会造成很大的BUG,导致客户端拉取数据的时候,提前终止响应流。

最后我们用于生产的时候,只能移除这两个内置的处理器,才能正常工作。但是移除之后,可能无法解析gzip和post的body数据。 所以看作者能否更完美的支持一下带range的大数据响应流。

monkeyWie commented 2 years ago

xml文件有多大,超过了8M吗

liuquanal commented 2 years ago

xml文件有多大,超过了8M吗

这是我遇到的一个rss文件的下载,发现总是报错: http://xinpi.stcn.com/rss/stcn_news_cf_a.xml

文件下载到本地,5M+,而且我设置了超时时间300秒,请求和响应包大小为32M 貌似总是会提示错误

monkeyWie commented 2 years ago

你是怎么请求的,有原始的请求报文吗,我用浏览器访问没有看到range头

liuquanal commented 2 years ago

你是怎么请求的,有原始的请求报文吗,我用浏览器访问没有看到range头

就是非常普通的apache的httpclient的get请求,把代理部署到本地、公网服务器,好像结果都不一样,好像和网络有关系。 但是去掉代理之后,是固定可以取到数据。我去掉FullResponse的内置处理器之后,本地正常,公网A服务器正常,但是公网B服务器不正常。

org.apache.http.TruncatedChunkException: Truncated chunk ( expected size: 2736; actual size: 159)

不改原项目的话,也是报size的问题。


import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

/**
 *
 * @author lq
 * @since 2021/7/29 18:04
 */
public class Test2 {
    public static void main(String[] args) {
        String rs = getContent("http://xinpi.stcn.com/rss/stcn_news_cf_a.xml",
                new HttpHost("xxx.xxx.xxx.xxx", 50001), "utf-8", 30000);
        System.out.println(rs);
    }

    public static String getContent(String url, HttpHost proxy, String charset, int timeout) {
        String result = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(url);
            RequestConfig.Builder configBuilder = RequestConfig.custom();
            if (timeout > 0) {
                configBuilder.setConnectionRequestTimeout(timeout)
                        .setSocketTimeout(timeout)
                        .setConnectTimeout(timeout);
            }
            if (proxy != null) {
                configBuilder.setProxy(proxy);
            }
            httpGet.setConfig(configBuilder.build());
            HttpResponse response = httpClient.execute(httpGet);
            if (response != null) {
                result = EntityUtils.toString(response.getEntity(), charset);
            }
        } catch (Exception e) {
            throw new RuntimeException("请求发生异常!原因:{}", e);
        }
        return result;
    }
}
liuquanal commented 2 years ago

图片

怀疑是这个Accept-Ranges: bytes响应头,造成了netty重复请求什么的

liuquanal commented 2 years ago

最新进展,在以下代码片段处,数据还是完整的,所以怀疑数据是在输出到客户端时,被提前中断了

if (fullHttpResponse.headers().contains(HttpHeaderNames.CONTENT_LENGTH)) {
    httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, fullHttpResponse.content().readableBytes());
    System.out.println("content len:" + clientChannel.id()
            + ",len:" + fullHttpResponse.content().readableBytes());
    String content = fullHttpResponse.content().toString(StandardCharsets.UTF_8);
    System.out.println("content:" + clientChannel.id() + "," + content);
}
monkeyWie commented 2 years ago

我先测试下看看吧

monkeyWie commented 2 years ago

@liuquanal 我刚刚用你的代码试了下,可以正常返回啊 image

liuquanal commented 2 years ago

我本机测试是正常的,但是把代码打包到公网的某台云机器上,就会报下面的错误:

org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 6324161; received: 131071
monkeyWie commented 2 years ago

你能用tcpdump把报文抓下来看看吗

liuquanal commented 2 years ago

dump.txt 固定重现,但是去掉代理的话,就很正常

content-length: 6364230
Exception in thread "main" java.lang.RuntimeException: 请求发生异常!原因:{}
    at com.tdx.webmagic.demo.Test2.getContent(Test2.java:48)
    at com.tdx.webmagic.demo.Test2.main(Test2.java:22)
Caused by: org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 6364230; received: 1689220
    at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:178)
    at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:135)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.Reader.read(Reader.java:140)
    at org.apache.http.util.EntityUtils.toString(EntityUtils.java:227)
    at org.apache.http.util.EntityUtils.toString(EntityUtils.java:270)
    at org.apache.http.util.EntityUtils.toString(EntityUtils.java:290)
    at com.tdx.webmagic.demo.Test2.getContent(Test2.java:45)
monkeyWie commented 2 years ago

你是不是打印了响应体 image

liuquanal commented 2 years ago

好像和这个没关系,但是我打印响应体的时候,流居然提前关闭了,客户端已经抛出异常了,而打印消息还在不停的打印。

monkeyWie commented 2 years ago

@liuquanal 把这行注释掉再试试,应该就是这里的问题

monkeyWie commented 2 years ago

@liuquanal 我复现了下,这个是netty的bug,升级netty版本之后就好了,你试下proxyee 1.5.4 版本

liuquanal commented 2 years ago

貌似我这里测试没有生效,netty是4.1.66,4.1.67也试过,现在定位到是FullResponseIntercept里面的HttpContentDecompressor处理器问题,屏蔽这一个处理器,就正常。估计是chunked、gzip、content-length什么的bug

monkeyWie commented 2 years ago

我之前在linux上测试了,也是稳定复现你这个问题,但是升级之后就没问题了,你那还是不行吗

liuquanal commented 2 years ago

还是不行,大概发现问题了,是因为那个content-length计算不正确。浏览器里面打开那个url,看到是chunked+gzip的格式,

if (fullHttpResponse.headers().contains(HttpHeaderNames.CONTENT_LENGTH)) {
    httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, fullHttpResponse.content().readableBytes());
}

估计是HttpContentDecompressor这个处理器,计算出的conteng-length是解压之后的大小,客户端接收,是按gzip后的大小接收的。我强制把conteng-length设为客户端接收到的大小,就不报错。

// 1572852 是客户端报错显示接收到的字节数
httpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, 1572852);
monkeyWie commented 2 years ago

过了代理之后其实相应已经不是gzip和chunked了。 image

liuquanal commented 2 years ago

图片 我的响应头多了一个这个,感觉是不是被这个给强制断开了?firefox显示响应被截断 图片

monkeyWie commented 2 years ago

你用chrome试试呢