LINUX.ORG.RU

Как вычитать http запрос?

 


1

1

Хочу вычитать входящий запрос по протоколу HTTP 1.0 или 1.1 и послать ответ, в котором будет текст запроса.

Я так понимаю, простой алгоритм вида «читать, пока клиент не закрыл соединение» не сработает, т.к. современные клиенты пытаются в http keepalive и соединение надо закрывать самому со стороны сервера. Но чтобы закрыть соединение, мне нужно вычитать весь запрос.

Выходит, придётся парсить запрос.

Я так понимаю, есть три алгоритма:

transfer-encoding не указан и content-length не указан. Тогда читаем до закрытия соединения.

content-length указан. Тогда читаем указанное число байтов (ну или до закрытия соединения, что будет странно).

указан transfer-encoding и в нём присутствует chunked. Тогда читаем чанки, пока не придёт чанк нулевой длины, что сигнализирует конец запроса.

Пока не понял, что делать, если указан и chunked и content-length, но я прочитаю.

Можно ли считать, что этого хватит для любых разумных и неразумных (но в рамках протокола) запросов?

★★★

Последнее исправление: vbr (всего исправлений: 1)

Клиент не будет закрывать соединение не из-за keep-alive, а из-за того что ему от сервера ещё ответ нужен.

Алгоритм один: если есть Content-Length то это длина данных после конца заголовков, если нет - данных значит тоже нет. В не-POST запросах данных быть не должно, если кто-то присылает GET с контентом то он невалидный.

На всякую экзотику (метод PUT и ещё что-то с контентом, кроме POST, а так же chunked формат тела запроса) можешь смело забивать, если только нет заранее уверенности в том что именно её тебе и будут слать (браузеры такое никогда не делают, например, и всякие популярные утилиты типа wget или curl - тоже).

Ну а так, от поддержки chunked вреда конечно не будет. chunked + content-length это скорее всего невалидное вообще, или можешь проигнорить content-length.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Я ещё и перепутал transfer-encoding и content-encoding. Даже не знал, что gzip может быть на уровне transfer-encoding. Ладно, вроде пока понятно. Похоже, что в запросе не указывать длину и закрывать соединение это невалидно, только в ответе так можно делать.

vbr ★★★
() автор топика

Можно ли считать, что этого хватит для любых разумных и неразумных (но в рамках протокола) запросов?

Я когда-то примерно так и делал, но для самописного клиента, проблем не было.

goingUp ★★★★★
()
Ответ на: комментарий от vbr

в запросе не указывать длину и закрывать соединение это невалидно

В rfc1945 (про HTTP/1.0) явно говорится: «A valid Content-Length field value is required on all HTTP/1.0 request messages containing an entity body.»

В rfc7230 довольно много вариантов, и мне сходу не получилось найти прям явное указание. Там для описания используется слово SHOULD, и непонятно, что будет если клиент всё же не установит Content-Length.

В любом случае если клиент закроет соединение до ответа сервера, сервер свободен делать что хочет. Можно просто представить, что сервер послал бы адекватный ответ, но раз клиент закрыл соединение, то ответа он всё равно не увидит, поэтому любое поведение сервера будет «правильным».

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Соединение закрывается с каждой стороны отдельно. Ничего не мешает закрыть соединение клиенту, но при этом ждать ответа от сервера. Сервер получит fin от клиента, его read вернёт 0, но write будет работать и отсылать данные.

Но в данном случае это нерелевантно. Ну по крайней мере я такие слова нашел в 7230:

3.3.3.6. If this is a request message and none of the above are true, then the message body length is zero (no message body is present).

3.3.3.7 Otherwise, this is a response message without a declared message body length, so the message body length is determined by the number of octets received prior to the server closing the connection.

Ну а предыдущий пункт вроде как определяет, что если длины нет, то для запроса это значит, что и тела нет. В принципе логично.

Резюмируя - в запросе один из заголовков, связанных с длиной, быть должен, если нет, значит и тела нет и точка. В ответе - может не быть, но тело может быть, хотя это относительно редкий случай.

vbr ★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

Соединение закрывается с каждой стороны отдельно. Ничего не мешает закрыть соединение клиенту, но при этом ждать ответа от сервера. Сервер получит fin от клиента, его read вернёт 0, но write будет работать и отсылать данные.

