【问题标题】:URLConnection does not handle content length via proxy correctlyURLConnection 无法通过代理正确处理内容长度
【发布时间】:2012-03-07 18:47:55
【问题描述】:

我遇到了以下问题:当通过代理使用URLConnection 时,内容长度始终设置为-1

首先我检查了代理确实返回了Content-Lengthlynxwget 也通过代理工作;没有其他方法可以从本地网络访问互联网):

$ lynx -source -head ftp://ftp.wipo.int/pub/published_pct_sequences/publication/2003/1218/WO03_104476/WO2003-104476-001.zip
HTTP/1.1 200 OK
Last-Modified: Mon, 09 Jul 2007 17:02:37 GMT
Content-Type: application/x-zip-compressed
Content-Length: 30745
Connection: close
Date: Thu, 02 Feb 2012 17:18:52 GMT

$ wget -S -X HEAD ftp://ftp.wipo.int/pub/published_pct_sequences/publication/2003/1218/WO03_104476/WO2003-104476-001.zip
--2012-04-03 19:36:54--  ftp://ftp.wipo.int/pub/published_pct_sequences/publication/2003/1218/WO03_104476/WO2003-104476-001.zip
Resolving proxy... 10.10.0.12
Connecting to proxy|10.10.0.12|:8080... connected.
Proxy request sent, awaiting response...
  HTTP/1.1 200 OK
  Last-Modified: Mon, 09 Jul 2007 17:02:37 GMT
  Content-Type: application/x-zip-compressed
  Content-Length: 30745
  Connection: close
  Age: 0
  Date: Tue, 03 Apr 2012 17:36:54 GMT
