Apache mod_dav XML尾随内容Java中的SAX解析器错误
我正在使用我自己的服务器上编译的Apache mod_dav。我的客户端是用Java构建的从头开始的自定义HTTP解析代码。我一直在使用这个服务器和代码库,在服务器上同步千兆字节的数据。Apache mod_dav XML尾随内容Java中的SAX解析器错误
今天我遇到了一个从未出现的问题:可怕的SAX“内容不允许在尾部”错误。在整个服务器资源树中执行WebDAV PROPFIND时,我总是在相同的位置出现此错误。
我测试并重新测试了我的HTTP解析代码,但它非常简单:Apache正在发送回分块内容,并且块指示要消耗的字节数。
它失败的地方是恰好使用110块的XML响应---比大多数其他响应(这是一个非常大的目录)大得多。但是,在我的日志中,我可以看到没有“尾随内容” - 每个XML响应(产生错误,不响应)以简单的换行符结束。
但更令人苦恼的是:我有一个输入流,用于解析HTTP分块内容并返回一个简单的字节字符串。当我将此输入流直接传递给XML解析器时,出现以下错误。但是,如果我采用相同的输入流并从其中流出所有字节,请将它们放在ByteArrayInputStream中,然后将ByteArrayInputStream(应该包含完全相同的数据!)发送到解析器,不会发生错误!直接从输入数据解析导致错误的是什么?
我的XML解析器是非常简单的:
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);
没有人见过这个? (我搜索“的mod_dav XML的错误” ---和刚拿到无关bug我五年前提出。)
这里是堆栈跟踪的相关部分:
Cause:org.xml.sax.SAXParseException: Content is not allowed in trailing section.
com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(Unknown Source)
com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(Unknown Source)
javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
com.globalmentor.net.http.HTTPClientTCPConnection.readResponseBodyXML(HTTPClientTCPConnection.java:666)
com.globalmentor.net.http.webdav.WebDAVResource.propFind(WebDAVResource.java:453)
更新:我一遍又一遍地做了这个测试。最后,我添加了代码走堆栈跟踪并打印出SAX解析信息获取:
Public Id: null System Id: null Line# 21937 Column# 1
我从日志文件复制XML,果然,线21937是文件的结尾---但那里什么都没有!
哦,男人 - 这是我曾经工作过的最加重和微妙的错误之一!我非常想读取XML格式的响应,并返回一个ByteArrayInputStream
并返回,虽然我不知道为什么解决了这个问题。事实证明,这是我的错,有点,在技术上,但仍...
因此,事实证明,如果你读了InputStream.read(byte b[], int off, int len)
的API合同,该方法永远不会返回零字节!如果到达数据的末尾,它应该返回-1,或阻塞直到数据可用。 (如果调用者请求len
为零,该怎么办目前还不清楚,因为这似乎不被API所禁止,更现代化的API将指定如果len<1
,len<1
应该抛出IllegalArgumentException
,但我离题了。)
我的HTTPChunkedInputStream
自动解析出一个HTTP分块响应的块。在写入方式中,如果HTTPChunkedInputStream.read(byte b[], int off, int len)
的调用者请求确切地说是上一个块中可用的字节数,则输入流不会主动尝试加载更多块并识别该流的结束。这本身并不是问题,但当调用者需要更多字节时,算法被写入的方式,我的输入流将尝试读取另一个块,识别出没有剩余块,然后指示零字节被读取! (请注意,只有当被调用的第一次请求的是最后一个块中的字节数,然后再询问更多字节时才会发生这种情况。)任何时间之后它都会返回-1,因为数据的结尾已经被触发。
因此,在这种特殊情况下,无论出于何种原因,XML解析器都要求WebDAV PROPFIND的XML响应中的剩余字节。然后解析器想要检查是否还有其他字符。实际读数发生在UTF8Reader
;当我的输入流返回零字节被读取时,这被传递了XMLEntityScanner
。这些类都不知道如何处理“没有读取字节”---它只是假设读取了。最后,XMLDocumentScannerImpl
检查,看看是什么“东西”是在1453行:
int ch = fEntityScanner.peekChar();
if (ch == -1) {
setScannerState(SCANNER_STATE_TERMINATED);
return XMLEvent.END_DOCUMENT ;
} else{
reportFatalError("ContentIllegalInTrailingMisc",
null);
fEntityScanner.scanChar();
setScannerState(SCANNER_STATE_TRAILING_MISC);
return XMLEvent.CHARACTERS;
}
由于流的末尾也没有说明(不知道怎么处理“无”),它假定有在那里是“某些东西”,而这个东西必须是非法的后面的内容。
Whe!我已经修复了我的HTTPChunkedInputStream
类,永远不会从read()
返回零字节。我已经筋疲力尽---除非在某些情况下,这种情况甚至不会出现。当我读取字节并将它们返回到ByteArrayInputStream
时,这并没有显示出来,因为我的代码吸取HTTPChunkedInputStream
中的字节从未请求过最后一个块的字节数 - 如果它确实如此,仍然知道如何吸出这些零字节并将它们与其他字节一起放入缓冲区。