2015-07-28 8 views
9

Я работаю в разделе модуля Perl, который создает большой ответ CSV. Сервер работает на Plack, на котором я далек от эксперта.Отправка небуферизованного ответа в Plack

В настоящее время я использую что-то вроде этого, чтобы отправить ответ:

$res->content_type('text/csv'); 
my $body = ''; 
query_data (
    parameters => \%query_parameters, 
    callback => sub { 
     my $row_object = shift; 
     $body .= $row_object->to_csv; 
    }, 
); 
$res->body($body); 
return $res->finalize; 

Однако, что query_data функция не быстрый один и получает много записей. Там я просто объединяю каждую строку в $body и после того, как все строки обработаны, отправив весь ответ.

Мне не нравится это по двум очевидным причинам: во-первых, требуется много оперативной памяти, пока не будет уничтожен $body. Во-вторых, пользователь не видит активности ответа до тех пор, пока этот метод не завершит работу и фактически отправит ответ с $res->body($body).

Я попытался найти ответ на этот вопрос in the documentation, не найдя, что мне нужно.

Я также попытался позвонить $res->body($row_object->to_csv) в мой раздел обратного вызова, но похоже, что это заканчивается тем, что отправляет только последний звонок, который я сделал для $res->body, переопределяя все предыдущие.

Есть ли способ отправить ответ Plack, который очищает содержимое в каждой строке, чтобы пользователь начал получать контент в реальном времени по мере сбора данных и без необходимости накапливать все данные в первую очередь?

Заранее благодарим за любые комментарии!

+0

Я не пробовал это, но вы должны иметь возможность использовать объект с методом 'getline'. Для более подробной информации отправьте короткий, но полный пример. –

+0

Спасибо Синан. Да, то, что вы упоминаете, правильно, я попробовал простой объект, реализующий 'getline', и он работал нормально, за исключением того факта, что Plack все еще буферизует ответ и не отправляет ничего в браузер до тех пор, пока' -> getline' не будет определен , Что касается моего примера, я изменил его, чтобы сделать его немного более понятным. Единственное реальное отличие, отправляя мой реальный код, - это много не связанных строк, добавленных в микс. Единственное, что я пытаюсь выяснить, - это сделать, чтобы Plack отправил небуферизованный ответ. –

+0

В коде отсутствует функция 'get_data'. Я думаю, вы имеете в виду 'query_data'. – simbabque

ответ

2

Вы не можете использовать Plack::Response, потому что этот класс предназначен для представления полного ответа, и вы никогда не получите полного ответа в памяти за один раз. То, что вы пытаетесь сделать, называется streaming, а PSGI supports it, даже если Plack :: Response нет.

Вот как вы могли бы идти о реализации его (адаптировано из вашего образца кода):

my $env = shift; 

if (!$env->{'psgi.streaming'}) { 
    # do something else... 
} 

# Immediately start the response and stream the content. 
return sub { 
    my $responder = shift; 
    my $writer = $responder->([200, ['Content-Type' => 'text/csv']]); 

    query_data(
     parameters => \%query_parameters, 
     callback => sub { 
      my $row_object = shift; 
      $writer->write($row_object->to_csv); 
      # TODO: Need to call $writer->close() when there is no more data. 
     }, 
    ); 
}; 

Некоторые интересные вещи об этом коде:

  • Вместо того, чтобы возвращать Plack::Response объект, вы можете вернуться a sub. Эта подпрограмма будет вызвана через некоторое время, чтобы получить фактический ответ. PSGI поддерживает это, чтобы разрешить так называемые «отложенные» ответы.
  • Подпрограмма, которую мы возвращаем, получает аргумент, который является coderef (в данном случае $responder), который должен быть вызван и передан реальный ответ. Если реальный ответ не включает в себя «тело» (т. Е. Обычно это 3-й элемент arrayref), то $responder вернет объект, для которого мы можем написать тело. PSGI поддерживает это, чтобы разрешить потоковое ответов.
  • Объект $writer имеет два метода: write и close, которые оба делают точно так же, как предлагают их имена. Не забудьте вызвать метод close для завершения ответа; приведенный выше код не показывает этого, потому что, как его следует вызывать, зависит от того, как работает query_data и ваш другой код.
  • Большинство серверов поддерживают такую ​​передачу. Вы можете проверить $env->{'psgi.streaming'}, чтобы быть уверенным в том, что у вас есть.
+0

Удивительно! Спасибо за это. :-) –

-2

Некоторые материалы для чтения для вас :)

Так копировать/вставить/адаптировать и доложите пожалуйста

-1

Плакет - это промежуточное программное обеспечение. Используете ли вы среду веб-приложений поверх нее, например Mojolicious или Dancer2, или что-то вроде сервера Apache или Starman под ним? Это повлияет на работу буферизации.

Ссылка выше показывает пример по автору Plack по: https://metacpan.org/source/MIYAGAWA/Plack-1.0037/eg/dot-psgi/echo-stream-sync.psgi

Или вы можете сделать это легко с помощью Dancer2 на вершине Plack и Starman или Apache: https://metacpan.org/pod/distribution/Dancer2/lib/Dancer2/Manual.pod#Delayed-responses-Async-Streaming

С уважением, Петр