2012-01-10 54 views
1

我正在使用我自己的服務器上編譯的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是文件的結尾---但那裏什麼都沒有!

回答

2

哦,男人 - 這是我曾經工作過的最加重和微妙的錯誤之一!我非常想讀取XML格式的響應,並返回一個ByteArrayInputStream並返回,雖然我不知道爲什麼解決了這個問題。事實證明,這是我的錯,有點,在技術上,但仍...

因此,事實證明,如果你讀了InputStream.read(byte b[], int off, int len)的API合同,該方法永遠不會返回零字節!如果到達數據的末尾,它應該返回-1,或阻塞直到數據可用。 (如果調用者請求len爲零,該怎麼辦目前還不清楚,因爲這似乎不被API所禁止,更現代化的API將指定如果len<1len<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中的字節從未請求過最後一個塊的字節數 - 如果它確實如此,仍然知道如何吸出這些零字節並將它們與其他字節一起放入緩衝區。