Это всё теория, на практике так не делают. А если кто попытается, то он и так столкнётся с кучей неожиданных проблем, поскольку никто кроме него про это не думал и не поддерживает. Возвращённый 0 из read большинство софта считает полностью закрытым коннектом и в детали не вдаётся.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)

Выше уже написали что дело не в Keep-Alive, а в том что клиент ждет ответ от сервера. Сервер должен прочитать запрос (в том числе тело, даже если оно ему не нужно), а только после этого отправить ответ. В след за запросом, кстати, может идти еще один запрос, если клиент использует pipelining.

Теперь по схеме передачи:

  • «transfer-encoding не указан и content-length не указан. Тогда читаем до закрытия соединения.» – вот такого на практике не бывает. И я сомневаюсь что какой-то rfc на http/1.0 так разрешает делать при передаче запроса.
  • «content-length указан. Тогда читаем указанное число байтов (ну или до закрытия соединения, что будет странно).» – вот это основной способ передачи запроса. Преждевременное закрытие соединения в этом случае нужно считать ошибкой.
  • «указан transfer-encoding и в нём присутствует chunked» – это довольно редкий кейс. Браузеры так никогда не делают, но может делать curl или какой-нибудь софт вооруженный http клиентом.
maxcom ★★★★★
()
Ответ на: комментарий от firkax

Я chunked встречал не один и не два раза в жизни, а довольно регулярно. Но не могу вспомнить, кто их слал.

Совершенно точно есть те, кто используют chunked, значит, игнорировать его не стоит, чтобы потом не разгребать неожиданные проблемы.

emorozov
()
Ответ на: комментарий от emorozov

Так делают всякие двунаправленные stream-over-http врапперы. В большинстве случаев - исключтельно потому, что какому-то быдлокодеру было лень сначала приготовить строку a=4&b=1 и отдать её целиком как тело запроса, и он вместо этого делает что-то типа stream.write('a=');stream.write_int(a);stream.write('&b=');stream.write_int(b);, из-за чего враппер отсылает 4 чанка по 1-3 байта в каждом. Поддерживать этот идиотизм по-дефолту по-моему незачем. И самый редкий и экзотический случай - когда реально нужен стриминг чего-то в сторону сервера, но автор же не собирался принимать потоковые запросы.

firkax ★★★★★
()
Ответ на: комментарий от maxcom

«transfer-encoding не указан и content-length не указан. Тогда читаем до закрытия соединения.» – вот такого на практике не бывает. И я сомневаюсь что какой-то rfc на http/1.0 так разрешает делать при передаче запроса.

Для ответов такое бывает и я сам с этим сталкивался, почему и экстраполировал до запросов. Для запросов, похоже, да, не бывает.

«указан transfer-encoding и в нём присутствует chunked» – это довольно редкий кейс. Браузеры так никогда не делают, но может делать curl или какой-нибудь софт вооруженный http клиентом.

Современный хром может делать: Streaming requests with the fetch API

Но, да, появилось, похоже, совсем недавно.

vbr ★★★
() автор топика
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от firkax

На всякую экзотику (метод PUT и ещё что-то с контентом, кроме POST, а так же chunked формат тела запроса) можешь смело забивать, если только нет заранее уверенности в том что именно её тебе и будут слать (браузеры такое никогда не делают, например, и всякие популярные утилиты типа wget или curl - тоже).

Но фронтенд (веб или мобилка) вполне может послать и PUT, и PATCH

static_lab ★★★★★
()

Мне кажется, что для любого мало-мальски популярного ЯП уже есть парсер http, и скорее всего, не один. Поэтому правильным решением будет использовать его, а не делать свой велосипед с неполной и кривой реализацией RFC :)

annulen ★★★★★
()
Ответ на: комментарий от vbr

как результат? хочу глянуть.

я тут тоже свой своял (это пример обычного запроса, или вот chuncked), на плюсах, он вроде работает, правда в реальном проекте не использовал. и это только парсер, то есть он просто читает данные, ничего о соединениях не знает.

zerhud
()
Ответ на: комментарий от firkax

стоит

Сколько?

не абстрактно-теоретически

Пожалуй отвечу вопросом на вопрос, можешь описать веб-приложение сложнее TODO, в котором нет нужды в PUT и PATCH? Ок, PATCH бывает не нужен, как и chunked, но PUT?

