Rails + Puma + Transfer-Encoding: фрагментированный ответ — поддерживается или нет?

Конфигурация

  • Рельсы: 4.2.7.1
  • Пума: 3.8.2

--

Transfer-Encoding:  chunked

Я не смог выполнить эту работу и не смог найти окончательного ответа: в приведенной выше конфигурации я хочу передавать клиенту большие объемы данных (в ответе) - поддерживается ли это?

  • If so, what is my responsibility?
    • Should my controller be emitting hexadecimal chunk sizes, \r\n, 0 etc?
  • It feels like I want but can't find a simple response API like:
    • write, write, write, flush
    • write, write, write, flush
    • close
  • Прочитал 100 постов про Rack, мартышкиные патчи и прочий маразм
  • Я читал о том, что Puma и/или Rack искажают кодировку, возможно, gzip/deflating в неправильном порядке.
  • Это кажется простой функцией, которая должна быть легко доступна, но я в тупике
  • Я создал множество тестов, например. self.response_body = Enumerator.new и response.stream.write и т. д. -- все с одинаковыми результатами (через curl) – Malformed encoding found in chunked-encoding или transfer closed with outstanding read data remaining

Может ли кто-нибудь показать мне свет?


person Publius    schedule 06.08.2018    source источник


Ответы (2)


API ActionController::Live предлагает именно то, что вы описываете: response.stream.write и response.stream.close. (write автоматически сбрасывает фрагмент; вам нужно будет выполнить собственную буферизацию, если это не сработает для вас.)

Пока вы include ActionController::Live (и обратите внимание, что это влияет на поведение всего контроллера, а не только на одно действие), вы сможете написать потоковый ответ без дополнительных усилий: вам не нужно и не следует устанавливать какие-либо заголовки и т. д. связанные с чанкингом.

Дополнительная ссылка: http://tenderlovemaking.com/2012/07/30/is-it-live.html

person matthewd    schedule 07.08.2018
comment
Не совсем так - API, который вы цитируете, устанавливает заголовок Content-Type: text/event-stream, который поддерживают не все браузеры. (AFAIK) Кроме того, я не думаю, что это масштабируемое решение из-за реализации многопоточности. - person Publius; 07.08.2018
comment
В примере используется этот тип содержимого; вы можете установить свой на все, что вам нравится. - person matthewd; 07.08.2018
comment
Я думаю, вы запутались - заголовок Transfer-Encoding, а не Content-Type, имеет отношение к этому обсуждению. Предположительно, вы знаете, как работает Transfer-Encoding: chunked и как клиент должен его обрабатывать. Content-Type: text/event-stream создает события на стороне сервера, и не все клиенты (браузеры) поддерживают это. С другой стороны, все браузеры поддерживают Transfer-Encoding: chunked. - person Publius; 07.08.2018
comment
def show response.headers[Transfer-Encoding] = chunked 100.times { response.stream.write hello world\n } обеспечить response.stream.close end Результат: HTTP/1.1 200 OK ‹snip› Transfer-Encoding: chunked Cache- Элемент управления: без кеша Content-Type: text/html; charset=utf-8 ‹/snip› Соединение: закрыть Сервер: тонкий завиток: (56) Недопустимая или отсутствующая шестнадцатеричная последовательность в кодировании по частям @matthewd - person Publius; 07.08.2018
comment
вам не нужно, и не следует устанавливать какие-либо заголовки и т. д., связанные с фрагментацией. (Кроме того, стройность — это не пума.) - person matthewd; 08.08.2018
comment
Хорошо - я думаю, что мы ходим по кругу, так как мой первоначальный вопрос остается в силе - как нанять Transfer-Encoding: chunked. Content-Type: text/event-stream (т. е. события, отправленные сервером), хотя интересно не то, о чем я спрашиваю. Тем не менее, я ценю ваши комментарии. @matthewd - person Publius; 08.08.2018
comment
Следуйте примеру в документе ActionController::Live. Установите тип содержимого на любой, который вам нравится, но больше ничего не меняйте. Разделением будет управлять Puma. Пытаясь установить заголовок самостоятельно, вы отключаете встроенную функциональность. - person matthewd; 08.08.2018
comment
вы правы на 100%. Я не понимал, что происходит в моей среде разработки — я не осознавал, что Thin работает вместо Puma. На самом деле, я даже не знал, что Тонкий был чем-то особенным. - person Publius; 08.08.2018

Важное дополнение к замечательному ответу @matthewd:

Спецификация Rack поддерживает потоковый ответ через использование метода each в объекте ответа или через использование hijack.

@matthewd прав, заявляя, что:

API ActionController::Live предлагает именно то, что вы описываете...

Однако реализация либо захватывает сокет, либо использует «взлом» спецификации Rack с методом each.

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

Однако это может привести к большому количеству потоков и ухудшению производительности: потоки занимают больше места в памяти для данных стека (1–2 МБ на поток/клиент), а переключение контекста становится дороже по мере увеличения количества потоков. создаются.

В худшем случае медленный цикл each заблокирует серверный поток, выведя сервер из строя и в конечном итоге приведя к ситуации DoS.

Правильный ответ должен быть НЕ для потоковой передачи данных по одному HTTP-запросу — вместо этого используйте собственное* решение WebSockets, SSE или AJAX.

Другим полуправильным методом будет сохранение всех данных во временный файл и отправка файла с использованием сервера, поддерживающего статическую потоковую передачу файлов за пределы уровня Ruby (например, iodine) или прокси-сервер (например, nginx).

* Встроенное: собственное решение WebSocket/SSE соответствует этому предложению Rack и позволяет серверу обрабатывать сетевой уровень, а не запускать еще один поток/реактор ввода-вывода. См. этот пост в блоге для более подробной информации.

person Myst    schedule 27.08.2018