Skip to content

Commit 3f2db5c

Browse files
authored
fix: core-cache headers case (#889)
1 parent a693b45 commit 3f2db5c

File tree

4 files changed

+212
-125
lines changed

4 files changed

+212
-125
lines changed

integration-tests/js-compute/fixtures/app/src/cache-core.js

+142
Original file line numberDiff line numberDiff line change
@@ -3786,3 +3786,145 @@ let error;
37863786
});
37873787
}
37883788
}
3789+
3790+
{
3791+
3792+
routes.set("/core-cache/transaction-lookup-transaction-insert-vary-works", async () => {
3793+
const key = `/core-cache/vary-works-${Date.now()}`
3794+
const animal = 'animal'
3795+
let entry = CoreCache.transactionLookup(key, {
3796+
headers: {
3797+
[animal]: 'cat'
3798+
}
3799+
})
3800+
let error = assert(entry.state().found(), false, `entry.state().found() === false`)
3801+
if (error) { return error }
3802+
let writer = entry.insert({
3803+
maxAge: 60_000 * 60,
3804+
vary: [animal],
3805+
headers: {
3806+
[animal]: 'cat'
3807+
}
3808+
})
3809+
3810+
writer.append('cat')
3811+
writer.close()
3812+
entry.close()
3813+
await sleep(1_000);
3814+
3815+
entry = CoreCache.transactionLookup(key, {
3816+
headers: {
3817+
[animal]: 'cat'
3818+
}
3819+
})
3820+
error = assert(entry.state().found(), true, `entry.state().found() === true`)
3821+
if (error) { return error }
3822+
3823+
error = assert(await streamToString(entry.body()), 'cat', `await streamToString(CoreCache.lookup(key).body())`)
3824+
if (error) { return error }
3825+
entry.close()
3826+
3827+
entry = CoreCache.transactionLookup(key, {
3828+
headers: {
3829+
[animal]: 'dog'
3830+
}
3831+
})
3832+
error = assert(entry.state().found(), false, `entry.state().found() == false`)
3833+
if (error) { return error }
3834+
3835+
writer = entry.insert({
3836+
maxAge: 60_000 * 60,
3837+
vary: [animal],
3838+
headers: {
3839+
[animal]: 'dog'
3840+
}
3841+
})
3842+
3843+
writer.append('dog')
3844+
writer.close()
3845+
entry.close()
3846+
await sleep(1_000);
3847+
3848+
entry = CoreCache.transactionLookup(key, {
3849+
headers: {
3850+
[animal]: 'dog'
3851+
}
3852+
})
3853+
error = assert(entry.state().found(), true, `entry.state().found() === true`)
3854+
if (error) { return error }
3855+
3856+
error = assert(await streamToString(entry.body()), 'dog', `await streamToString(CoreCache.lookup(key).body())`)
3857+
if (error) { return error }
3858+
entry.close()
3859+
3860+
return pass("ok")
3861+
});
3862+
3863+
routes.set("/core-cache/lookup-insert-vary-works", async () => {
3864+
const key = `/core-cache/vary-works-${Date.now()}`
3865+
const animal = 'animal'
3866+
let entry = CoreCache.lookup(key, {
3867+
headers: {
3868+
[animal]: 'cat'
3869+
}
3870+
})
3871+
let error = assert(entry, null, `entry == null`)
3872+
if (error) { return error }
3873+
let writer = CoreCache.insert(key, {
3874+
maxAge: 60_000 * 60,
3875+
vary: [animal],
3876+
headers: {
3877+
[animal]: 'cat'
3878+
}
3879+
})
3880+
3881+
writer.append('cat')
3882+
writer.close()
3883+
await sleep(1_000);
3884+
3885+
entry = CoreCache.lookup(key, {
3886+
headers: {
3887+
[animal]: 'cat'
3888+
}
3889+
})
3890+
error = assert(entry.state().found(), true, `entry.state().found() === true`)
3891+
if (error) { return error }
3892+
3893+
error = assert(await streamToString(entry.body()), 'cat', `await streamToString(CoreCache.lookup(key).body())`)
3894+
if (error) { return error }
3895+
entry.close()
3896+
3897+
entry = CoreCache.lookup(key, {
3898+
headers: {
3899+
[animal]: 'dog'
3900+
}
3901+
})
3902+
error = assert(entry, null, `entry == null`)
3903+
if (error) { return error }
3904+
3905+
writer = CoreCache.insert(key, {
3906+
maxAge: 60_000 * 60,
3907+
vary: [animal],
3908+
headers: {
3909+
[animal]: 'dog'
3910+
}
3911+
})
3912+
3913+
writer.append('dog')
3914+
writer.close()
3915+
await sleep(1_000);
3916+
3917+
entry = CoreCache.lookup(key, {
3918+
headers: {
3919+
[animal]: 'dog'
3920+
}
3921+
})
3922+
error = assert(entry.state().found(), true, `entry.state().found() === true`)
3923+
if (error) { return error }
3924+
3925+
error = assert(await streamToString(entry.body()), 'dog', `await streamToString(CoreCache.lookup(key).body())`)
3926+
if (error) { return error }
3927+
3928+
return pass("ok")
3929+
});
3930+
}

