zio / zio-http

A next-generation Scala framework for building scalable, correct, and efficient HTTP clients and servers
https://zio.dev/zio-http
Apache License 2.0
785 stars 396 forks source link

Cannot read twice a response body when backed by Netty #3184

Open thierry-st opened 22 hours ago

thierry-st commented 22 hours ago

Describe the bug When provided with the default Client, the following effect fails with an "IllegalStateException: Cannot connect twice":

val requestBodyAsChunk: RIO[Client, Chunk[Byte]] = ZIO.scoped {
  for {
    client <- ZIO.service[Client]
    response <- client(Request.get("http://jsonplaceholder.typicode.com/todos"))
    _ <- response.body.asChunk
    bodyAsChunk <- response.body.asChunk
  } yield bodyAsChunk
}

This is due to the zio.http.netty.AsyncBodyReader.connect method being called twice, the second time with an illegal state.

To Reproduce Run this Scastie

Expected behaviour The fact that the response body uses zio.http.netty.AsyncBodyReader should be an implementation detail, and calling zio.http.Body.asChunk should not be stateful.

987Nabil commented 15 hours ago

@kyri-petrou I think would could for the aggregation operations (asChunk/asArray) in the netty body cache the result internally? But I don't think we can do this for streaming, since it would defy the purpose of streaming it (not keeping all in memory). wdyt?

kyri-petrou commented 5 hours ago

@987Nabil that's one option. Not exactly a fix, but this can also be achieved by using the batched API. The following works as expected:

val requestBodyAsChunk: RIO[Client, Chunk[Byte]] = {
  for {
    client <- ZIO.service[Client]
    response <- client.batched(Request.get("http://jsonplaceholder.typicode.com/todos"))
    _ <- response.body.asChunk
    bodyAsChunk <- response.body.asChunk
  } yield bodyAsChunk
}