@sproutboat/toolchain 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/acquire.test.ts +3 -1
- package/src/patch.test.ts +92 -1
- package/src/patch.ts +167 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ba5a6a0: Keep the normal native-fetch request handler outside the optional direct R2 transfer compiler guard.
|
|
8
|
+
|
|
9
|
+
## 0.4.6
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a357b91: Compile direct R2 transfer support only into artifacts that declare an R2 binding.
|
|
14
|
+
|
|
3
15
|
## 0.4.5
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/acquire.test.ts
CHANGED
|
@@ -28,7 +28,9 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
28
28
|
" }\n" +
|
|
29
29
|
" return -1;\n" +
|
|
30
30
|
"}\n",
|
|
31
|
-
"compiler/index.js":
|
|
31
|
+
"compiler/index.js":
|
|
32
|
+
" '-xc', '-', '-c',\n uSocketsArchive,\n '-lm'\n" +
|
|
33
|
+
" '-I', `${uwsDir}/uSockets/src`,\n",
|
|
32
34
|
"compiler/uwebsockets.js":
|
|
33
35
|
"static const size_t REQUEST_BODY_MAX_BYTES = 1024u * 1024u;\n" +
|
|
34
36
|
"int porf_native_fetch_read_value(struct jsval value, const char** out_buf, size_t* out_len, char** out_owned);\n" +
|
package/src/patch.test.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { EMPTY_BINDINGS, wrapNativeFetchHandler } from "../../runtime/src/wrap";
|
|
6
|
+
import { patchCompilerArgs, patchRenderJs, patchUwebsockets } from "./patch";
|
|
6
7
|
|
|
7
8
|
// The parts of Porffor's compiler/render.js the patch anchors to.
|
|
8
9
|
const RENDER = `void porf_native_fetch_runtime_init(void) {
|
|
@@ -36,6 +37,16 @@ int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_
|
|
|
36
37
|
}
|
|
37
38
|
`;
|
|
38
39
|
|
|
40
|
+
const COMPILER_INDEX = `const compileArgs = [
|
|
41
|
+
'-xc', '-', '-c',
|
|
42
|
+
];
|
|
43
|
+
const linkArgs = [
|
|
44
|
+
'-I', \`${"${uwsDir}"}/uSockets/src\`,
|
|
45
|
+
uSocketsArchive,
|
|
46
|
+
'-lm'
|
|
47
|
+
];
|
|
48
|
+
`;
|
|
49
|
+
|
|
39
50
|
// Porffor's compiler/uwebsockets.js, trimmed to the parts the patch touches:
|
|
40
51
|
// body limit (#56), the #156 status-line switch, #163's collect_headers, and
|
|
41
52
|
// #176's read_value forward decl / is_forbidden_response_header /
|
|
@@ -230,6 +241,86 @@ test("a drifted shim fails loudly instead of silently no-op'ing", async () => {
|
|
|
230
241
|
}
|
|
231
242
|
});
|
|
232
243
|
|
|
244
|
+
test("native capability flags reach both the generated C and socket-shim C++ compiles", async () => {
|
|
245
|
+
const root = await mkdtemp(join(tmpdir(), "sb-patch-compiler-"));
|
|
246
|
+
try {
|
|
247
|
+
await mkdir(join(root, "compiler"), { recursive: true });
|
|
248
|
+
await writeFile(join(root, "compiler/index.js"), COMPILER_INDEX);
|
|
249
|
+
await patchCompilerArgs(root);
|
|
250
|
+
const once = await readFile(join(root, "compiler/index.js"), "utf8");
|
|
251
|
+
expect(once.match(/SB_EXTRA_CFLAGS/g)).toHaveLength(4);
|
|
252
|
+
expect(once).toContain("SB_UWS_EXTRA_CFLAGS");
|
|
253
|
+
await patchCompilerArgs(root);
|
|
254
|
+
expect(await readFile(join(root, "compiler/index.js"), "utf8")).toBe(once);
|
|
255
|
+
} finally {
|
|
256
|
+
await rm(root, { recursive: true, force: true });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("R2 builds define every direct transfer symbol the gated socket shim calls", async () => {
|
|
261
|
+
const root = await shimDir();
|
|
262
|
+
try {
|
|
263
|
+
await patchUwebsockets(root);
|
|
264
|
+
const shim = await readFile(join(root, "compiler/uwebsockets.js"), "utf8");
|
|
265
|
+
const abi = shim.match(/extern "C" \{([\s\S]*?)\n\}/)?.[1];
|
|
266
|
+
if (abi === undefined) throw new Error("patched shim did not contain the R2 transfer ABI");
|
|
267
|
+
const required = [...abi.matchAll(/\b(sb_r2_transfer_[a-z_]+)\s*\(/g)].map((match) => match[1]);
|
|
268
|
+
const wrapped = wrapNativeFetchHandler(
|
|
269
|
+
"export default { fetch() { return new Response('ok'); } }",
|
|
270
|
+
"",
|
|
271
|
+
{},
|
|
272
|
+
{ ...EMPTY_BINDINGS, r2: ["OBJECTS"] },
|
|
273
|
+
8080,
|
|
274
|
+
"2026-08-26",
|
|
275
|
+
"test",
|
|
276
|
+
undefined,
|
|
277
|
+
"broker",
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
for (const symbol of new Set(required)) {
|
|
281
|
+
expect(wrapped).toMatch(new RegExp(`\\b${symbol}\\s*\\([^)]*\\)\\s*\\{`));
|
|
282
|
+
}
|
|
283
|
+
} finally {
|
|
284
|
+
await rm(root, { recursive: true, force: true });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("the direct R2 socket path is excluded unless the compiler enables it", async () => {
|
|
289
|
+
const root = await shimDir();
|
|
290
|
+
try {
|
|
291
|
+
await patchUwebsockets(root);
|
|
292
|
+
const shim = await readFile(join(root, "compiler/uwebsockets.js"), "utf8");
|
|
293
|
+
expect(shim).toContain("#ifdef SB_R2_TRANSFER");
|
|
294
|
+
expect(shim).toContain("#endif\n __porffor_js_enter();");
|
|
295
|
+
} finally {
|
|
296
|
+
await rm(root, { recursive: true, force: true });
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("an existing compiler cache upgrades to the gated R2 socket path", async () => {
|
|
301
|
+
const root = await shimDir();
|
|
302
|
+
try {
|
|
303
|
+
await patchUwebsockets(root);
|
|
304
|
+
const file = join(root, "compiler/uwebsockets.js");
|
|
305
|
+
const current = await readFile(file, "utf8");
|
|
306
|
+
const legacy = current
|
|
307
|
+
.replace('#ifdef SB_R2_TRANSFER\nextern "C" {', 'extern "C" {')
|
|
308
|
+
.replace("\n#endif\nstatic void on_request", "\nstatic void on_request")
|
|
309
|
+
.replace(
|
|
310
|
+
"#ifdef SB_R2_TRANSFER\n if (try_handle_r2_transfer(res, req, method)) return;\n#endif\n",
|
|
311
|
+
" if (try_handle_r2_transfer(res, req, method)) return;\n",
|
|
312
|
+
);
|
|
313
|
+
await writeFile(file, legacy);
|
|
314
|
+
|
|
315
|
+
await patchUwebsockets(root);
|
|
316
|
+
const upgraded = await readFile(file, "utf8");
|
|
317
|
+
expect(upgraded).toContain('#ifdef SB_R2_TRANSFER\nextern "C" {');
|
|
318
|
+
expect(upgraded).toContain("#endif\n __porffor_js_enter();");
|
|
319
|
+
} finally {
|
|
320
|
+
await rm(root, { recursive: true, force: true });
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
233
324
|
test("#165: render.js routes console output to stderr, unbuffered, idempotently", async () => {
|
|
234
325
|
const root = await mkdtemp(join(tmpdir(), "sb-patch-render-"));
|
|
235
326
|
try {
|
package/src/patch.ts
CHANGED
|
@@ -167,6 +167,14 @@ const CFLAGS_INJECT =
|
|
|
167
167
|
" ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
|
|
168
168
|
const CFLAGS_MARKER = "SB_EXTRA_CFLAGS";
|
|
169
169
|
|
|
170
|
+
/** The native-fetch socket shim is a separately compiled C++ translation unit.
|
|
171
|
+
* It needs the same optional capability flags as the generated C object. */
|
|
172
|
+
const UWS_CFLAGS_ANCHOR = " '-I', `${uwsDir}/uSockets/src`,\n";
|
|
173
|
+
const UWS_CFLAGS_INJECT =
|
|
174
|
+
" // SB_UWS_EXTRA_CFLAGS\n" +
|
|
175
|
+
" ...(process.env.SB_EXTRA_CFLAGS ? process.env.SB_EXTRA_CFLAGS.split(' ').filter(Boolean) : []),\n";
|
|
176
|
+
const UWS_CFLAGS_MARKER = "SB_UWS_EXTRA_CFLAGS";
|
|
177
|
+
|
|
170
178
|
/**
|
|
171
179
|
* #56 — make the inbound request-body limit configurable.
|
|
172
180
|
*
|
|
@@ -416,8 +424,10 @@ const WRITE_RESPONSE_MARKER = "porf_native_fetch_read_raw_bytes(body_value";
|
|
|
416
424
|
// #195: reserve a direct-transfer ticket before native-fetch allocates the
|
|
417
425
|
// ordinary in-memory PendingRequest. uWS then gives each wire chunk directly
|
|
418
426
|
// to the runtime C ABI, which owns the temporary object file.
|
|
419
|
-
const R2_TRANSFER_ANCHOR =
|
|
420
|
-
|
|
427
|
+
const R2_TRANSFER_ANCHOR =
|
|
428
|
+
"static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {\n";
|
|
429
|
+
const R2_TRANSFER_INJECT = `#ifdef SB_R2_TRANSFER
|
|
430
|
+
extern "C" {
|
|
421
431
|
struct sb_r2_transfer_ctx;
|
|
422
432
|
int sb_r2_transfer_open(const char*, const char*, size_t, sb_r2_transfer_ctx**);
|
|
423
433
|
int sb_r2_transfer_write(sb_r2_transfer_ctx*, const char*, size_t);
|
|
@@ -532,13 +542,17 @@ static bool try_handle_r2_transfer(uWS::HttpResponse<false>* res, uWS::HttpReque
|
|
|
532
542
|
});
|
|
533
543
|
return true;
|
|
534
544
|
}
|
|
545
|
+
#endif
|
|
535
546
|
static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {
|
|
536
547
|
`;
|
|
537
548
|
const R2_TRANSFER_MARKER = "sb_r2_transfer_path(std::string_view path";
|
|
538
|
-
const R2_TRANSFER_CALL_ANCHOR =
|
|
549
|
+
const R2_TRANSFER_CALL_ANCHOR =
|
|
550
|
+
" __porffor_js_enter();\n const i32 url_ptr = alloc_request_url(req);";
|
|
539
551
|
const R2_TRANSFER_CALL_INJECT =
|
|
540
|
-
" if (try_handle_r2_transfer(res, req, method)) return;\n" +
|
|
552
|
+
"#ifdef SB_R2_TRANSFER\n if (try_handle_r2_transfer(res, req, method)) return;\n#endif\n" +
|
|
553
|
+
R2_TRANSFER_CALL_ANCHOR;
|
|
541
554
|
const R2_TRANSFER_CALL_MARKER = "try_handle_r2_transfer(res, req, method)";
|
|
555
|
+
const R2_GATE_MARKER = "#ifdef SB_R2_TRANSFER";
|
|
542
556
|
const R2_DOWNLOAD_CACHE_MARKER = "sb_r2_transfer_download_v1";
|
|
543
557
|
const R2_RANGE_CACHE_MARKER = "sb_r2_transfer_range_v1";
|
|
544
558
|
const R2_CONDITIONAL_CACHE_MARKER = "sb_r2_transfer_conditional_v1";
|
|
@@ -552,7 +566,8 @@ const R2_DOWNLOAD_DECL_INJECT =
|
|
|
552
566
|
" const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n" +
|
|
553
567
|
" size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);\n" +
|
|
554
568
|
" void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);\n}";
|
|
555
|
-
const R2_DOWNLOAD_BRANCH_ANCHOR =
|
|
569
|
+
const R2_DOWNLOAD_BRANCH_ANCHOR =
|
|
570
|
+
' if (method != "PUT") { respond_with_error(res, "405 Method Not Allowed", "R2 transfer requires PUT", true); return true; }';
|
|
556
571
|
const R2_DOWNLOAD_BRANCH_INJECT = ` // sb_r2_transfer_download_v1
|
|
557
572
|
// sb_r2_transfer_range_v1
|
|
558
573
|
// sb_r2_transfer_range_header_v2
|
|
@@ -645,8 +660,18 @@ const UWS_EDITS = [
|
|
|
645
660
|
WRITE_RESPONSE_INJECT,
|
|
646
661
|
"detect x-sb-raw-body, steer the body read (#176)",
|
|
647
662
|
],
|
|
648
|
-
[
|
|
649
|
-
|
|
663
|
+
[
|
|
664
|
+
R2_TRANSFER_MARKER,
|
|
665
|
+
R2_TRANSFER_ANCHOR,
|
|
666
|
+
R2_TRANSFER_INJECT,
|
|
667
|
+
"native direct R2 transfer ingress (#195)",
|
|
668
|
+
],
|
|
669
|
+
[
|
|
670
|
+
R2_TRANSFER_CALL_MARKER,
|
|
671
|
+
R2_TRANSFER_CALL_ANCHOR,
|
|
672
|
+
R2_TRANSFER_CALL_INJECT,
|
|
673
|
+
"direct R2 transfer dispatch (#195)",
|
|
674
|
+
],
|
|
650
675
|
] as const;
|
|
651
676
|
|
|
652
677
|
/** The uWebSockets shim source: body limit + the #156 status-line fix. Exported for tests. */
|
|
@@ -654,18 +679,41 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
654
679
|
const file = resolve(root, "compiler/uwebsockets.js");
|
|
655
680
|
let src = await readFile(file, "utf8");
|
|
656
681
|
let changed = false;
|
|
682
|
+
// Existing shared compiler caches already contain the direct-transfer patch.
|
|
683
|
+
// Upgrade them in place so a cache built before #202 does not keep linking
|
|
684
|
+
// the R2 path into every subsequent artifact.
|
|
685
|
+
if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_GATE_MARKER)) {
|
|
686
|
+
const oldOpen = 'extern "C" {\n struct sb_r2_transfer_ctx;';
|
|
687
|
+
const newOpen = '#ifdef SB_R2_TRANSFER\nextern "C" {\n struct sb_r2_transfer_ctx;';
|
|
688
|
+
const oldClose = `}\n${R2_TRANSFER_ANCHOR}`;
|
|
689
|
+
const newClose = `}\n#endif\n${R2_TRANSFER_ANCHOR}`;
|
|
690
|
+
const oldCall =
|
|
691
|
+
" if (try_handle_r2_transfer(res, req, method)) return;\n" + R2_TRANSFER_CALL_ANCHOR;
|
|
692
|
+
if (!src.includes(oldOpen) || !src.includes(oldClose) || !src.includes(oldCall))
|
|
693
|
+
throw new Error(
|
|
694
|
+
`could not gate Porffor's direct R2 transfer bridge: anchor not found in ${file}.`,
|
|
695
|
+
);
|
|
696
|
+
src = src
|
|
697
|
+
.replace(oldOpen, newOpen)
|
|
698
|
+
.replace(oldClose, newClose)
|
|
699
|
+
.replace(oldCall, R2_TRANSFER_CALL_INJECT);
|
|
700
|
+
changed = true;
|
|
701
|
+
}
|
|
657
702
|
// Upgrade already-patched caches from the initial upload-only bridge. New
|
|
658
703
|
// caches receive the full bridge below; old ones need declarations plus the
|
|
659
704
|
// GET dispatch grafted onto their existing transfer handler.
|
|
660
705
|
if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
661
706
|
if (!src.includes(R2_DOWNLOAD_DECL_ANCHOR) || !src.includes(R2_DOWNLOAD_BRANCH_ANCHOR))
|
|
662
|
-
throw new Error(
|
|
707
|
+
throw new Error(
|
|
708
|
+
`could not upgrade Porffor's direct R2 transfer bridge: anchor not found in ${file}.`,
|
|
709
|
+
);
|
|
663
710
|
src = src.replace(R2_DOWNLOAD_DECL_ANCHOR, R2_DOWNLOAD_DECL_INJECT);
|
|
664
711
|
src = src.replace(R2_DOWNLOAD_BRANCH_ANCHOR, R2_DOWNLOAD_BRANCH_INJECT);
|
|
665
712
|
changed = true;
|
|
666
713
|
}
|
|
667
714
|
if (src.includes(R2_DOWNLOAD_CACHE_MARKER) && !src.includes(R2_RANGE_CACHE_MARKER)) {
|
|
668
|
-
const stateAnchor =
|
|
715
|
+
const stateAnchor =
|
|
716
|
+
" struct Download { sb_r2_transfer_ctx* ctx; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, sb_r2_transfer_download_size(download)});";
|
|
669
717
|
const range = ` const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
670
718
|
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
671
719
|
const std::string_view range = req->getHeader("range");
|
|
@@ -681,18 +729,30 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
681
729
|
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
682
730
|
}
|
|
683
731
|
`;
|
|
684
|
-
const state =
|
|
685
|
-
|
|
686
|
-
|
|
732
|
+
const state =
|
|
733
|
+
" struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };\n auto state = std::make_shared<Download>(Download{download, first, last - first + 1});";
|
|
734
|
+
if (!src.includes(stateAnchor))
|
|
735
|
+
throw new Error(
|
|
736
|
+
`could not upgrade Porffor's direct R2 range bridge: anchor not found in ${file}.`,
|
|
737
|
+
);
|
|
738
|
+
src = src.replace(
|
|
739
|
+
" // sb_r2_transfer_download_v1\n",
|
|
740
|
+
" // sb_r2_transfer_download_v1\n // sb_r2_transfer_range_v1\n",
|
|
741
|
+
);
|
|
687
742
|
src = src.replace(stateAnchor, range + state);
|
|
688
|
-
src = src.replace(
|
|
743
|
+
src = src.replace(
|
|
744
|
+
"sb_r2_transfer_download_read(state->ctx, (size_t)offset, chunk, sizeof(chunk))",
|
|
745
|
+
"sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk))",
|
|
746
|
+
);
|
|
689
747
|
changed = true;
|
|
690
748
|
}
|
|
691
749
|
if (src.includes(R2_RANGE_CACHE_MARKER) && !src.includes(R2_CONDITIONAL_CACHE_MARKER)) {
|
|
692
750
|
const declaration = " size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n";
|
|
693
|
-
const declarationReplacement =
|
|
751
|
+
const declarationReplacement =
|
|
752
|
+
declaration + " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n";
|
|
694
753
|
const anchor = " const uintmax_t full_size = sb_r2_transfer_download_size(download);\n";
|
|
695
|
-
const inject =
|
|
754
|
+
const inject =
|
|
755
|
+
` // sb_r2_transfer_conditional_v1
|
|
696
756
|
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
697
757
|
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
698
758
|
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
@@ -700,7 +760,10 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
700
760
|
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;
|
|
701
761
|
}
|
|
702
762
|
` + anchor;
|
|
703
|
-
if (!src.includes(declaration) || !src.includes(anchor))
|
|
763
|
+
if (!src.includes(declaration) || !src.includes(anchor))
|
|
764
|
+
throw new Error(
|
|
765
|
+
`could not upgrade Porffor's direct R2 conditional bridge: anchor not found in ${file}.`,
|
|
766
|
+
);
|
|
704
767
|
src = src.replace(declaration, declarationReplacement).replace(anchor, inject);
|
|
705
768
|
changed = true;
|
|
706
769
|
}
|
|
@@ -719,15 +782,28 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
719
782
|
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
720
783
|
if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
|
|
721
784
|
}`;
|
|
722
|
-
if (!src.includes(oldPump))
|
|
723
|
-
|
|
785
|
+
if (!src.includes(oldPump))
|
|
786
|
+
throw new Error(
|
|
787
|
+
`could not upgrade Porffor's direct R2 streaming bridge: anchor not found in ${file}.`,
|
|
788
|
+
);
|
|
789
|
+
src = src
|
|
790
|
+
.replace(
|
|
791
|
+
" // sb_r2_transfer_conditional_v1\n",
|
|
792
|
+
" // sb_r2_transfer_conditional_v1\n // sb_r2_transfer_streaming_v2\n",
|
|
793
|
+
)
|
|
794
|
+
.replace(oldPump, newPump);
|
|
724
795
|
changed = true;
|
|
725
796
|
}
|
|
726
797
|
// A short-lived v2 cache marker was written before its pump fix. Repair
|
|
727
798
|
// those caches as well as applying the current form to future upgrades.
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
799
|
+
if (
|
|
800
|
+
src.includes("state->base + offset") &&
|
|
801
|
+
src.includes("uintmax_t size; bool closed = false;")
|
|
802
|
+
) {
|
|
803
|
+
const oldState =
|
|
804
|
+
"struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; bool closed = false; };";
|
|
805
|
+
const newState =
|
|
806
|
+
"struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; };";
|
|
731
807
|
const oldPump = ` const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
|
|
732
808
|
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
733
809
|
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
@@ -741,7 +817,10 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
741
817
|
if (!result.first) { res->onWritable([pump, state](uintmax_t offset) { state->sent = offset; return (*pump)(); }); return false; }
|
|
742
818
|
state->sent += bytes;
|
|
743
819
|
}`;
|
|
744
|
-
if (!src.includes(oldState) || !src.includes(oldPump))
|
|
820
|
+
if (!src.includes(oldState) || !src.includes(oldPump))
|
|
821
|
+
throw new Error(
|
|
822
|
+
`could not repair Porffor's direct R2 streaming bridge: anchor not found in ${file}.`,
|
|
823
|
+
);
|
|
745
824
|
src = src.replace(oldState, newState).replace(oldPump, newPump);
|
|
746
825
|
changed = true;
|
|
747
826
|
}
|
|
@@ -756,37 +835,79 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
756
835
|
changed = true;
|
|
757
836
|
}
|
|
758
837
|
if (src.includes(R2_STREAMING_CACHE_MARKER) && !src.includes(R2_RANGE_HEADER_CACHE_MARKER)) {
|
|
759
|
-
const rangeHeader =
|
|
760
|
-
if (!src.includes(rangeHeader))
|
|
761
|
-
|
|
762
|
-
|
|
838
|
+
const rangeHeader = ' const std::string_view range = req->getHeader("range");\n';
|
|
839
|
+
if (!src.includes(rangeHeader))
|
|
840
|
+
throw new Error(
|
|
841
|
+
`could not upgrade Porffor's direct R2 range-header bridge: anchor not found in ${file}.`,
|
|
842
|
+
);
|
|
843
|
+
src = src.replace(
|
|
844
|
+
" // sb_r2_transfer_range_v1\n",
|
|
845
|
+
" // sb_r2_transfer_range_v1\n // sb_r2_transfer_range_header_v2\n",
|
|
846
|
+
);
|
|
847
|
+
src = src.replace(
|
|
848
|
+
rangeHeader,
|
|
849
|
+
' std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");\n',
|
|
850
|
+
);
|
|
763
851
|
changed = true;
|
|
764
852
|
}
|
|
765
|
-
const earlyConditionalHeaders =
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
853
|
+
const earlyConditionalHeaders =
|
|
854
|
+
' res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");\n';
|
|
855
|
+
if (
|
|
856
|
+
src.includes(earlyConditionalHeaders) &&
|
|
857
|
+
src.indexOf(earlyConditionalHeaders) <
|
|
858
|
+
src.indexOf(" const uintmax_t full_size = sb_r2_transfer_download_size(download);")
|
|
859
|
+
) {
|
|
860
|
+
const old304 =
|
|
861
|
+
' sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;';
|
|
862
|
+
const new304 =
|
|
863
|
+
' sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;';
|
|
864
|
+
const rangeEnd =
|
|
865
|
+
' res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);\n }\n';
|
|
866
|
+
if (!src.includes(old304) || !src.includes(rangeEnd))
|
|
867
|
+
throw new Error(
|
|
868
|
+
`could not repair Porffor's direct R2 response ordering: anchor not found in ${file}.`,
|
|
869
|
+
);
|
|
870
|
+
src = src
|
|
871
|
+
.replace(earlyConditionalHeaders, "")
|
|
872
|
+
.replace(old304, new304)
|
|
873
|
+
.replace(
|
|
874
|
+
rangeEnd,
|
|
875
|
+
rangeEnd +
|
|
876
|
+
' res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");\n',
|
|
877
|
+
);
|
|
772
878
|
changed = true;
|
|
773
879
|
}
|
|
774
880
|
if (src.includes("state->sent") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
775
881
|
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
776
|
-
const stateEnd = src.indexOf(
|
|
882
|
+
const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
|
|
777
883
|
const freshStart = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" struct Download {");
|
|
778
|
-
const freshEnd = R2_DOWNLOAD_BRANCH_INJECT.indexOf(
|
|
779
|
-
|
|
780
|
-
|
|
884
|
+
const freshEnd = R2_DOWNLOAD_BRANCH_INJECT.indexOf(
|
|
885
|
+
' res->writeHeader("Content-Type"',
|
|
886
|
+
freshStart,
|
|
887
|
+
);
|
|
888
|
+
if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0)
|
|
889
|
+
throw new Error(
|
|
890
|
+
`could not replace Porffor's direct R2 stream pump: anchor not found in ${file}.`,
|
|
891
|
+
);
|
|
892
|
+
src =
|
|
893
|
+
src.slice(0, stateStart) +
|
|
894
|
+
R2_DOWNLOAD_BRANCH_INJECT.slice(freshStart, freshEnd) +
|
|
895
|
+
src.slice(stateEnd);
|
|
781
896
|
changed = true;
|
|
782
897
|
}
|
|
783
898
|
if (src.includes("writable_offset") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
784
899
|
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
785
|
-
const stateEnd = src.indexOf(
|
|
900
|
+
const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
|
|
786
901
|
const freshStart = R2_TRANSFER_INJECT.indexOf(" struct Download {");
|
|
787
|
-
const freshEnd = R2_TRANSFER_INJECT.indexOf(
|
|
788
|
-
if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0)
|
|
789
|
-
|
|
902
|
+
const freshEnd = R2_TRANSFER_INJECT.indexOf(' res->writeHeader("Content-Type"', freshStart);
|
|
903
|
+
if (stateStart < 0 || stateEnd < 0 || freshStart < 0 || freshEnd < 0)
|
|
904
|
+
throw new Error(
|
|
905
|
+
`could not restore Porffor's direct R2 stream pump: anchor not found in ${file}.`,
|
|
906
|
+
);
|
|
907
|
+
src =
|
|
908
|
+
src.slice(0, stateStart) +
|
|
909
|
+
R2_TRANSFER_INJECT.slice(freshStart, freshEnd) +
|
|
910
|
+
src.slice(stateEnd);
|
|
790
911
|
changed = true;
|
|
791
912
|
}
|
|
792
913
|
for (const [marker, anchor, inject, what] of UWS_EDITS) {
|
|
@@ -803,13 +924,14 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
803
924
|
if (changed) await writeFile(file, src);
|
|
804
925
|
}
|
|
805
926
|
|
|
806
|
-
async function patchCompilerArgs(root: string): Promise<void> {
|
|
927
|
+
export async function patchCompilerArgs(root: string): Promise<void> {
|
|
807
928
|
const file = resolve(root, "compiler/index.js");
|
|
808
929
|
let src = await readFile(file, "utf8");
|
|
809
930
|
let changed = false;
|
|
810
931
|
for (const [marker, anchor, inject, what] of [
|
|
811
932
|
[LINK_MARKER, LINK_ANCHOR, LINK_INJECT, "extra link args"],
|
|
812
933
|
[CFLAGS_MARKER, CFLAGS_ANCHOR, CFLAGS_INJECT, "extra compiler flags"],
|
|
934
|
+
[UWS_CFLAGS_MARKER, UWS_CFLAGS_ANCHOR, UWS_CFLAGS_INJECT, "native-fetch shim compiler flags"],
|
|
813
935
|
] as const) {
|
|
814
936
|
if (src.includes(marker)) continue;
|
|
815
937
|
const at = src.indexOf(anchor);
|
|
@@ -819,10 +941,10 @@ async function patchCompilerArgs(root: string): Promise<void> {
|
|
|
819
941
|
"Porffor's native-fetch build changed — check patches/UPSTREAM.md.",
|
|
820
942
|
);
|
|
821
943
|
}
|
|
822
|
-
// After the anchor for
|
|
823
|
-
// line (the object list ends with it).
|
|
944
|
+
// After the anchor for compiler flags (the args follow it), before it for
|
|
945
|
+
// the link line (the object list ends with it).
|
|
824
946
|
src =
|
|
825
|
-
marker === CFLAGS_MARKER
|
|
947
|
+
marker === CFLAGS_MARKER || marker === UWS_CFLAGS_MARKER
|
|
826
948
|
? src.slice(0, at + anchor.length) + inject + src.slice(at + anchor.length)
|
|
827
949
|
: src.slice(0, at) + inject + src.slice(at);
|
|
828
950
|
changed = true;
|