zio-http icon indicating copy to clipboard operation
zio-http copied to clipboard

Cannot read twice a response body when backed by Netty

Open thierry-st opened this issue 1 year ago • 2 comments

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.

thierry-st avatar Sep 29 '24 10:09 thierry-st

@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?

987Nabil avatar Sep 29 '24 17:09 987Nabil

@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
}

kyri-petrou avatar Sep 30 '24 03:09 kyri-petrou

You have to use the batched API if you want to read the body twice. I don't think we can fix this.

jdegoes avatar Nov 09 '24 02:11 jdegoes