Converting a FetchHttpClient response stream drops workerd's known-length capability
Maintainer thường phản hồi trong vòng 1 ngày
Một pull request liên quan đã được merge.
- #9673 của @effect-bot — đã merge
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 45/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- typescript
- Lĩnh vực
- backend
Hướng nghiên cứu
Start by reproducing the failure with FetchHttpClient, HttpClientResponse.fromWeb(), Stream.fromReadableStream, and Stream.toReadableStreamEffect() in the issue's Vitest example. Inspect how the response body is wrapped and converted, then verify that direct conversion preserves the source body's known-length capability without changing arbitrary transformed streams. Done means the shown R2 write succeeds and the regression case passes.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
What version of Effect is running?
- Effect
4.0.0-rc.112 @cloudflare/vitest-pool-workers0.22.0- Miniflare
5.20260815.0-alpha/ workerd1.20260815.1
What steps can reproduce the bug?
import { env } from "cloudflare:workers"
import { Effect, Stream } from "effect"
import {
FetchHttpClient,
HttpClient,
HttpClientRequest
} from "effect/unstable/http"
import { expect, it, vi } from "vitest"
it("drops the native body's known length after Effect conversion", async () => {
const bytes = new Uint8Array([1, 2, 3])
const url = "https://example.com/archive.tgz"
vi.stubGlobal("fetch", () => Promise.resolve(new Response(bytes)))
const nativeResponse = await fetch(url)
if (nativeResponse.body === null) throw new Error("Missing response body")
await expect(
env.BACKUP.put("native.tgz", nativeResponse.body)
).resolves.not.toBeNull()
const program = Effect.gen(function*() {
const request = HttpClientRequest.get(url)
const response = yield* HttpClient.execute(request)
const body = yield* Stream.toReadableStreamEffect(response.stream)
yield* Effect.tryPromise(() => env.BACKUP.put("effect.tgz", body))
}).pipe(Effect.provide(FetchHttpClient.layer))
await expect(Effect.runPromise(program)).rejects.toThrow(
"Provided readable stream must have a known length"
)
})
The R2 operation rejects with:
Provided readable stream must have a known length
(request/response body or readable half of FixedLengthStream)
In the same runtime, the native response-body write succeeds. FixedLengthStream.readable also succeeds.
What is the expected behavior?
For the direct conversion shown above, Stream.toReadableStreamEffect(response.stream) should produce a Web stream that retains the source body's runtime-known length and is accepted by R2.
What do you see instead?
The public HttpClientResponse body API is an Effect Stream. Converting it with Stream.toReadableStreamEffect() creates a generic Web ReadableStream, which does not retain the runtime-internal identity-length capability that R2 queries.
The body bytes are unchanged, but R2 rejects the converted stream. This prevents direct streaming through HttpClientResponse.stream without reconstructing the length or buffering the body.
Additional information
HttpClientResponse.fromWeb() keeps the original Response in a private field. HttpClientResponse.stream wraps Response.body with Stream.fromReadableStream. The HTTP client also adds interruption and finalization wrappers. Stream.toReadableStreamEffect() then creates a new Web stream, so workerd can no longer query the source body's identity length.
Cloudflare documents ReadableStream as a valid R2Bucket.put() value, but workerd's R2 implementation requires a stream to report an identity length before it starts the write. This check is in workerd itself, not only in Miniflare.
This request concerns direct conversion of response.stream. An arbitrary transformed, filtered, concatenated, or generated Effect stream does not necessarily have a known length.
A caller can read Content-Length, create a Cloudflare FixedLengthStream, and pump the Effect stream into it with Sink.fromWritableStream. This is a platform-specific manual bridge, and it works only when the header is present and equals the number of bytes emitted by the stream. Buffering through response.arrayBuffer is another workaround when the body fits safely in memory.
Our workaround fetches archives with the platform fetch() and passes the original Response.body to R2. Effect still owns retries, errors, and timeouts around that operation. Packument requests continue to use FetchHttpClient.
- Ngôn ngữ chính
- TypeScript
- Star
- 16.7k
- Fork
- 808
- Merge trung bình
- 10 giờ 49 phút
- Pull request đã merge (30 ngày)
- 846
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của Effect-TS/effect
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
Maintainer thường phản hồi trong vòng 1 ngày
-
add first-class support for skills extension in McpServerCó thể đã có người làm @effect-bot đã nhận hôm nay. Đang mởenhancement
Effect-TS/effect#10016 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
SqlEventJournal: remote catch-up exceeds SQLite's bind-variable limitCó thể đã có người làm @effect-bot đã nhận hôm nay. Đang mở
Effect-TS/effect#10012 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 75/100
Maintainer thường phản hồi trong vòng 1 ngày
-
AI providers drop the cause of a transport failure from `NetworkError`Có thể đã có người làm @effect-bot đã nhận hôm nay. Đang mở
Effect-TS/effect#9983 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của Effect-TS/effect
Issue tương tự
-
agent-reported area/react bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Nimblesite/SharpLsp#510 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
backend refactor
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 73/100
NIAEFEUP/tts#927 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
callstackincubator/react-native-harness#296 ·
Maintainer thường phản hồi trong vòng 1 ngày