@sproutboat/toolchain 0.4.4 → 0.4.6
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 +94 -1
- package/src/patch.ts +355 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.4.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a357b91: Compile direct R2 transfer support only into artifacts that declare an R2 binding.
|
|
8
|
+
|
|
9
|
+
## 0.4.5
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 5ee065c: Stream standalone direct R2 byte ranges safely and support ETag conditionals.
|
|
14
|
+
|
|
3
15
|
## 0.4.4
|
|
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 /
|
|
@@ -171,6 +182,8 @@ test("#156: status-line fallback synthesizes a line for unlisted codes, idempote
|
|
|
171
182
|
expect(once).toContain("try_handle_r2_transfer(res, req, method)");
|
|
172
183
|
expect(once).toContain("sb_r2_transfer_download_open");
|
|
173
184
|
expect(once).toContain("sb_r2_transfer_download_v1");
|
|
185
|
+
expect(once).toContain("sb_r2_transfer_range_v1");
|
|
186
|
+
expect(once).toContain("sb_r2_transfer_conditional_v1");
|
|
174
187
|
|
|
175
188
|
// #163: collect_headers takes res, drops a client-sent x-sb-remote-addr, and
|
|
176
189
|
// appends the real peer; the call site passes res through.
|
|
@@ -228,6 +241,86 @@ test("a drifted shim fails loudly instead of silently no-op'ing", async () => {
|
|
|
228
241
|
}
|
|
229
242
|
});
|
|
230
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
|
+
|
|
231
324
|
test("#165: render.js routes console output to stderr, unbuffered, idempotently", async () => {
|
|
232
325
|
const root = await mkdtemp(join(tmpdir(), "sb-patch-render-"));
|
|
233
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);
|
|
@@ -425,6 +435,7 @@ const R2_TRANSFER_INJECT = `extern "C" {
|
|
|
425
435
|
void sb_r2_transfer_abort(sb_r2_transfer_ctx*);
|
|
426
436
|
int sb_r2_transfer_download_open(const char*, const char*, sb_r2_transfer_ctx**);
|
|
427
437
|
size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);
|
|
438
|
+
const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);
|
|
428
439
|
size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);
|
|
429
440
|
void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);
|
|
430
441
|
}
|
|
@@ -452,22 +463,58 @@ static bool try_handle_r2_transfer(uWS::HttpResponse<false>* res, uWS::HttpReque
|
|
|
452
463
|
std::string bucket, token;
|
|
453
464
|
if (!sb_r2_transfer_path(req->getUrl(), &bucket, &token)) return false;
|
|
454
465
|
// sb_r2_transfer_download_v1
|
|
466
|
+
// sb_r2_transfer_range_v1
|
|
467
|
+
// sb_r2_transfer_range_header_v2
|
|
468
|
+
// sb_r2_transfer_conditional_v1
|
|
469
|
+
// sb_r2_transfer_streaming_v2
|
|
455
470
|
if (method == "GET") {
|
|
456
471
|
sb_r2_transfer_ctx* download = nullptr;
|
|
457
472
|
const int open_status = sb_r2_transfer_download_open(bucket.c_str(), token.c_str(), &download);
|
|
458
473
|
if (open_status != 0 || !download) { respond_with_error(res, sb_r2_transfer_status(open_status), "R2 transfer unavailable", true); return true; }
|
|
459
|
-
|
|
460
|
-
|
|
474
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
475
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
476
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
477
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;
|
|
478
|
+
}
|
|
479
|
+
const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
480
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
481
|
+
std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");
|
|
482
|
+
if (!range.empty()) {
|
|
483
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
484
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
485
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
486
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
487
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
488
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
489
|
+
if (last >= full_size) last = full_size - 1;
|
|
490
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
491
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
492
|
+
}
|
|
493
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
494
|
+
struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; char chunk[65536]; };
|
|
495
|
+
auto state = std::make_shared<Download>(Download{download, first, last - first + 1});
|
|
461
496
|
auto pump = std::make_shared<std::function<bool()>>();
|
|
462
497
|
*pump = [res, state, pump]() {
|
|
463
498
|
if (state->closed) return false;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
499
|
+
for (;;) {
|
|
500
|
+
const size_t want = state->size - state->sent < sizeof(state->chunk) ? (size_t)(state->size - state->sent) : sizeof(state->chunk);
|
|
501
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), state->chunk, want);
|
|
502
|
+
const auto result = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
503
|
+
if (result.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
504
|
+
if (!result.first) {
|
|
505
|
+
res->onWritable([res, state](uintmax_t offset) {
|
|
506
|
+
if (state->closed) return true;
|
|
507
|
+
const size_t want = state->size - offset < sizeof(state->chunk) ? (size_t)(state->size - offset) : sizeof(state->chunk);
|
|
508
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), state->chunk, want);
|
|
509
|
+
const auto resumed = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
510
|
+
if (resumed.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return true; }
|
|
511
|
+
return resumed.first;
|
|
512
|
+
});
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
state->sent += bytes;
|
|
516
|
+
if (state->sent == state->size) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
517
|
+
}
|
|
471
518
|
};
|
|
472
519
|
res->writeHeader("Content-Type", "application/octet-stream");
|
|
473
520
|
res->onAborted([state]() { if (!state->closed) { sb_r2_transfer_download_close(state->ctx); state->closed = true; } });
|
|
@@ -496,37 +543,84 @@ static bool try_handle_r2_transfer(uWS::HttpResponse<false>* res, uWS::HttpReque
|
|
|
496
543
|
return true;
|
|
497
544
|
}
|
|
498
545
|
static void on_request(uWS::HttpResponse<false>* res, uWS::HttpRequest* req) {
|
|
546
|
+
#endif
|
|
499
547
|
`;
|
|
500
548
|
const R2_TRANSFER_MARKER = "sb_r2_transfer_path(std::string_view path";
|
|
501
|
-
const R2_TRANSFER_CALL_ANCHOR =
|
|
549
|
+
const R2_TRANSFER_CALL_ANCHOR =
|
|
550
|
+
" __porffor_js_enter();\n const i32 url_ptr = alloc_request_url(req);";
|
|
502
551
|
const R2_TRANSFER_CALL_INJECT =
|
|
503
|
-
" 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;
|
|
504
554
|
const R2_TRANSFER_CALL_MARKER = "try_handle_r2_transfer(res, req, method)";
|
|
555
|
+
const R2_GATE_MARKER = "#ifdef SB_R2_TRANSFER";
|
|
505
556
|
const R2_DOWNLOAD_CACHE_MARKER = "sb_r2_transfer_download_v1";
|
|
557
|
+
const R2_RANGE_CACHE_MARKER = "sb_r2_transfer_range_v1";
|
|
558
|
+
const R2_CONDITIONAL_CACHE_MARKER = "sb_r2_transfer_conditional_v1";
|
|
559
|
+
const R2_STREAMING_CACHE_MARKER = "sb_r2_transfer_streaming_v2";
|
|
560
|
+
const R2_RANGE_HEADER_CACHE_MARKER = "sb_r2_transfer_range_header_v2";
|
|
506
561
|
const R2_DOWNLOAD_DECL_ANCHOR = " void sb_r2_transfer_abort(sb_r2_transfer_ctx*);\n}";
|
|
507
562
|
const R2_DOWNLOAD_DECL_INJECT =
|
|
508
563
|
" void sb_r2_transfer_abort(sb_r2_transfer_ctx*);\n" +
|
|
509
564
|
" int sb_r2_transfer_download_open(const char*, const char*, sb_r2_transfer_ctx**);\n" +
|
|
510
565
|
" size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n" +
|
|
566
|
+
" const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n" +
|
|
511
567
|
" size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx*, size_t, char*, size_t);\n" +
|
|
512
568
|
" void sb_r2_transfer_download_close(sb_r2_transfer_ctx*);\n}";
|
|
513
|
-
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; }';
|
|
514
571
|
const R2_DOWNLOAD_BRANCH_INJECT = ` // sb_r2_transfer_download_v1
|
|
572
|
+
// sb_r2_transfer_range_v1
|
|
573
|
+
// sb_r2_transfer_range_header_v2
|
|
574
|
+
// sb_r2_transfer_conditional_v1
|
|
575
|
+
// sb_r2_transfer_streaming_v2
|
|
515
576
|
if (method == "GET") {
|
|
516
577
|
sb_r2_transfer_ctx* download = nullptr;
|
|
517
578
|
const int status = sb_r2_transfer_download_open(bucket.c_str(), token.c_str(), &download);
|
|
518
579
|
if (status != 0 || !download) { respond_with_error(res, sb_r2_transfer_status(status), "R2 transfer unavailable", true); return true; }
|
|
519
|
-
|
|
520
|
-
|
|
580
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
581
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
582
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
583
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->writeHeader("ETag", etag); res->end(); return true;
|
|
584
|
+
}
|
|
585
|
+
const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
586
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
587
|
+
std::string_view range = req->getHeader("range"); if (range.empty()) range = req->getHeader("Range");
|
|
588
|
+
if (!range.empty()) {
|
|
589
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
590
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
591
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
592
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
593
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
594
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
595
|
+
if (last >= full_size) last = full_size - 1;
|
|
596
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
597
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
598
|
+
}
|
|
599
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
600
|
+
struct Download { sb_r2_transfer_ctx* ctx; uintmax_t base; uintmax_t size; uintmax_t sent = 0; bool closed = false; char chunk[65536]; };
|
|
601
|
+
auto state = std::make_shared<Download>(Download{download, first, last - first + 1});
|
|
521
602
|
auto pump = std::make_shared<std::function<bool()>>();
|
|
522
603
|
*pump = [res, state, pump]() {
|
|
523
604
|
if (state->closed) return false;
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
605
|
+
for (;;) {
|
|
606
|
+
const size_t want = state->size - state->sent < sizeof(state->chunk) ? (size_t)(state->size - state->sent) : sizeof(state->chunk);
|
|
607
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), state->chunk, want);
|
|
608
|
+
const auto result = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
609
|
+
if (result.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
610
|
+
if (!result.first) {
|
|
611
|
+
res->onWritable([res, state](uintmax_t offset) {
|
|
612
|
+
if (state->closed) return true;
|
|
613
|
+
const size_t want = state->size - offset < sizeof(state->chunk) ? (size_t)(state->size - offset) : sizeof(state->chunk);
|
|
614
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), state->chunk, want);
|
|
615
|
+
const auto resumed = res->tryEnd(std::string_view(state->chunk, bytes), state->size);
|
|
616
|
+
if (resumed.second || bytes == 0) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return true; }
|
|
617
|
+
return resumed.first;
|
|
618
|
+
});
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
state->sent += bytes;
|
|
622
|
+
if (state->sent == state->size) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
623
|
+
}
|
|
530
624
|
};
|
|
531
625
|
res->writeHeader("Content-Type", "application/octet-stream");
|
|
532
626
|
res->onAborted([state]() { if (!state->closed) { sb_r2_transfer_download_close(state->ctx); state->closed = true; } });
|
|
@@ -566,8 +660,18 @@ const UWS_EDITS = [
|
|
|
566
660
|
WRITE_RESPONSE_INJECT,
|
|
567
661
|
"detect x-sb-raw-body, steer the body read (#176)",
|
|
568
662
|
],
|
|
569
|
-
[
|
|
570
|
-
|
|
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
|
+
],
|
|
571
675
|
] as const;
|
|
572
676
|
|
|
573
677
|
/** The uWebSockets shim source: body limit + the #156 status-line fix. Exported for tests. */
|
|
@@ -575,16 +679,237 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
575
679
|
const file = resolve(root, "compiler/uwebsockets.js");
|
|
576
680
|
let src = await readFile(file, "utf8");
|
|
577
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
|
+
}
|
|
578
702
|
// Upgrade already-patched caches from the initial upload-only bridge. New
|
|
579
703
|
// caches receive the full bridge below; old ones need declarations plus the
|
|
580
704
|
// GET dispatch grafted onto their existing transfer handler.
|
|
581
705
|
if (src.includes(R2_TRANSFER_MARKER) && !src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
582
706
|
if (!src.includes(R2_DOWNLOAD_DECL_ANCHOR) || !src.includes(R2_DOWNLOAD_BRANCH_ANCHOR))
|
|
583
|
-
throw new Error(
|
|
707
|
+
throw new Error(
|
|
708
|
+
`could not upgrade Porffor's direct R2 transfer bridge: anchor not found in ${file}.`,
|
|
709
|
+
);
|
|
584
710
|
src = src.replace(R2_DOWNLOAD_DECL_ANCHOR, R2_DOWNLOAD_DECL_INJECT);
|
|
585
711
|
src = src.replace(R2_DOWNLOAD_BRANCH_ANCHOR, R2_DOWNLOAD_BRANCH_INJECT);
|
|
586
712
|
changed = true;
|
|
587
713
|
}
|
|
714
|
+
if (src.includes(R2_DOWNLOAD_CACHE_MARKER) && !src.includes(R2_RANGE_CACHE_MARKER)) {
|
|
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)});";
|
|
717
|
+
const range = ` const uintmax_t full_size = sb_r2_transfer_download_size(download);
|
|
718
|
+
uintmax_t first = 0, last = full_size ? full_size - 1 : 0;
|
|
719
|
+
const std::string_view range = req->getHeader("range");
|
|
720
|
+
if (!range.empty()) {
|
|
721
|
+
if (range.substr(0, 6) != "bytes=") { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
722
|
+
const std::string_view spec = range.substr(6); const size_t dash = spec.find('-');
|
|
723
|
+
if (dash == std::string_view::npos || dash == 0) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; }
|
|
724
|
+
first = 0; for (char c : spec.substr(0, dash)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } first = first * 10 + (c - '0'); }
|
|
725
|
+
if (dash + 1 < spec.size()) { last = 0; for (char c : spec.substr(dash + 1)) { if (c < '0' || c > '9') { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "invalid range", true); return true; } last = last * 10 + (c - '0'); } }
|
|
726
|
+
if (first >= full_size || last < first) { sb_r2_transfer_download_close(download); respond_with_error(res, "416 Range Not Satisfiable", "range outside object", true); return true; }
|
|
727
|
+
if (last >= full_size) last = full_size - 1;
|
|
728
|
+
char content_range[96]; snprintf(content_range, sizeof(content_range), "bytes %llu-%llu/%llu", (unsigned long long)first, (unsigned long long)last, (unsigned long long)full_size);
|
|
729
|
+
res->writeStatus("206 Partial Content"); res->writeHeader("Content-Range", content_range);
|
|
730
|
+
}
|
|
731
|
+
`;
|
|
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
|
+
);
|
|
742
|
+
src = src.replace(stateAnchor, range + state);
|
|
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
|
+
);
|
|
747
|
+
changed = true;
|
|
748
|
+
}
|
|
749
|
+
if (src.includes(R2_RANGE_CACHE_MARKER) && !src.includes(R2_CONDITIONAL_CACHE_MARKER)) {
|
|
750
|
+
const declaration = " size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx*);\n";
|
|
751
|
+
const declarationReplacement =
|
|
752
|
+
declaration + " const char* sb_r2_transfer_download_etag(sb_r2_transfer_ctx*);\n";
|
|
753
|
+
const anchor = " const uintmax_t full_size = sb_r2_transfer_download_size(download);\n";
|
|
754
|
+
const inject =
|
|
755
|
+
` // sb_r2_transfer_conditional_v1
|
|
756
|
+
const std::string etag = sb_r2_transfer_download_etag(download);
|
|
757
|
+
res->writeHeader("ETag", etag); res->writeHeader("Accept-Ranges", "bytes");
|
|
758
|
+
const std::string_view if_none_match = req->getHeader("if-none-match");
|
|
759
|
+
if (if_none_match == "*" || if_none_match == etag || (if_none_match.size() == etag.size() + 2 && if_none_match.front() == '"' && if_none_match.back() == '"' && if_none_match.substr(1, etag.size()) == etag)) {
|
|
760
|
+
sb_r2_transfer_download_close(download); res->writeStatus("304 Not Modified"); res->end(); return true;
|
|
761
|
+
}
|
|
762
|
+
` + 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
|
+
);
|
|
767
|
+
src = src.replace(declaration, declarationReplacement).replace(anchor, inject);
|
|
768
|
+
changed = true;
|
|
769
|
+
}
|
|
770
|
+
if (src.includes(R2_CONDITIONAL_CACHE_MARKER) && !src.includes(R2_STREAMING_CACHE_MARKER)) {
|
|
771
|
+
const oldPump = ` const uintmax_t offset = res->getWriteOffset();
|
|
772
|
+
char chunk[65536];
|
|
773
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
774
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
775
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
776
|
+
if (!result.first) res->onWritable([pump](uintmax_t) { return (*pump)(); });
|
|
777
|
+
return false;`;
|
|
778
|
+
const newPump = ` for (;;) {
|
|
779
|
+
const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
|
|
780
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
781
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
782
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
783
|
+
if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
|
|
784
|
+
}`;
|
|
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);
|
|
795
|
+
changed = true;
|
|
796
|
+
}
|
|
797
|
+
// A short-lived v2 cache marker was written before its pump fix. Repair
|
|
798
|
+
// those caches as well as applying the current form to future upgrades.
|
|
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; };";
|
|
807
|
+
const oldPump = ` const uintmax_t offset = res->getWriteOffset(); char chunk[65536];
|
|
808
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + offset), chunk, sizeof(chunk));
|
|
809
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
810
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
811
|
+
if (!result.first) { res->onWritable([pump](uintmax_t) { return (*pump)(); }); return false; }
|
|
812
|
+
}`;
|
|
813
|
+
const newPump = ` char chunk[65536];
|
|
814
|
+
const size_t bytes = sb_r2_transfer_download_read(state->ctx, (size_t)(state->base + state->sent), chunk, sizeof(chunk));
|
|
815
|
+
const auto result = res->tryEnd(std::string_view(chunk, bytes), state->size);
|
|
816
|
+
if (result.second) { sb_r2_transfer_download_close(state->ctx); state->closed = true; return false; }
|
|
817
|
+
if (!result.first) { res->onWritable([pump, state](uintmax_t offset) { state->sent = offset; return (*pump)(); }); return false; }
|
|
818
|
+
state->sent += bytes;
|
|
819
|
+
}`;
|
|
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
|
+
);
|
|
824
|
+
src = src.replace(oldState, newState).replace(oldPump, newPump);
|
|
825
|
+
changed = true;
|
|
826
|
+
}
|
|
827
|
+
const duplicatePump = " for (;;) {\n char chunk[65536];\n for (;;) {\n";
|
|
828
|
+
if (src.includes(duplicatePump)) {
|
|
829
|
+
src = src.replace(duplicatePump, " for (;;) {\n char chunk[65536];\n");
|
|
830
|
+
changed = true;
|
|
831
|
+
}
|
|
832
|
+
const unclosedPump = " state->sent += bytes;\n };\n";
|
|
833
|
+
if (src.includes(unclosedPump)) {
|
|
834
|
+
src = src.replace(unclosedPump, " state->sent += bytes;\n }\n };\n");
|
|
835
|
+
changed = true;
|
|
836
|
+
}
|
|
837
|
+
if (src.includes(R2_STREAMING_CACHE_MARKER) && !src.includes(R2_RANGE_HEADER_CACHE_MARKER)) {
|
|
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
|
+
);
|
|
851
|
+
changed = true;
|
|
852
|
+
}
|
|
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
|
+
);
|
|
878
|
+
changed = true;
|
|
879
|
+
}
|
|
880
|
+
if (src.includes("state->sent") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
881
|
+
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
882
|
+
const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
|
|
883
|
+
const freshStart = R2_DOWNLOAD_BRANCH_INJECT.indexOf(" struct Download {");
|
|
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);
|
|
896
|
+
changed = true;
|
|
897
|
+
}
|
|
898
|
+
if (src.includes("writable_offset") && src.includes(R2_DOWNLOAD_CACHE_MARKER)) {
|
|
899
|
+
const stateStart = src.indexOf(" struct Download {", src.indexOf(R2_DOWNLOAD_CACHE_MARKER));
|
|
900
|
+
const stateEnd = src.indexOf(' res->writeHeader("Content-Type"', stateStart);
|
|
901
|
+
const freshStart = R2_TRANSFER_INJECT.indexOf(" struct Download {");
|
|
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);
|
|
911
|
+
changed = true;
|
|
912
|
+
}
|
|
588
913
|
for (const [marker, anchor, inject, what] of UWS_EDITS) {
|
|
589
914
|
if (src.includes(marker)) continue;
|
|
590
915
|
if (!src.includes(anchor)) {
|
|
@@ -599,13 +924,14 @@ export async function patchUwebsockets(root: string): Promise<void> {
|
|
|
599
924
|
if (changed) await writeFile(file, src);
|
|
600
925
|
}
|
|
601
926
|
|
|
602
|
-
async function patchCompilerArgs(root: string): Promise<void> {
|
|
927
|
+
export async function patchCompilerArgs(root: string): Promise<void> {
|
|
603
928
|
const file = resolve(root, "compiler/index.js");
|
|
604
929
|
let src = await readFile(file, "utf8");
|
|
605
930
|
let changed = false;
|
|
606
931
|
for (const [marker, anchor, inject, what] of [
|
|
607
932
|
[LINK_MARKER, LINK_ANCHOR, LINK_INJECT, "extra link args"],
|
|
608
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"],
|
|
609
935
|
] as const) {
|
|
610
936
|
if (src.includes(marker)) continue;
|
|
611
937
|
const at = src.indexOf(anchor);
|
|
@@ -615,10 +941,10 @@ async function patchCompilerArgs(root: string): Promise<void> {
|
|
|
615
941
|
"Porffor's native-fetch build changed — check patches/UPSTREAM.md.",
|
|
616
942
|
);
|
|
617
943
|
}
|
|
618
|
-
// After the anchor for
|
|
619
|
-
// 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).
|
|
620
946
|
src =
|
|
621
|
-
marker === CFLAGS_MARKER
|
|
947
|
+
marker === CFLAGS_MARKER || marker === UWS_CFLAGS_MARKER
|
|
622
948
|
? src.slice(0, at + anchor.length) + inject + src.slice(at + anchor.length)
|
|
623
949
|
: src.slice(0, at) + inject + src.slice(at);
|
|
624
950
|
changed = true;
|