Как передать тело ответа в Кваркусе с помощью Котлина

#java #kotlin #kotlin-coroutines #quarkus #coroutine

Вопрос:

Цель

Создайте службу, которая передает zip-файл, созданный на лету. ZIP-файл содержит файлы, загруженные по протоколу HTTP. Файлы достаточно велики, чтобы не помещаться в оперативной памяти. Следует избегать использования файловой системы, так как в этом нет необходимости.

Проблемы

JSX-RS определяет ответ на поток и, похоже, является ответом. Однако это не поддерживается системой Quarkus Reactive. Он просто возвращается ZipResource$ZipStreamingOutput@50a05849 в качестве тела ответа.

Использование Multi (из Mutiny) в качестве возвращаемого результата не позволяет связать его с сопрограммами Kotlin, поскольку сопрограммы требуют вызова их из функции suspend. И если метод помечен suspend , то он просто возвращается io.smallrye.mutiny.operators.multi.builders.EmitterBasedMulti@24ac00e7 как тело функции. Пример кода:

 @GET
@Produces("application/zip")
suspend fun download(): Multi<String> {
    return flow { emit("hello") }.asMulti()
}
 

Кроме того, даже если бы это сработало, я не вижу возможности указать заголовки ответов.

Критерий

  • Неблокирующая потоковая передача, так что параллелизм не ограничен количеством потоков
  • Сопрограммы Kotlin предпочтительнее, поскольку они являются родными для языка, а не для конкретной платформы

Контекст

  • Котлин 1.5.30
  • Кваркус 2.2.2.Финал

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

 import java.io.IOException
import java.io.OutputStream
import java.net.URL
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
import javax.ws.rs.WebApplicationException
import javax.ws.rs.core.Response
import javax.ws.rs.core.StreamingOutput

data class Entry(val url: String, val name: String)

@Path("/zip")
class ZipResource {
    @GET
    @Produces("application/zip")
    fun download(): Response? {
        val entries = listOf(
            Entry("http://link-to-a-source-file1", "file1.txt"),
            Entry("http://link-to-a-source-file2", "file2.txt"),
        )
        val contentDisposition = """attachment; filename="test.zip""""
        return Response.ok(ZipStreamingOutput(entries)).header("Content-Disposition", contentDisposition).build()
    }

    class ZipStreamingOutput(private val entries: List<Entry>) : StreamingOutput {
        @Throws(IOException::class, WebApplicationException::class)
        override fun write(output: OutputStream) {
            val zipOut = ZipOutputStream(output)
            for (entry in entries) {
                zipOut.putNextEntry(ZipEntry(entry.name))
                val downloadStream = URL(entry.url).openStream()
                downloadStream.transferTo(zipOut)
                downloadStream.close()
            }
            zipOut.close()
        }
    }
}
 

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

1. Интересный вариант использования. В настоящее время у нас есть простой способ сделать это. Пожалуйста, откройте проблему в репозитории Quarkus Github, чтобы мы могли рассмотреть возможность ее добавления.