Можно конечно и без GET обойтись при желании, но советовать такое не стоит :)

ddidwyll ★★★★
()
Ответ на: комментарий от ddidwyll

Можно конечно и без GET обойтись при желании, но советовать такое не стоит

Если клиент отправляет запрос, который сервер не поддерживает, то это ошибка клиента. Вполне имеет право на жизнь специализированный сервер, умеющий только PUT, если он, к примеру, занимается сбором крэшлогов. Если клиентом может быть браузер под управлением человека, тогда, без условно, лучше GET поддержать.

можешь описать веб-приложение сложнее TODO, в котором нет нужды в PUT и PATCH

Любая страница без интерактива, возвращающая статику или данные из базы, обойдется одним GET.

Вообще, по секрету скажу: все что угодно можно реализовать с помощью GET и POST. Да, результат может оказаться не RESTful, но кому на это не плевать?

annulen ★★★★★
()
Ответ на: комментарий от ddidwyll

Без GET нельзя, он нужен чтобы отличать статические или псевдостатические запросы от отправки данных или динамических запросов. Последние два делаются POST, и зачем нужны ещё какие-то методы отправки данных - я не знаю.

Учитывая что автор хочет чтобы ему зеркально назад возвращался запрос, речь идёт про отладку отправки всяких <form>, которые шлются через POST. Ни разу не видел чтобы браузер слал PUT, PATCH или ещё какую экзотику. Ну а т.н. «веб-приложения» не в браузере - не нужны.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

Без GET нельзя

Да без проблем, все меж-серверные запросы ничто не мешает делать только POST'ом, но это не отменяет того факта, что, к примеру, почти все апи, что я видел использовали get, post, put, delete и, в меньшей степени, patch.

Учитывая что автор хочет чтобы ему зеркально назад возвращался запрос

Я для такого использую httpbin.org, в равной степени для всех методов и разных transfer-encoding'ов.

речь идёт про отладку отправки всяких <form>

Из чего это следует?

Ни разу не видел

Показать?

я не знаю

не нужны

Ок, ты победил.

ddidwyll ★★★★
()
Ответ на: комментарий от annulen

все что угодно можно

Если очень хочется, но и советовать такое не стоит. В чем смысл в случае ТС'а реализовывать только POST? Особенно учитывая его отличия от PUT и PATCH в контексте темы.

ddidwyll ★★★★
()
Последнее исправление: ddidwyll (всего исправлений: 1)
Ответ на: комментарий от firkax

Покажи пример где ему это нужно для других целей может быть.

Покажи где про формы.

Да

const element = document.querySelector('#put-request .date-updated');
const requestOptions = {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title: 'Fetch PUT Request Example' })
};
fetch('https://reqres.in/api/articles/1', requestOptions)
    .then(response => response.json())
    .then(data => element.innerHTML = data.updatedAt );
ddidwyll ★★★★
()
Ответ на: комментарий от firkax

Сорри, неправильно прочитал первое, тяжелая неделя.

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

ddidwyll ★★★★
()
Ответ на: комментарий от firkax

Без обид, я эмоционально отреагировал на высказывание про экзотику пута и чанков, потому что для меня GET/POST, 200/400, div/span -only и т.д. и т.п. прочно ассоциируется с непрофессионализмом (что само по себе не плохо).

ddidwyll ★★★★
()
Ответ на: комментарий от ddidwyll

Хм, ну ладно, видимо и правда бывает.

Но по мне так инкапсулировать все отправки чего угодно (кроме урла запроса) на сервер в POST это самый простой и заведомо беспроблемный вариант. Приведённый пример вполне мог быть отправлен хотя бы в виде POST /api/article_create?id=1 с тем же телом запроса, минусов (кроме чьей-то субъективной эстетики) не вижу. А вот плюсы есть - POST точно поддерживается прям всеми и везде, в отличие от.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

запрос можно и самому сдампить по-моему

К примеру - может понадобиться запатчить и пересобрать http-клиент или как-то проксировать, чтобы увидеть итоговый запрос, а его путь - один параметр в моём коде. В зависимости от обстоятельств любой из этих вариантов может быть удобнее.

ddidwyll ★★★★
()
Последнее исправление: ddidwyll (всего исправлений: 1)