Skip to content

Unable to proxy ReadableStream to client #53

@Ehesp

Description

@Ehesp

Hey all,

I'm experiencing an issue streaming results from a container to a client.

I have a reproduction of the following issue with instructions: https://github.com/Ehesp/cf-containers-repro

My container (a Bun server) had an endpoint which streams back a 10 messages every second:

app.post("/stream", async (c) => {
  let remaining = 10;
  const stream = new ReadableStream({
    async start(controller) {
      while (remaining > 0) {
        console.log("enqueue:", remaining);
        controller.enqueue(
          new TextEncoder().encode(`Hello, world! ${remaining}\n\n`)
        );
        await new Promise((resolve) => setTimeout(resolve, 1000));
        remaining--;
      }

      console.log("closing stream");
      controller.close();
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/plain",
      "Transfer-Encoding": "chunked",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
      "Content-Encoding": "identity",
    },
  });
});

Inside the DO, this is proxied via containerFetch:

  async stream() {
    const response = await this.containerFetch("http://container/stream", {
      method: "POST",
      body: JSON.stringify({
        message: "Hello, world!",
      }),
    });

    return new Response(response.body, {
      headers: response.headers,
      status: response.status,
      statusText: response.statusText,
    });
  }

My worker then proxies:

app.post("/stream", async (c) => {
  const container = getContainer(c.env.MY_CONTAINER);
  const response = await container.stream();

  return new Response(response.body, {
    headers: response.headers,
    status: response.status,
    statusText: response.statusText,
  });
});

My Node client script:

async function main() {
  const response = await fetch("http://localhost:8787/stream", {
    duplex: "half",
    method: "POST",
    body: JSON.stringify({}),
    headers: {
      "Accept-Encoding": "identity", // Tell the worker to not compress the stream
    },
  });

  console.log("client headers", Object.fromEntries(response.headers));

  for await (const chunk of response.body) {
    console.log("Client>", new TextDecoder().decode(chunk));
  }
}

main().catch((error) => {
  console.error(error);
});

I constantly get inconsistent results, either the whole thing streams (less often) or some results:

Run 1:

client headers {
  'cache-control': 'no-cache',
  'content-encoding': 'identity',
  'content-type': 'text/plain',
  date: 'Thu, 17 Jul 2025 08:54:32 GMT',
  'transfer-encoding': 'chunked'
}
Client> Hello, world! 10
Client> Hello, world! 9

Run 2:

client headers {
  'cache-control': 'no-cache',
  'content-encoding': 'identity',
  'content-type': 'text/plain',
  date: 'Thu, 17 Jul 2025 08:58:56 GMT',
  'transfer-encoding': 'chunked'
}
Client> Hello, world! 10
Client> Hello, world! 9
Client> Hello, world! 8
Client> Hello, world! 7
Client> Hello, world! 6
Client> Hello, world! 5
Client> Hello, world! 4

I'm totally unsure where the issue lies, but I've not had this issue when the origin isn't a Cloudflare Container.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions