Ошибка синтаксического анализатора SAX с замыкающим содержимым Apache mod_dav XML в Java

Я работаю с Apache mod_dav, скомпилированным на моем собственном сервере. Мой клиент — это созданный с нуля пользовательский код синтаксического анализа HTTP на Java. Я использую этот сервер и кодовую базу в течение многих лет, синхронизируя гигабайты данных на сервере.

Сегодня я столкнулся с проблемой, с которой раньше никогда не сталкивался: страшная ошибка SAX «контент не разрешен в завершающем разделе». При выполнении WebDAV PROPFIND по всему дереву ресурсов моего сервера я всегда получаю эту ошибку в одном и том же месте.

Я протестировал и перепроверил свой код синтаксического анализа HTTP, но он довольно прост: Apache отправляет обратно фрагментированный контент, и фрагменты указывают количество байтов, которые нужно использовать.

Место, где он терпит неудачу, - это ответ XML, который использует 110 фрагментов --- значительно больше, чем большинство других ответов (это очень большой каталог). Однако в моих журналах я вижу, что нет «завершающего содержимого» — каждый ответ 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 - это конец файла --- но там ничего нет!!


person Garret Wilson    schedule 10.01.2012    source источник


Ответы (1)


О, чувак, это одна из самых раздражающих и незаметных ошибок, над которыми я когда-либо работал! У меня было такое искушение просто прочитать ответ XML в байтах и ​​вернуть ByteArrayInputStream и вернуть это, хотя я не знал, почему это решило проблему. Получается, что это была моя вина, вроде, технически, но все же...

Так что получается, что если вы читаете контракт API InputStream.read(byte b[], int off, int len), метод никогда не должен возвращать нулевые байты! Если он достигает конца данных, он должен вернуть -1 или заблокировать, пока данные не будут доступны. (Что делать, если вызывающая сторона запрашивает len нуля, неясно, так как это, похоже, не запрещено API. Более современный API указывает, что IllegalArgumentException следует выдавать, если len<1, но я отвлекся.)

Мой HTTPChunkedInputStream автоматически анализирует фрагменты ответа HTTP. Как было написано, если вызывающая сторона HTTPChunkedInputStream.read(byte b[], int off, int len) запросила точно количество байтов, доступных в последнем фрагменте, то входной поток не будет упреждающе пытаться загрузить дальнейшие фрагменты и распознать конец потока. Само по себе это не проблема, но в следующий раз, когда вызывающему объекту потребуется больше байтов, так как алгоритм был написан, мой входной поток попытается прочитать другой фрагмент, распознав, что фрагментов больше не осталось. , а затем указать, что было прочитано ноль байтов! (Обратите внимание, это происходило только в том случае, если вызываемый сначала запрашивал точное количество байтов в последнем фрагменте, а затем запрашивал дополнительные байты.) В любое время после этого он возвращал -1, так как был достигнут конец данных. .

Таким образом, в этом конкретном случае, по какой-то причине, синтаксический анализатор XML запросил точно оставшиеся байты в ответе XML от WebDAV PROPFIND. Затем синтаксический анализатор хотел проверить, есть ли другие символы. Фактическое чтение происходит в 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;
}

Поскольку конец потока не был указан (он не знает, как обращаться с «ничего»), предполагалось, что там есть «что-то», и это что-то должно быть незаконным конечным содержимым.

Вау! Я исправил свой класс HTTPChunkedInputStream, чтобы он никогда не возвращал нулевые байты из read(). Я измотан — это одна из вещей, которые никогда не всплывают, за исключением редких случаев при определенных условиях. И когда я прочитал байты и вернул их в ByteArrayInputStream, это не появилось, потому что мой код для высасывания байтов из HTTPChunkedInputStream никогда не запрашивал точное количество байтов в последнем фрагменте --- а если и запрашивал, то еще знал, как высосать эти нулевые байты и поместить их в буфер вместе с остальными.

person Garret Wilson    schedule 10.01.2012