Эффективное обслуживание файла с помощью Play 2.3

#file #scala #playframework

#файл #scala #playframework

Вопрос:

Мне нужно предоставить некоторый контент из действия в виде файла: по сути, я создаю содержимое CSV «на лету» и отправляю его клиенту.

Я не могу сделать это с помощью sendFile, поскольку файл на самом деле не существует; Я пытался использовать фрагментированную передачу, но получаю очень медленный ответ (на локальном хосте я получил файл со скоростью около 100 КБ / с, что, на мой взгляд, действительно странно).

Есть ли у меня способ установить тип содержимого и написать ответ «построчно», без необходимости указывать длину содержимого «априори»?

Комментарии:

1. Это для Java или Scala?

2. Scala! Извините, я забыл написать это выше…

Ответ №1:

Вот один из способов, использующий простой предопределен Enumerator , который будет выдавать ответ из байтов, записанных в OutputStream :

 def csv = Action {
    val enumerator = Enumerator.outputStream { out =>
       out.write(...)

       // Keep writing to the Enumerator

       out.close()
    }

    Ok.chunked(enumerator.andThen(Enumerator.eof)).withHeaders(
       "Content-Type" -> "text/csv", 
       "Content-Disposition" -> s"attachment; filename=test.csv"
    )
}
 

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

Обновить:

После еще одного тестирования кажется, что размер Byte массивов, которые вы записываете OutputStream , имеет огромное значение в пропускной способности.

Используя этот пример потока:

 val s = Stream.continually(0.toByte)
 

Запись в виде фрагментов размером 1 КБ в OutputStream подобное это привело к пропускной способности 6 МБ / с.:

 (0 until 1024*1024).foreach{i =>
    out.write(s.take(1024).toArray)
}
 

Однако, если я записываю только 10 байт за раз, пропускная способность замедляется до менее 100 КБ / с. Итак, мое предложение использовать этот метод для записи CSV в фрагментированной форме заключалось бы в том, чтобы записывать несколько строк за раз OutputStream , а не по одной строке за раз.

Комментарии:

1. Я думаю, что я уже пробовал это, и по какой-то причине файл загружался довольно медленно… Я попробую сделать это как можно скорее и добавлю больше комментариев.

2. Вы уверены, что не просто медленно генерируете файл? Я смог очень быстро обслуживать большие файлы, подобные этому.

3. Хорошо, я попробовал, и фрагментированный ответ, похоже, является самой медленной частью: я регистрирую момент непосредственно перед закрытием выходного потока, и это происходит очень быстро, но затем для воспроизведения требуется 10 секунд, чтобы отправить результирующие данные клиенту (1 МБ при 100 КБ / с). Есть идеи?

4. @AlessandroSivieri Я обновил свой ответ, включив в него некоторые новые материалы, которые, вероятно, решат вашу проблему.