From 4b92b3da820addbe250870f3489876fe06ecca7d Mon Sep 17 00:00:00 2001 From: Charles Eckman Date: Thu, 12 Jun 2025 11:28:17 -0400 Subject: [PATCH 1/2] Option to respect range during cache streaming The core cache API allows for cache items to be concurrently streamed to / from the cache. If: - The size of the cached item's body was not provided by the writer - The reader requests a specific range of the cached item's body (`to_stream_from_range`) - The writer and reader are concurrent, i.e. the body is streamed from one to the other the core cache API will ignore the requested range and provide the whole body. There is no explicit notification that the whole body is provided instead of a range. In this SDK release, this remains the default behavior. However, lookup calls (lookup, transaction lookup) now offer an `always_use_requested_range` option. If set to true, body reads conducted as part of this lookup/transaction/replacement will always use the requested range, even when streaming. In a future (major) SDK release, the default behavior will change to `always_use_requested_range`. --- runtime/fastly/builtins/cache-core.cpp | 14 ++++++++++++++ runtime/fastly/host-api/fastly.h | 3 +++ runtime/fastly/host-api/host_api.cpp | 6 ++++++ runtime/fastly/host-api/host_api_fastly.h | 10 ++++++++++ 4 files changed, 33 insertions(+) diff --git a/runtime/fastly/builtins/cache-core.cpp b/runtime/fastly/builtins/cache-core.cpp index 7a0bb32ba3..28f4fad8fd 100644 --- a/runtime/fastly/builtins/cache-core.cpp +++ b/runtime/fastly/builtins/cache-core.cpp @@ -42,6 +42,7 @@ JS::Result parseLookupOptions(JSContext *cx, return JS::Result(JS::Error()); } JS::RootedObject options_obj(cx, &options_val.toObject()); + JS::RootedValue headers_val(cx); if (!JS_GetProperty(cx, options_obj, "headers", &headers_val)) { return JS::Result(JS::Error()); @@ -79,6 +80,19 @@ JS::Result parseLookupOptions(JSContext *cx, } options.request_headers = host_api::HttpReq(request_handle); } + + JS::RootedValue always_use_requested_range_val(cx); + if (!JS_GetProperty(cx, options_obj, "always_use_requested_range", &always_use_requested_range_val)) { + return JS::Result(JS::Error()); + } + // always_use_requested_range property is optional + if (!always_use_requested_range_val.isUndefined()) { + if (!always_use_requested_range_val.isBoolean()) { + JS_ReportErrorASCII(cx, "always_use_requested_range must be a boolean"); + return JS::Result(JS::Error()); + } + options.always_use_requested_range = always_use_requested_range_val.toBoolean(); + } } return options; } diff --git a/runtime/fastly/host-api/fastly.h b/runtime/fastly/host-api/fastly.h index b0d0cb28c6..bc4c965bdc 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -871,6 +871,9 @@ int purge_surrogate_key(char *surrogate_key, size_t surrogate_key_len, uint32_t #define FASTLY_CACHE_LOOKUP_OPTIONS_MASK_RESERVED (1 << 0) #define FASTLY_CACHE_LOOKUP_OPTIONS_MASK_REQUEST_HEADERS (1 << 1) +// Note: SERVICE_ID for internal use only. +#define FASTLY_CACHE_LOOKUP_OPTIONS_MASK_SERVICE_ID (1 << 2) +#define FASTLY_CACHE_LOOKUP_OPTIONS_MASK_ALWAYS_USE_REQUESTED_RANGE (1 << 3) // Extensible options for cache lookup operations currently used for both `lookup` and // `transaction_lookup`. diff --git a/runtime/fastly/host-api/host_api.cpp b/runtime/fastly/host-api/host_api.cpp index ab33543bfc..b091eb4be0 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -2950,6 +2950,9 @@ Result CacheHandle::lookup(std::string_view key, const CacheLookupO os.request_headers = opts.request_headers.handle; options_mask |= FASTLY_CACHE_LOOKUP_OPTIONS_MASK_REQUEST_HEADERS; } + if (opts.always_use_requested_range) { + options_mask |= FASTLY_CACHE_LOOKUP_OPTIONS_MASK_ALWAYS_USE_REQUESTED_RANGE; + } if (!convert_result(fastly::cache_lookup(reinterpret_cast(key_str.ptr), key_str.len, options_mask, &os, &handle), @@ -2979,6 +2982,9 @@ Result CacheHandle::transaction_lookup(std::string_view key, os.request_headers = opts.request_headers.handle; options_mask |= FASTLY_CACHE_LOOKUP_OPTIONS_MASK_REQUEST_HEADERS; } + if (opts.always_use_requested_range) { + options_mask |= FASTLY_CACHE_LOOKUP_OPTIONS_MASK_ALWAYS_USE_REQUESTED_RANGE; + } if (!convert_result(fastly::cache_transaction_lookup(reinterpret_cast(key_str.ptr), key_str.len, options_mask, &os, &handle), diff --git a/runtime/fastly/host-api/host_api_fastly.h b/runtime/fastly/host-api/host_api_fastly.h index ad444c67d0..f98da2d0d2 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -940,6 +940,16 @@ class SecretStore final { struct CacheLookupOptions final { /// A full request handle, used only for its headers. HttpReq request_headers; + + /// When a range is provided to get_body, + /// respect the requested range even when the body length is unknown + /// (e.g. streaming). If "false", when the body length is unknown, + /// the entire body will be returned instead of just the requested range. + /// + /// The default is "false" to preserve the legacy behavior. + /// However, if using ranged get_body, Fastly recommends setting this to "true". + /// The default may change in a future release. + bool always_use_requested_range; }; struct CacheGetBodyOptions final { From 3d8e8a321102b28f72470126d76a56b3a4212867 Mon Sep 17 00:00:00 2001 From: Charles Eckman Date: Thu, 12 Jun 2025 16:04:42 -0400 Subject: [PATCH 2/2] Format cpp file --- runtime/fastly/builtins/cache-core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime/fastly/builtins/cache-core.cpp b/runtime/fastly/builtins/cache-core.cpp index 28f4fad8fd..3f384158af 100644 --- a/runtime/fastly/builtins/cache-core.cpp +++ b/runtime/fastly/builtins/cache-core.cpp @@ -82,7 +82,8 @@ JS::Result parseLookupOptions(JSContext *cx, } JS::RootedValue always_use_requested_range_val(cx); - if (!JS_GetProperty(cx, options_obj, "always_use_requested_range", &always_use_requested_range_val)) { + if (!JS_GetProperty(cx, options_obj, "always_use_requested_range", + &always_use_requested_range_val)) { return JS::Result(JS::Error()); } // always_use_requested_range property is optional