Length: 30745 (30K) [application/x-zip-compressed]
Saving to: `WO2003-104476-001.zip'

我在 Java 中写道:

URL url = new URL("ftp://ftp.wipo.int/pub/published_pct_sequences/publication/2003/1218/WO03_104476/WO2003-104476-001.zip");
int length = url.openConnection().getContentLength();
logger.debug("Got length: " + length);

我得到-1。我开始调试FtpURLConnection,结果发现必要的信息在底层HttpURLConnection.responses 字段中,但它从未从那里正确填充:

(标题中有Content-Length: 30745)。当您开始读取流时,甚至在读取流之后,内容长度都不会更新。代码:

URL url = new URL("ftp://ftp.wipo.int/pub/published_pct_sequences/publication/2003/1218/WO03_104476/WO2003-104476-001.zip");
URLConnection connection = url.openConnection();

logger.debug("Got length (1): " + connection.getContentLength());

InputStream input = connection.getInputStream();

byte[] buffer = new byte[4096];
int count = 0, len;
while ((len = input.read(buffer)) > 0) {
    count += len;
}

logger.debug("Got length (2): " + connection.getContentLength() + " but wanted " + count);

输出:

Got length (1): -1
Got length (2): -1 but wanted 30745

好像是JDK6的bug,所以我新开了bug#7168608

  • 如果有人可以帮助我编写代码,应返回正确的内容长度以用于直接 FTP 连接、通过代理的 FTP 连接和本地 file:/ URL,我将不胜感激。
  • 如果给定的问题不能用 JDK6 解决,建议任何其他绝对适用于我提到的所有情况的库 (Apache Http Client?)。

【问题讨论】:

  • 为什么需要内容长度?实际的数据流是否正确?如果是这样,您不需要内容长度,并且一切正常。
  • @jtahlborn:实际 URL 是正确的(它是公共 FTP,因此您也可以测试)。我需要了解内容长度而不将流读到最后,这显然是可以做到的。

标签: java proxy urlconnection http-proxy


【解决方案1】:

请记住,代理通常会更改底层实体的表示。在您的情况下,我怀疑代理可能正在更改传输编码。这反过来又使 Content-Length 即使提供也毫无意义。

您违反了 HTTP 1.1 规范的以下两个部分:

4.4 Message Length

  1. ...
  2. ...
  3. 如果存在 Content-Length 标头字段(第 14.13 节),则其在 OCTET 中的十进制值表示实体长度和传输长度。如果这两个长度不同(即,如果存在 Transfer-Encoding 头字段),则不得发送 Content-Length 头字段。如果收到的消息同时带有 Transfer-Encoding 标头字段和 Content-Length 标头字段,则必须忽略后者。

14.41 Transfer-Encoding

Transfer-Encoding 通用标头字段指示已对消息正文应用了何种(如果有)类型的转换,以便在发送者和接收者之间安全地传输它。这与内容编码的不同之处在于,传输编码是消息的属性,而不是实体的属性。

Transfer-Encoding       = "Transfer-Encoding" ":" 1#transfer-coding

传输编码在第 3.6 节中定义。一个例子是:

Transfer-Encoding: chunked

如果多个编码已应用于一个实体,则传输编码必须按照它们应用的顺序列出。有关编码参数的附加信息可能由本规范未定义的其他实体头字段提供。

许多旧的 HTTP/1.0 应用程序不理解 Transfer-Encoding 标头。

因此,根据规范,URLConnection 然后忽略 Content-Length 标头,因为在存在 chunked 传输时它是没有意义的

在您的调试器屏幕截图中,不清楚是否存在 Transfer-Encoding 标头。请告诉我们...

进一步调查 - 当您发出 lynx -head 时,lynx 似乎没有显示所有返回的标头。它没有显示对本次讨论至关重要的 Transfer-Encoding 标头。

这是与公开可见网站的差异的证明

Ξ▶ lynx -useragent='dummy' -source -head http://www.bbc.co.uk                                                                                                                  
HTTP/1.1 302 Found
Server: Apache
X-Cache-Action: PASS (non-cacheable)
X-Cache-Age: 0
Content-Type: text/html; charset=iso-8859-1
Date: Tue, 03 Apr 2012 13:33:06 GMT
Location: http://www.bbc.co.uk/mobile/
Connection: close

Ξ▶ wget -useragent='dummy' -S -X HEAD http://www.bbc.co.uk                                                                                                                 
--2012-04-03 14:33:22--  http://www.bbc.co.uk/
Resolving www.bbc.co.uk... 212.58.244.70
Connecting to www.bbc.co.uk|212.58.244.70|:80... connected.
HTTP request sent, awaiting response... 
HTTP/1.1 200 OK
Server: Apache
Cache-Control: private, max-age=15
Etag: "7e0f292b2e5e4c33cac1bc033779813b"
Content-Type: text/html
Transfer-Encoding: chunked
Date: Tue, 03 Apr 2012 13:33:22 GMT
Connection: keep-alive
X-Cache-Action: MISS
X-Cache-Age: 0
X-LB-NoCache: true
Vary: Cookie

由于我显然不在您的网络中,因此无法复制您的确切情况,但请确认您在通过代理时确实没有收到 Transfer-Encoding 标头。 p>

【讨论】:

  • 为什么在分块传输的情况下没有意义?如果服务器可以传达整个流的长度,为什么消费者不能使用这些信息?这是URLConnection 的任务,用于收集所有块并向消费者隐藏此协议详细信息。但是好的,如果规范这么说的话......在我的lynx 输出的问题中,你可以看到Transfer-Encoding 不存在,所以我不能投票给你的答案。
  • 您问题中的 lynx 命令看起来很奇怪。首先,-head 不适用于 http URL - 如图所示的命令不适用于 lynx 2.8.7rel.2。其次,您希望如果响应是通过代理,您会在响应中看到 Via: 标头。 (尽管代理并不总是尊重这一点)
  • 感谢您的评论。我给出的 lynx 命令对我来说很好用(针对 v2.8.6rel.5 进行了测试)。 HTTP HEAD 适用于任何 URL,如果您通过代理:您可以为此尝试任何开放代理。如果Via 不存在,并不意味着回复不是来自代理。如果您希望我可以将wget 命令输出,但它会产生等效的输出。正如我所提到的,我不能以其他方式上网:由于防火墙,这是不可能的。事实上,我们的代理配置为不将Via 返回给客户端。 lynx 的输出与 Java 中的输出完全相同,请当真。
  • 我已经编辑了我的问题。我相信office Squid 也遵循规范,如果Transfer-Encodingchunked,则不会返回Content-Length。另外我不相信 Squid 可以分块 FTP 资源。这不是代理的问题,希望我说服了你。非常感谢您的 cmets(我已经了解了分块传输),但是您的方向错误。此外,我不认为 Java 会“仔细检查”规范并巧妙地忽略 Content-Length(如果定义了 Transfer-Encoding)。
  • @dma_k - 好的,我没有任何进一步的建议。如果您找到解决方案,请更新 - 将有兴趣了解。祝你好运
【解决方案2】:

我认为这是 jdk 中与处理代理的 ftp 连接相关的“错误”。当使用代理时,FtpURLConnection 委托给 HttpURLConnection。 然而,在这种情况下,FtpURLConnection 似乎没有将任何标头管理委派给此 HttpURLConnection。因此,您可以正确获取流,但我认为您无法访问任何“标题”值,例如内容长度或内容类型。 (这是基于对 1.6 的 openjdk 源代码的快速浏览,我可能遗漏了一些东西)。

【讨论】:

  • @dma_k wrt jdk 中的错误 - 显然 FTP 客户端代码已针对 JDK 7 进行了彻底检查。bugs.sun.com/view_bug.do?bug_id=6893702bugs.sun.com/view_bug.do?bug_id=6519647 似乎相关(尽管不完全是您的问题)。你试过 JDK 7 吗?
  • 2all:如果您确认问题并可以将其报告给 Sun + share the lisnk,我将奖励赏金(还有 8 小时)。即使问题在 JDK7 中得到解决(我还没有检查过),我也无法从中受益:生产 AS 在 Java6 中,并且在接下来的几年中也会如此。更糟糕的是:代码应该是 1.5 兼容的。
  • @dma_k - 为什么其他人的工作是确认您的问题?我给了你我认为正确的答案。确认应该很简单。
  • HttpURLConnection 没有将标头传播到上层的问题对我来说很清楚,我花了几个小时查看反编译的 Sun 代码来证明这一说法。但是我可能忽略了明显的事情。感谢您查看 OpenJDK 代码,但老实说,我希望有 10k 名声的人能够提出解决方案/变通办法。您对我在问题中提到的要点有什么看法吗?我不问为什么会出现问题,我问如何来克服它。
【解决方案3】:

检查我要做的一件事是实际阅读响应(写下我的头顶,所以期待错误):

URLConnection connection= url.openConnection();
InputStream input= connection.getInputStream();
byte[] buffer= new byte[4096];
while(input.read(buffer) > 0)
  ;
logger.debug("Got length: " + getContentLength());

如果你得到的大小很好,那么寻找一种方法让 URLConnection 读取标题而不是数据以避免读取整个响应。

【讨论】:

  • 运气不好:这不起作用(请参阅我的更新答案)。如果您使用任何公共代理,您可以自己测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 2018-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-20
相关资源
最近更新 更多