HttpClient.withBytesBody ignores byteOffset/byteLength and sends the whole backing buffer
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 86/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 静か
- 技術スタック
- javascript
- 領域
- api
調査の方向性
gren-lang/node/src/Gren/Kernel/HttpClient.js の _HttpClient_prepBytes から開始し、_HttpClient_extractRequestBody とストリーミングのチャンク経路を追って、両方がそれを使用していることを確認します。プールされた小さいペイロードのケースを含め、再現用の run.sh を実行します。/relay が送信バイト数と受信バイト数が等しいと報告し、既存の HttpServer の処理が変更されていなければ完了です。
索引モデルが issue の本文から書いたものです。
説明
HttpClient.withBytesBody ignores byteOffset/byteLength and sends the whole backing buffer
_HttpClient_prepBytes builds its Uint8Array from bytes.buffer alone:
// gren-lang/node/src/Gren/Kernel/HttpClient.js
var _HttpClient_prepBytes = function (bytes) {
return new Uint8Array(bytes.buffer);
};
A Bytes value is a DataView, and a DataView is a window onto an
ArrayBuffer — byteOffset and byteLength are part of the value. Dropping
them sends everything in the backing buffer instead of the bytes the caller
asked for.
HttpServer builds a request body withBuffer.concat, and Node serves any
allocation under 4096 bytes out of a shared
8 KiB pool, so request.body is essentially always a view. Forwarding one
with withBytesBody puts 8192 bytes on the wire regardless of how few were
received, and the extra bytes are whatever else is in the pool — other requests'
data, in a server that handles more than one.
- Package:
gren-lang/node(HttpClientkernel) - Versions: gren 0.6.6, gren-lang/core 7.4.2, gren-lang/node 6.1.3, Node.js
v25.1.0, Linux x86-64
Reproducing
A minimal reproduction is in the https://github.com/gilramir/gren-bug-reports
repo.
$ git clone https://github.com/gilramir/gren-bug-reports.git
$ cd gren-bug-reports/2026-08-16-withBytesBody
$ ./run.sh
src/Relay.gren is one ~160-line program serving three paths on :8086:
POST /echoreports how many bytes it received — the observer, nothing under
test;POST /relayforwards the request body to/echowithwithBytesBody;POST /relay-compactdoes the same after re-encoding the body through
Bytes.Encode.bytes, which allocates an exact-width buffer at offset zero.
Note that Bytes.length request.body already reports the right number — the
DataView's byteLength is intact, and every Gren-level operation agrees with
it. Only the kernel's new Uint8Array(bytes.buffer) disagrees, which is why
this cannot be caught from inside the language.
Bytes.Encode.encode (Bytes.Encode.bytes …) on the /relay-compact path is a
copy into a buffer the value owns, fixing the behavior.
Each path answers sent=<n> received=<n>.
=== POST /relay — withBytesBody on the request body (expected: sent == received)
8 bytes: sent=8 received=8192
100 bytes: sent=100 received=8192
4095 bytes: sent=4095 received=8192
4096 bytes: sent=4096 received=4096
5000 bytes: sent=5000 received=5000
=== POST /relay-compact — same, after re-encoding through Bytes.Encode.bytes
8 bytes: sent=8 received=8
100 bytes: sent=100 received=100
4095 bytes: sent=4095 received=4095
4096 bytes: sent=4096 received=4096
5000 bytes: sent=5000 received=5000
By hand:
gren make Relay --output relay
node relay &
curl -s --data-binary '12345678' localhost:8086/relay # sent=8 received=8192
curl -s --data-binary '12345678' localhost:8086/relay-compact # sent=8 received=8
Why the cutoff is at 4096
Node's Buffer.allocUnsafe serves requests smaller than Buffer.poolSize >>> 1
(4096, with the default 8192 pool) from a shared pool, and allocates
independently at or above it. run.sh prints this directly:
Buffer.poolSize = 8192 -> pooled when size < 4096
8 byteOffset 8 buffer.byteLength 8192 new Uint8Array(b.buffer).byteLength 8192
4095 byteOffset 16 buffer.byteLength 8192 new Uint8Array(b.buffer).byteLength 8192
4096 byteOffset 0 buffer.byteLength 4096 new Uint8Array(b.buffer).byteLength 4096
So the bug affects small payloads and spares large ones.
A test suite that exercises withBytesBody
with a Bytes.fromString literal will also miss it, because
Bytes.fromString produces a fresh exact-width buffer at offset zero.
Suggested fix
HttpServer already does this correctly;
_HttpServer_setBodyAsBytes passes all three arguments:
// gren-lang/node/src/Gren/Kernel/HttpServer.js
var _HttpServer_setBodyAsBytes = F2(function (data, res) {
let body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
res.write(body);
return res;
});
_HttpClient_prepBytes should match:
var _HttpClient_prepBytes = function (bytes) {
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
};
That one line covers both call sites: the one-shot body path
(_HttpClient_extractRequestBody, the BYTES case, line 323) and the streaming
chunk path (line 231) both go through prepBytes.
Those are the only two new Uint8Array( in gren-lang/node's kernels, and the
other one is HttpServer's correct version:
$ grep -rn 'new Uint8Array(' src/Gren/Kernel/
src/Gren/Kernel/HttpServer.js:84: let body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
src/Gren/Kernel/HttpClient.js:330: return new Uint8Array(bytes.buffer);
Related
The sibling report for another issue in 2026-08-16-flatten/ is the same mistake —
new Uint8Array(view.buffer), with the offset dropped — in gren-lang/core's
Bytes.flatten. It behaves differently there: flatten takes its length from
byteLength and only its contents from the wrong place, so the result is the
right size and the wrong bytes, which is harder to notice than this one. They
are independent fixes in different packages.
Worth knowing here because Bytes.flatten is the obvious way to copy a Bytes
before handing it to withBytesBody, and it does not work. Bytes.Encode.bytes
does, which is what /relay-compact uses.
- 主要言語
- JavaScript
- スター
- 13
- フォーク
- 6
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 3
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
gren-lang/node のほかの issue
-
breaking enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 38/100
-
breaking enhancement
難易度 3/5 1〜2日 初心者へのやさしさ 42/100
-
breaking bug
難易度 3/5 1〜2日 初心者へのやさしさ 64/100
-
enhancement
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
-
enhancement
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
似ている issue
-
[Bug] Composer can submit an IME confirmation when keyCode is 229 but isComposing is false対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
apache/rocketmq-dashboard#6067 ·
メンテナーはふだん 4 日以内に返信
-
severity: low
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
luainkernel/lunatik#1853 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信
-
factory-active factory-automatic harness/claude-code task-identify-harness-labels-done task-identify-issue-type-done
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
メンテナーはふだん 1 日以内に返信