Apache mod_dav Конечный контент XML Ошибка парсера SAX в Java - PullRequest
1 голос
/ 10 января 2012

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

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

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

Место, где он терпит неудачу, - это XML-ответ, который использует 110 блоков - значительно больше, чем большинство других ответов (это очень большой каталог). Тем не менее, в моих журналах я вижу, что нет «конечного контента» - каждый XML-ответ (который вызывает ошибку, а другой нет) заканчивается простым символом перевода строки.

Но еще больше огорчает: у меня есть входной поток, который анализирует фрагментированный HTTP-контент и отправляет обратно простую строку байтов. Когда я передаю этот поток ввода непосредственно в анализатор XML, я получаю следующую ошибку. Однако: если я возьму того же входного потока и очисту все байты от него, поместим их в ByteArrayInputStream, а затем передам ByteArrayInputStream (который должен содержать точно такие же данные!) Анализатору, ошибок нет происходит! Что такое синтаксический анализ непосредственно из входящих данных, который вызывает ошибку?

Мой XML-парсер довольно прост:

final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);

Кто-нибудь видел это раньше? (Я искал "XML-ошибку mod_dav" --- и только что получил несвязанную ошибку , которую я подал пять лет назад.)

Вот соответствующая часть трассировки стека:

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 - это конец файла --- но там ничего нет !!

1 Ответ

2 голосов
/ 11 января 2012

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

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

My 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 никогда не запрашивал точное количество байтов в последнем блоке --- и еслион все еще знал, как высосать эти нулевые байты и поместить их в буфер вместе с остальными.

...