integration-tests/js-compute/fixtures/app/tests.json

+22
Original file line numberDiff line numberDiff line change
@@ -8703,5 +8703,27 @@
87038703
"status": 200,
87048704
"body": "ok"
87058705
}
8706+
},
8707+
"GET /core-cache/transaction-lookup-transaction-insert-vary-works": {
8708+
"environments": ["compute"],
8709+
"downstream_request": {
8710+
"method": "GET",
8711+
"pathname": "/core-cache/transaction-lookup-transaction-insert-vary-works"
8712+
},
8713+
"downstream_response": {
8714+
"status": 200,
8715+
"body": "ok"
8716+
}
8717+
},
8718+
"GET /core-cache/lookup-insert-vary-works": {
8719+
"environments": ["compute"],
8720+
"downstream_request": {
8721+
"method": "GET",
8722+
"pathname": "/core-cache/lookup-insert-vary-works"
8723+
},
8724+
"downstream_response": {
8725+
"status": 200,
8726+
"body": "ok"
8727+
}
87068728
}
87078729
}

runtime/fastly/builtins/cache-core.cpp

+25-58
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ api::Engine *ENGINE;
3030
JS::Result<host_api::CacheLookupOptions> parseLookupOptions(JSContext *cx,
3131
JS::HandleValue options_val) {
3232
host_api::CacheLookupOptions options;
33+
// options parameter is optional
34+
// options is meant to be an object with an optional headers field,
35+
// the headers field can be:
36+
// Headers | string[][] | Record<string, string>;
3337
if (!options_val.isUndefined()) {
3438
if (!options_val.isObject()) {
3539
JS_ReportErrorASCII(cx, "options argument must be an object");
@@ -42,28 +46,29 @@ JS::Result<host_api::CacheLookupOptions> parseLookupOptions(JSContext *cx,
4246
}
4347
// headers property is optional
4448
if (!headers_val.isUndefined()) {
45-
JS::RootedObject headersInstance(
46-
cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj));
47-
if (!headersInstance) {
49+
if (!headers_val.isObject()) {
50+
JS_ReportErrorASCII(
51+
cx, "Failed to construct Headers object. If defined, the first argument must be either "
52+
"a [ ['name', 'value'], ... ] sequence, or a { 'name' : 'value', ... } record.");
4853
return JS::Result<host_api::CacheLookupOptions>(JS::Error());
4954
}
50-
auto headers = Headers::create(cx, headersInstance, Headers::Mode::Standalone, nullptr,
51-
headers_val, true);
52-
if (!headers) {
55+
JS::RootedObject request_opts(cx, JS_NewPlainObject(cx));
56+
if (!JS_SetProperty(cx, request_opts, "headers", headers_val)) {
5357
return JS::Result<host_api::CacheLookupOptions>(JS::Error());
5458
}
55-
JS::RootedValue headers_val(cx, JS::ObjectValue(*headers));
5659
JS::RootedObject requestInstance(cx, Request::create_instance(cx));
5760
if (!requestInstance) {
5861
return JS::Result<host_api::CacheLookupOptions>(JS::Error());
5962
}
6063

64+
JS::RootedValue request_opts_val(cx, ObjectValue(*request_opts));
65+
6166
// We need to convert the supplied HeadersInit in the `headers` property into a host-backed
6267
// Request which contains the same headers Request::create does exactly that
6368
// however, it also expects a fully valid URL for the Request. We don't ever use the Request
6469
// URL, so we hard-code a valid URL
6570
JS::RootedValue input(cx, JS::StringValue(JS_NewStringCopyZ(cx, "http://example.com")));
66-
JS::RootedObject request(cx, Request::create(cx, requestInstance, input, headers_val));
71+
JS::RootedObject request(cx, Request::create(cx, requestInstance, input, request_opts_val));
6772
options.request_headers = host_api::HttpReq(Request::request_handle(request));
6873
}
6974
}
@@ -326,28 +331,28 @@ JS::Result<host_api::CacheWriteOptions> parseInsertOptions(JSContext *cx,
326331
}
327332
// headers property is optional
328333
if (!headers_val.isUndefined()) {
329-
JS::RootedObject headersInstance(
330-
cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj));
331-
if (!headersInstance) {
334+
if (!headers_val.isObject()) {
335+
JS_ReportErrorASCII(
336+
cx, "Failed to construct Headers object. If defined, the first argument must be either "
337+
"a [ ['name', 'value'], ... ] sequence, or a { 'name' : 'value', ... } record.");
332338
return JS::Result<host_api::CacheWriteOptions>(JS::Error());
333339
}
334-
auto headers =
335-
Headers::create(cx, headersInstance, Headers::Mode::Standalone, nullptr, headers_val, true);
336-
if (!headers) {
340+
JS::RootedObject request_opts(cx, JS_NewPlainObject(cx));
341+
if (!JS_SetProperty(cx, request_opts, "headers", headers_val)) {
337342
return JS::Result<host_api::CacheWriteOptions>(JS::Error());
338343
}
339-
JS::RootedValue headers_val(cx, JS::ObjectValue(*headers));
340344
JS::RootedObject requestInstance(cx, Request::create_instance(cx));
341345
if (!requestInstance) {
342346
return JS::Result<host_api::CacheWriteOptions>(JS::Error());
343347
}
348+
JS::RootedValue request_opts_val(cx, ObjectValue(*request_opts));
344349

345350
// We need to convert the supplied HeadersInit in the `headers` property into a host-backed
346351
// Request which contains the same headers Request::create does exactly that however,
347352
// it also expects a fully valid URL for the Request. We don't ever use the Request URL, so we
348353
// hard-code a valid URL
349354
JS::RootedValue input(cx, JS::StringValue(JS_NewStringCopyZ(cx, "http://example.com")));
350-
JS::RootedObject request(cx, Request::create(cx, requestInstance, input, headers_val));
355+
JS::RootedObject request(cx, Request::create(cx, requestInstance, input, request_opts_val));
351356
options.request_headers = host_api::HttpReq(Request::request_handle(request));
352357
}
353358
return options;
@@ -1055,49 +1060,11 @@ bool CoreCache::transactionLookup(JSContext *cx, unsigned argc, JS::Value *vp) {
10551060
return false;
10561061
}
10571062

1058-
host_api::CacheLookupOptions options;
1059-
auto options_val = args.get(1);
1060-
// options parameter is optional
1061-
// options is meant to be an object with an optional headers field,
1062-
// the headers field can be:
1063-
// Headers | string[][] | Record<string, string>;
1064-
if (!options_val.isUndefined()) {
1065-
if (!options_val.isObject()) {
1066-
JS_ReportErrorASCII(cx, "options argument must be an object");
1067-
return false;
1068-
}
1069-
JS::RootedObject options_obj(cx, &options_val.toObject());
1070-
JS::RootedValue headers_val(cx);
1071-
if (!JS_GetProperty(cx, options_obj, "headers", &headers_val)) {
1072-
return false;
1073-
}
1074-
// headers property is optional
1075-
if (!headers_val.isUndefined()) {
1076-
JS::RootedObject headersInstance(
1077-
cx, JS_NewObjectWithGivenProto(cx, &Headers::class_, Headers::proto_obj));
1078-
if (!headersInstance) {
1079-
return false;
1080-
}
1081-
auto headers = Headers::create(cx, headersInstance, Headers::Mode::Standalone, nullptr,
1082-
headers_val, true);
1083-
if (!headers) {
1084-
return false;
1085-
}
1086-
JS::RootedValue headers_val(cx, JS::ObjectValue(*headers));
1087-
JS::RootedObject requestInstance(cx, Request::create_instance(cx));
1088-
if (!requestInstance) {
1089-
return false;
1090-
}
1091-
1092-
// We need to convert the supplied HeadersInit in the `headers` property into a host-backed
1093-
// Request which contains the same headers Request::create does exactly that
1094-
// however, it also expects a fully valid URL for the Request. We don't ever use the Request
1095-
// URL, so we hard-code a valid URL
1096-
JS::RootedValue input(cx, JS::StringValue(JS_NewStringCopyZ(cx, "http://example.com")));
1097-
JS::RootedObject request(cx, Request::create(cx, requestInstance, input, headers_val));
1098-
options.request_headers = host_api::HttpReq(Request::request_handle(request));
1099-
}
1063+
auto options_result = parseLookupOptions(cx, args.get(1));
1064+
if (options_result.isErr()) {
1065+
return false;
11001066
}
1067+
auto options = options_result.unwrap();
11011068

11021069
auto res = host_api::CacheHandle::transaction_lookup(key, options);
11031070
if (auto *err = res.to_err()) {

0 commit comments

Comments
 (0)