BufferedReader застрял в readLine ()

Я пытаюсь получить HTTP-запрос от Google Chrome, чтобы получить данные. Для этого я использую readLine() из BufferedReader, но по какой-то причине мне кажется, что он застревает в последней строке, потому что буфер остается открытым и ожидает ввода дополнительных данных. Вот код, который я использую в цикле while:

String line;
ArrayList<String> request = new ArrayList<String>();
while ((line = inFromClient.readLine()) != null) {
    request.add(line);
}

Если я принудительно разорву цикл, он сработает, в основном я пытаюсь эффективно прочитать все строки, но без несоответствий ready()


person Bruno Cotrim    schedule 12.10.2020    source источник
comment
Что ты имеешь в виду, он застревает на последней строчке? Он застревает на }?   -  person Spectric    schedule 12.10.2020
comment
Последняя строка HTTP-запроса это первая строка-GET /mp1.html HTTP / 1.1 после того, как есть тело, а затем после загрузки всех данных он застревает в конце чтения, как здесь Accept-Language: pt- PT, pt; q = 0.9, en-US; q = 0.8, en; q = 0.7 - здесь застревает, я мог бы использовать isEmpty, чтобы остановить цикл, проблема в том, что тело и данные разделены пустой строкой   -  person Bruno Cotrim    schedule 12.10.2020
comment
Я не знаю, как Google Chrome отправляет свои данные в вашу программу, но я думаю, что он, вероятно, оставляет поток открытым и повторно использует его для всех HTTP-запросов. Если я посмотрю документацию для BufferedReader#readLine() он сообщает [возвращает] null, если достигнут конец потока. Я подозреваю, что ваш цикл на самом деле не застрял, он просто ждет дополнительных данных. То, что вы прочитали все доступные данные, не означает, что вы достигли конца потока.   -  person Charlie Armstrong    schedule 12.10.2020
comment
Интересно, может быть, поэтому с настраиваемым клиентом это работает, потому что я закрываю поток, и если это происходит с хромом, как можно прочитать все строки до последней, если поток никогда не закрывается?   -  person Bruno Cotrim    schedule 12.10.2020
comment
Вы можете проверить _ 1_ вместо нулевой проверки.   -  person Charlie Armstrong    schedule 12.10.2020
comment
Большое спасибо, этот пост действительно помог с подготовкой, я просто нахожу некоторые несоответствия с этой реализацией, я постараюсь обойти это и посмотреть, куда я могу пойти дальше.   -  person Bruno Cotrim    schedule 12.10.2020
comment
@HamzaBelmellouki - это предлагает использовать ready() в качестве решения ... что является плохой идеей.   -  person Stephen C    schedule 12.10.2020
comment
Использование ready() ненадежно. Возможно, что ready() вернет false, когда еще есть данные, но их еще нет из-за сбоя в сети.   -  person Stephen C    schedule 12.10.2020
comment
Считывание строк до тех пор, пока их больше нет, просто некорректно. Реальное решение состоит в том, чтобы реализовать достаточное количество протокола HTTP, чтобы вы могли определить, когда вам следует прекратить чтение строк ... в соответствии с протоколом. Или еще лучше использовать существующую библиотеку, которая реализует протокол на стороне сервера.   -  person Stephen C    schedule 12.10.2020
comment
@StephenC, согласен. Однако он может предотвратить блокировку потока, выполняющего этот код, если он ожидает данных из сети. Если он вернет false, возможно, он сможет сделать что-то еще, тогда он сможет снова проверить готовность данных.   -  person Hamza Belmellouki    schedule 12.10.2020
comment
Это для целей понимания Http, но я пытаюсь продвинуть его немного дальше, пытаясь заставить код работать по каждому запросу, я не использую библиотеки, потому что его попросили реализовать HTTP вручную, чтобы лучше понять, как он работает, я попробовал чтобы найти шаблоны, когда следует прекратить чтение строк в HTTP-запросах, но я не мог найти твердый шаблон, я исправил код и попытался сделать готовый немного более надежным, вернув существование данных в буфере   -  person Bruno Cotrim    schedule 12.10.2020


