From ade8e4c86f5a4979c5ae317bf807e4efaeea5d3c Mon Sep 17 00:00:00 2001 From: Charles Eckman Date: Thu, 12 Jun 2025 11:28:17 -0400 Subject: [PATCH] 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`. Format cpp file --- runtime/fastly/builtins/cache-core.cpp | 15 +++++++++++++++ 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, 34 insertions(+) diff --git a/runtime/fastly/builtins/cache-core.cpp b/runtime/fastly/builtins/cache-core.cpp index da5cfe7a9f..7815707340 100644 --- a/runtime/fastly/builtins/cache-core.cpp +++ b/runtime/fastly/builtins/cache-core.cpp @@ -41,6 +41,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()); @@ -78,6 +79,20 @@ 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 a1b9497c21..86c44704b5 100644 --- a/runtime/fastly/host-api/fastly.h +++ b/runtime/fastly/host-api/fastly.h @@ -936,6 +936,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 dc44a62038..33bc3de008 100644 --- a/runtime/fastly/host-api/host_api.cpp +++ b/runtime/fastly/host-api/host_api.cpp @@ -3337,6 +3337,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), @@ -3366,6 +3369,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 cc55140424..bc4bf782a7 100644 --- a/runtime/fastly/host-api/host_api_fastly.h +++ b/runtime/fastly/host-api/host_api_fastly.h @@ -1018,6 +1018,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 {