URLConnection不能正确处理通过代理传输的内容长度

5
我遇到了以下问题:当通过代理使用URLConnection时,内容长度总是设置为-1。
首先,我检查了代理是否真的返回了Content-Length(lynx和wget也通过代理工作;从本地网络访问互联网没有其他方法):
$ 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字段中,但是从来没有被正确地填充:

enter image description here (头部有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
@jtahlborn:实际的URL是正确的(它是公共FTP,所以您也可以进行测试)。我需要学习内容长度,而不必读取流到结尾,显然可以做到。 - dma_k
3个回答

2

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

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

4.4 Message Length

  1. ...
  2. ...
  3. If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present). If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.

14.41 Transfer-Encoding

The Transfer-Encoding general-header field indicates what (if any) type of transformation has been applied to the message body in order to safely transfer it between the sender and the recipient. This differs from the content-coding in that the transfer-coding is a property of the message, not of the entity.

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

Transfer-codings are defined in section 3.6. An example is:

Transfer-Encoding: chunked

If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in the order in which they were applied. Additional information about the encoding parameters MAY be provided by other entity-header fields not defined by this specification.

Many older HTTP/1.0 applications do not understand the Transfer- Encoding header.

所以根据规范,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头。


为什么在分块传输存在的情况下这是毫无意义的?如果服务器可以通知整个流的长度,为什么消费者不能使用这些信息?这是 URLConnection 的任务,它收集所有块并将此协议细节对消费者隐藏。但好吧,如果规范这样说……从 lynx 的输出中可以看出 Transfer-Encoding 不存在,因此我无法为您的答案投票。 - dma_k
你问题中的lynx命令看起来很奇怪。首先,对于http URL,-head不合适。使用所示的命令在我的lynx 2.8.7rel.2上无法工作。其次,如果响应是通过代理进行的,你会期望在响应中看到一个Via:头信息。(尽管代理不总是遵守这个规定) - sw1nn
感谢评论。我给出的lynx命令对我来说运行良好(已测试v2.8.6rel.5)。如果您通过代理方式访问,HTTP HEAD适用于任何URL:您可以尝试任何开放的代理。如果“Via”不存在,则不意味着回复不来自代理服务器。如果您愿意,我可以提供wget命令输出,但结果相同。正如我所提到的,我不能用另一种方式上网,这是由于防火墙的限制。实际上,我们的代理服务器未配置返回“Via”给客户端的设置。lynx的输出与Java中显示的完全相同,请将其视为事实。 - dma_k
我已经编辑了我的问题。我相信Office Squid也遵循规范,如果传输编码是分块的话,它也不会返回Content-Length。此外,我不相信Squid可以对FTP资源进行分块。这不是代理的问题,我希望我已经说服了你。非常感谢您的评论(我学到了关于分块传输的知识),但您走错了方向。我也不认为Java会“双重检查”规范并聪明地忽略Content-Length,如果定义了传输编码的话。 - dma_k
@dma_k - 好的,我没有更多的建议了。如果你找到解决方案,请更新一下 - 我会很感兴趣的。祝你好运。 - sw1nn

1

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


@dma_k 关于JDK中的错误 - 显然,FTP客户端代码已经在JDK 7中进行了彻底的改进。http://bugs.sun.com/view_bug.do?bug_id=6893702和http://bugs.sun.com/view_bug.do?bug_id=6519647似乎是相关的(虽然不完全是你的问题)。你尝试过JDK 7吗? - sw1nn
2all:如果您确认了问题并可以向Sun报告并分享链接,我将授予赏金(还有8小时)。即使问题在JDK7中得到解决(我还没有检查),我也无法从中受益:生产AS是在Java6中,并且在未来几年内仍将如此。更糟糕的是:代码应符合1.5标准。 - dma_k
1
@dma_k - 为什么别人要确认你的问题?我已经给出了我认为是正确答案。对于你来说应该很简单确认一下。 - jtahlborn
我清楚地了解到HttpURLConnection不会将标头传播到上层的问题,并且我花了数小时查看反编译的Sun代码来证明这一说法。但是,我可能已经忽略了显而易见的事情。感谢您查看OpenJDK代码,但老实说,我希望有10k声望的人能够提出解决方案/解决方法。您是否有任何关于我在问题中提到的要点的解决方案?我不是在问为什么会出现这个问题,而是在问如何克服它。 - dma_k

0

我会检查的一件事是实际阅读响应(脑海中写作,所以可能会有错误):

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

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

很遗憾:它不起作用(请参见我的更新答案)。如果您使用任何公共代理,您可以自行测试。 - dma_k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接