Ответы (1)


HTTP кажется безумно простым протоколом, но это не так; вам следует использовать клиентскую библиотеку HTTP, такую ​​как встроенный _ 1_ клиент.

Проблема в том, что концепция «дайте мне мои данные, а затем закройте их» - это HTTP / 1.0, и она устарела на несколько десятилетий. HTTP / 2.0 и HTTP / 3.0 являются двоичными протоколами, а HTTP / 1.1 имеет тенденцию оставлять соединение открытым. В общем, «читать строки» и даже «использовать Reader» (например, читать символы вместо байтов) - это неправильный способ сделать это, поскольку HTTP не является текстовым протоколом. Я знаю. Похоже, что один. Это не.

Вот очень упрощенный обзор того, как, например, браузер читает ответы HTTP / 1.1:

  1. Используйте необработанную обработку байтов, потому что содержимое тела HTTP является необработанным (или может быть), поэтому все это обертывается, например, InputStreamReader или BufferedReader не запускается.
  2. Продолжайте читать, пока не будет прочитан байт 0x0A (в ASCII, символ новой строки), или X байтов и ваш буфер для этого не будет заполнен, где X не является чрезмерно большим. Не хотелось бы, чтобы сервер с плохим поведением или недоразумение при подключении к другой (не HTTP) службе вызвало проблемы с памятью! Разберите эту первую строку как ответ HTTP / 1.1.
  3. Продолжайте делать этот цикл, чтобы собрать все заголовки. Используйте тот же трюк «у моего буфера есть ограничения», чтобы избежать проблем с памятью.
  4. Затем проверьте код ответа, чтобы узнать, будет ли тело в ближайшее время. Это HTTP / 1.1, так что вы не можете просто пойти: ну, если соединение закрыто, я думаю, никто не появится. Придет он или нет, зависит в первую очередь от кода ответа.
  5. Предполагая, что тело существует, прочтите двойной символ новой строки, отделяющий заголовки от тела.
  6. Если содержимое передается как фрагментированное кодирование (обычное), начните копирование данных в буфер, но проверьте, читаете ли вы весь фрагмент. На самом деле чтение фрагментированной кодировки - это отдельная игра.
  7. В качестве альтернативы HTTP / 1.1 ТРЕБУЕТ, что, если кодирование по фрагментам не используется, присутствует Content-Length. Используйте этот заголовок, чтобы точно знать, сколько байтов нужно прочитать.
  8. Ни «новая строка», ни «тесное соединение» никогда не могут служить значимым маркером «конца данных» в HTTP / 1.1, так что не делайте этого.
  9. Затем либо дословно передайте содержимое + заголовки + код возврата запрашивающему коду, либо немного его приукрасите. Например, если заголовок Content-Type присутствует и имеет значение text/html; encoding=UTF-8, вы можете рассмотреть возможность преобразования основных данных в строку через UTF-8 (new String(byteArray, StandardCharsets.UTF_8);).

Обратите внимание, что я пропустил какое-то странное поведение серверов, потому что в былые времена какой-то глупый браузер делал странные вещи, и теперь это статус-кво (например, запросы диапазона довольно странные), и, конечно же, HTTP2 и HTTP3, которые совершенно разные протоколы.

Кроме того, конечно, в наши дни редко встречаются HTTP-серверы; HTTPS - вот где он, и это тоже совсем другое.

person rzwitserloot    schedule 12.10.2020
comment
У меня простой вопрос. Как браузер определяет кодировку заголовка? или все они предполагают, что это ASCⅡ. - person haoyu wang; 12.10.2020
comment
Спецификация более или менее подразумевает, что все байты во всем, по крайней мере, пока вы не дойдете до заголовка, - это ISO-8859-1, а на практике - ASCII. - person rzwitserloot; 12.10.2020