From 6ab3d158d483e671fbf0339297bb4b69a465677a Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 14:28:46 -0400 Subject: [PATCH 01/15] Start cache api --- Sources/Compute/Fastly/FastlyCache.swift | 143 ++++++++++++++++++ Sources/Compute/Fastly/FastlyTypes.swift | 107 +++++++++---- .../ComputeRuntime/include/ComputeRuntime.h | 92 ++++++++--- 3 files changed, 292 insertions(+), 50 deletions(-) create mode 100644 Sources/Compute/Fastly/FastlyCache.swift diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift new file mode 100644 index 00000000..2828f890 --- /dev/null +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -0,0 +1,143 @@ +// +// FastlyCache.swift +// +// +// Created by Andrew Barba on 9/13/23. +// + +import ComputeRuntime + +extension Fastly { + + public struct Cache: Sendable { + + public static func getOrSet(_ key: String, _ handler: () async throws -> FetchResponse) async throws -> ReadableBody { + return try await getOrSet(key) { + let body = try await handler().body.body + return HandlerData.body(body) + } + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> [UInt8]) async throws -> ReadableBody { + return try await getOrSet(key) { + let bytes = try await handler() + return HandlerData.bytes(bytes) + } + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> String) async throws -> ReadableBody { + return try await getOrSet(key) { + let text = try await handler() + return HandlerData.bytes(.init(text.utf8)) + } + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> Data) async throws -> ReadableBody { + return try await getOrSet(key) { + let data = try await handler() + return HandlerData.bytes(data.bytes) + } + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> [String: Sendable]) async throws -> ReadableBody { + return try await getOrSet(key) { + let json = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return HandlerData.bytes(data.bytes) + } + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> [Sendable]) async throws -> ReadableBody { + return try await getOrSet(key) { + let json = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return HandlerData.bytes(data.bytes) + } + } + } +} + +extension Fastly.Cache { + + internal enum HandlerData { + case body(_ body: Fastly.Body) + case bytes(_ bytes: [UInt8]) + } + + internal static func getOrSet(_ key: String, _ handler: () async throws -> HandlerData) async throws -> ReadableBody { + // Open the transaction + let trx = try await Transaction.lookup(key) + + // Get current transaction state + let state = try await trx.getState() + + // If state is usable then return the body from cache + if state == .found || state == .usable { + let body = try await trx.getBody() + return ReadableWasiBody(body) + } + + // If its not usable then begin executing handler with new value + let data = try await handler() + + // Get an instance to the insert handle + var writer = try await trx.insertAndStreamBack() + + // Append bytes from handler to writeable body + switch data { + case .body(let body): + try writer.body.append(body) + case .bytes(let bytes): + try writer.body.write(bytes) + } + + // Get handle to reabable body out from cache + return try await ReadableWasiBody(writer.transaction.getBody()) + } +} + +extension Fastly.Cache { + public struct Transaction: Sendable { + + internal let handle: WasiHandle + + internal init(_ handle: WasiHandle) { + self.handle = handle + } + + internal static func lookup(_ key: String) async throws -> Transaction { + var handle: WasiHandle = 0 + let options = CacheLookupOptions.reserved + var config = CacheLookupConfig() + try wasi(fastly_cache__cache_transaction_lookup(key, key.utf8.count, options.rawValue, &config, &handle)) + return Transaction(handle) + } + + internal func insertAndStreamBack() async throws -> (body: Fastly.Body, transaction: Transaction) { + var bodyHandle: WasiHandle = 0 + var cacheHandle: WasiHandle = 0 + let options = CacheWriteOptions.reserved + var config = CacheWriteConfig() + try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) + return (.init(bodyHandle), .init(cacheHandle)) + } + + internal func getState() async throws -> CacheState { + var value: UInt8 = 0 + try wasi(fastly_cache__cache_get_state(handle, &value)) + return CacheState(rawValue: value) + } + + internal func getBody() async throws -> Fastly.Body { + var bodyHandle: WasiHandle = 0 + let options = CacheGetBodyOptions.reserved + var config = CacheGetBodyConfig() + try wasi(fastly_cache__cache_get_body(handle, options.rawValue, &config, &bodyHandle)) + return .init(bodyHandle) + } + + internal func cancel() async throws { + try wasi(fastly_cache__cache_transaction_cancel(handle)) + } + } +} diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index f6728b3c..ca38949d 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -318,29 +318,6 @@ public typealias MultiValueCursor = Int32 public typealias MultiValueCursorResult = Int64 -public struct CacheOverrideTag: OptionSet { - - public let rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } -} - -extension CacheOverrideTag { - public static let none: CacheOverrideTag = [] - public static let pass = CacheOverrideTag(rawValue: 1 << 0) - public static let ttl = CacheOverrideTag(rawValue: 1 << 1) - public static let swr = CacheOverrideTag(rawValue: 1 << 2) - public static let pci = CacheOverrideTag(rawValue: 1 << 3) -} - -public enum CachePolicy: Sendable { - case origin - case pass - case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) -} - public typealias HeaderCount = Int32 public typealias IsDone = Int32 @@ -376,15 +353,6 @@ public enum BodyScanContinuation: Sendable { } public struct BackendConfigOptions: OptionSet, Sendable { - - public let rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } -} - -extension BackendConfigOptions { public static let reserved = BackendConfigOptions(rawValue: 1 << 0) public static let hostOverride = BackendConfigOptions(rawValue: 1 << 1) public static let connectTimeout = BackendConfigOptions(rawValue: 1 << 2) @@ -397,6 +365,81 @@ extension BackendConfigOptions { public static let caCert = BackendConfigOptions(rawValue: 1 << 9) public static let ciphers = BackendConfigOptions(rawValue: 1 << 10) public static let sniHostname = BackendConfigOptions(rawValue: 1 << 11) + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheOverrideTag: OptionSet { + public static let pass = CacheOverrideTag(rawValue: 1 << 0) + public static let ttl = CacheOverrideTag(rawValue: 1 << 1) + public static let swr = CacheOverrideTag(rawValue: 1 << 2) + public static let pci = CacheOverrideTag(rawValue: 1 << 3) + + public static let none: CacheOverrideTag = [] + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheState: OptionSet { + public static let found = CacheState(rawValue: 1 << 0) + public static let usable = CacheState(rawValue: 1 << 1) + public static let stale = CacheState(rawValue: 1 << 2) + public static let mustInsertOrUpdate = CacheState(rawValue: 1 << 3) + + public let rawValue: UInt8 + public init(rawValue: UInt8) { + self.rawValue = rawValue + } +} + +public enum CachePolicy: Sendable { + case origin + case pass + case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) +} + +public struct CacheWriteOptions: OptionSet, Sendable { + public static let reserved = CacheWriteOptions(rawValue: 1 << 0) + public static let requestHeaders = CacheWriteOptions(rawValue: 1 << 1) + public static let varyRule = CacheWriteOptions(rawValue: 1 << 2) + public static let initialAgeNs = CacheWriteOptions(rawValue: 1 << 3) + public static let staleWhileRevalidateNs = CacheWriteOptions(rawValue: 1 << 4) + public static let surrogateKeys = CacheWriteOptions(rawValue: 1 << 5) + public static let length = CacheWriteOptions(rawValue: 1 << 6) + public static let userMetadata = CacheWriteOptions(rawValue: 1 << 7) + public static let sensitiveData = CacheWriteOptions(rawValue: 1 << 8) + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheLookupOptions: OptionSet, Sendable { + public static let reserved = CacheLookupOptions(rawValue: 1 << 0) + public static let requestHeaders = CacheLookupOptions(rawValue: 1 << 1) + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } +} + +public struct CacheGetBodyOptions: OptionSet, Sendable { + public static let reserved = CacheLookupOptions(rawValue: 1 << 0) + public static let from = CacheLookupOptions(rawValue: 1 << 1) + public static let to = CacheLookupOptions(rawValue: 1 << 2) + + public let rawValue: UInt32 + public init(rawValue: UInt32) { + self.rawValue = rawValue + } } public let maxHeaderLength = 69000 diff --git a/Sources/ComputeRuntime/include/ComputeRuntime.h b/Sources/ComputeRuntime/include/ComputeRuntime.h index b210acf0..d7aadadd 100644 --- a/Sources/ComputeRuntime/include/ComputeRuntime.h +++ b/Sources/ComputeRuntime/include/ComputeRuntime.h @@ -19,24 +19,6 @@ typedef uint32_t WasiHandle; -typedef struct DynamicBackendConfig { - const char* host_override; - size_t host_override_len; - size_t connect_timeout_ms; - size_t first_byte_timeout_ms; - size_t between_bytes_timeout_ms; - size_t ssl_min_version; - size_t ssl_max_version; - const char* cert_hostname; - size_t cert_hostname_len; - const char* ca_cert; - size_t ca_cert_len; - const char* ciphers; - size_t ciphers_len; - const char* sni_hostname; - size_t sni_hostname_len; -} DynamicBackendConfig; - /* FASTLY_ABI */ WASM_IMPORT("fastly_abi", "init") @@ -211,6 +193,24 @@ int fastly_http_req__redirect_to_grip_proxy(const char *backend, size_t backend_ WASM_IMPORT("fastly_http_req", "downstream_tls_ja3_md5") int fastly_http_req__downstream_tls_ja3_md5(uint8_t *value, size_t *nwritten); +typedef struct DynamicBackendConfig { + const char* host_override; + size_t host_override_len; + size_t connect_timeout_ms; + size_t first_byte_timeout_ms; + size_t between_bytes_timeout_ms; + size_t ssl_min_version; + size_t ssl_max_version; + const char* cert_hostname; + size_t cert_hostname_len; + const char* ca_cert; + size_t ca_cert_len; + const char* ciphers; + size_t ciphers_len; + const char* sni_hostname; + size_t sni_hostname_len; +} DynamicBackendConfig; + WASM_IMPORT("fastly_http_req", "register_dynamic_backend") int fastly_http_req__register_dynamic_backend(const char *name, size_t name_len, @@ -267,5 +267,61 @@ int fastly_http_resp__framing_headers_mode_set(WasiHandle resp_handle, uint32_t WASM_IMPORT("fastly_http_resp", "http_keepalive_mode_set") int fastly_http_resp__http_keepalive_mode_set(WasiHandle resp_handle, uint32_t mode); +/* FASTLY_CACHE */ + +typedef struct CacheLookupConfig { + // * A full request handle, but used only for its headers + WasiHandle request_headers; +} CacheLookupConfig; + +typedef struct CacheGetBodyConfig { + uint64_t start; + uint64_t end; +} CacheGetBodyConfig; + +typedef struct CacheWriteConfig { + uint64_t max_age_ns; + uint32_t request_headers; + const uint8_t *vary_rule_ptr; + size_t vary_rule_len; + uint64_t initial_age_ns; + uint64_t stale_while_revalidate_ns; + const uint8_t *surrogate_keys_ptr; + size_t surrogate_keys_len; + uint64_t length; + const uint8_t *user_metadata_ptr; + size_t user_metadata_len; +} CacheWriteConfig; + +WASM_IMPORT("fastly_cache", "lookup") +int cache_lookup(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheLookupConfig *config, + WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "insert") +int fastly_cache__cache_insert(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheWriteConfig *config, WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "transaction_lookup") +int fastly_cache__cache_transaction_lookup(const char *cache_key, size_t cache_key_len, uint32_t options_mask, + CacheLookupConfig *config, + WasiHandle *ret); + +WASM_IMPORT("fastly_cache", "transaction_insert_and_stream_back") +int fastly_cache__cache_transaction_insert_and_stream_back(WasiHandle handle, uint32_t options_mask, CacheWriteConfig *config, + WasiHandle *ret_body, + WasiHandle *ret_cache); + +WASM_IMPORT("fastly_cache", "transaction_cancel") +int fastly_cache__cache_transaction_cancel(WasiHandle handle); + +WASM_IMPORT("fastly_cache", "get_state") +int fastly_cache__cache_get_state(WasiHandle handle, uint8_t *ret); + +WASM_IMPORT("fastly_cache", "get_body") +int fastly_cache__cache_get_body(WasiHandle handle, uint32_t options_mask, + CacheGetBodyConfig *config, + WasiHandle *ret); + #pragma GCC diagnostic pop #endif /* ComputeRuntime_h */ From 154825386b5cf2d74ef7cfa2aa2fe4ec4221cdcc Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 15:43:45 -0400 Subject: [PATCH 02/15] Cache policy --- Sources/Compute/Fastly/FastlyCache.swift | 66 +++++++++++++++--------- Sources/Compute/Fastly/FastlyTypes.swift | 22 ++++++++ 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 2828f890..10f16dfd 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -11,47 +11,51 @@ extension Fastly { public struct Cache: Sendable { - public static func getOrSet(_ key: String, _ handler: () async throws -> FetchResponse) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let body = try await handler().body.body - return HandlerData.body(body) + let (res, cachePolicy) = try await handler() + guard let header = res.headers[.contentLength], let length = Int(header) else { + let bytes = try await res.bytes() + return (HandlerData.bytes(bytes), .origin) + } + return await (HandlerData.body(res.body.body, length: length), cachePolicy) } } - public static func getOrSet(_ key: String, _ handler: () async throws -> [UInt8]) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let bytes = try await handler() - return HandlerData.bytes(bytes) + let (bytes, cachePolicy) = try await handler() + return (HandlerData.bytes(bytes), cachePolicy) } } - public static func getOrSet(_ key: String, _ handler: () async throws -> String) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let text = try await handler() - return HandlerData.bytes(.init(text.utf8)) + let (text, cachePolicy) = try await handler() + return (HandlerData.bytes(.init(text.utf8)), cachePolicy) } } - public static func getOrSet(_ key: String, _ handler: () async throws -> Data) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let data = try await handler() - return HandlerData.bytes(data.bytes) + let (data, cachePolicy) = try await handler() + return (HandlerData.bytes(data.bytes), cachePolicy) } } - public static func getOrSet(_ key: String, _ handler: () async throws -> [String: Sendable]) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let json = try await handler() + let (json, cachePolicy) = try await handler() let data = try JSONSerialization.data(withJSONObject: json) - return HandlerData.bytes(data.bytes) + return (HandlerData.bytes(data.bytes), cachePolicy) } } - public static func getOrSet(_ key: String, _ handler: () async throws -> [Sendable]) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { - let json = try await handler() + let (json, cachePolicy) = try await handler() let data = try JSONSerialization.data(withJSONObject: json) - return HandlerData.bytes(data.bytes) + return (HandlerData.bytes(data.bytes), cachePolicy) } } } @@ -60,11 +64,20 @@ extension Fastly { extension Fastly.Cache { internal enum HandlerData { - case body(_ body: Fastly.Body) + case body(_ body: Fastly.Body, length: Int) case bytes(_ bytes: [UInt8]) + + var length: Int { + switch self { + case .body(_, let length): + return length + case .bytes(let bytes): + return bytes.count + } + } } - internal static func getOrSet(_ key: String, _ handler: () async throws -> HandlerData) async throws -> ReadableBody { + internal static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> ReadableBody { // Open the transaction let trx = try await Transaction.lookup(key) @@ -78,14 +91,14 @@ extension Fastly.Cache { } // If its not usable then begin executing handler with new value - let data = try await handler() + let (data, cachePolicy) = try await handler() // Get an instance to the insert handle - var writer = try await trx.insertAndStreamBack() + var writer = try await trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) // Append bytes from handler to writeable body switch data { - case .body(let body): + case .body(let body, _): try writer.body.append(body) case .bytes(let bytes): try writer.body.write(bytes) @@ -113,11 +126,14 @@ extension Fastly.Cache { return Transaction(handle) } - internal func insertAndStreamBack() async throws -> (body: Fastly.Body, transaction: Transaction) { + internal func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) async throws -> (body: Fastly.Body, transaction: Transaction) { var bodyHandle: WasiHandle = 0 var cacheHandle: WasiHandle = 0 - let options = CacheWriteOptions.reserved + let options: CacheWriteOptions = [.initialAgeNs, .staleWhileRevalidateNs, .length] var config = CacheWriteConfig() + config.max_age_ns = .init(cachePolicy.ttl) * 1_000_000_000 + config.stale_while_revalidate_ns = .init(cachePolicy.staleWhileRevalidate) * 1_000_000_000 + config.length = .init(length) try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) return (.init(bodyHandle), .init(cacheHandle)) } diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index ca38949d..88e4347c 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -402,6 +402,28 @@ public enum CachePolicy: Sendable { case origin case pass case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) + + public var ttl: Int { + switch self { + case .origin: + return 0 + case .pass: + return 0 + case .ttl(let seconds, _, _): + return seconds + } + } + + public var staleWhileRevalidate: Int { + switch self { + case .origin: + return 0 + case .pass: + return 0 + case .ttl(_, let seconds, _): + return seconds + } + } } public struct CacheWriteOptions: OptionSet, Sendable { From b5d705cc495880681c2641b4dc2b040d9e0be345 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 15:48:18 -0400 Subject: [PATCH 03/15] Cleanup names --- Sources/Compute/Fastly/FastlyCache.swift | 4 ++-- Sources/Compute/Fastly/FastlyTypes.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 10f16dfd..ec85516a 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -131,8 +131,8 @@ extension Fastly.Cache { var cacheHandle: WasiHandle = 0 let options: CacheWriteOptions = [.initialAgeNs, .staleWhileRevalidateNs, .length] var config = CacheWriteConfig() - config.max_age_ns = .init(cachePolicy.ttl) * 1_000_000_000 - config.stale_while_revalidate_ns = .init(cachePolicy.staleWhileRevalidate) * 1_000_000_000 + config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000 + config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 config.length = .init(length) try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) return (.init(bodyHandle), .init(cacheHandle)) diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index 88e4347c..c73cd4a4 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -403,7 +403,7 @@ public enum CachePolicy: Sendable { case pass case ttl(_ seconds: Int, staleWhileRevalidate: Int = 0, pciCompliant: Bool = false) - public var ttl: Int { + public var maxAge: Int { switch self { case .origin: return 0 @@ -414,7 +414,7 @@ public enum CachePolicy: Sendable { } } - public var staleWhileRevalidate: Int { + public var staleMaxAge: Int { switch self { case .origin: return 0 From ea78f67967123d4ad6430cc84fce4d9f7305fdc0 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 16:11:58 -0400 Subject: [PATCH 04/15] Fix return policy --- Sources/Compute/Fastly/FastlyCache.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index ec85516a..210f9ef7 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -16,7 +16,7 @@ extension Fastly { let (res, cachePolicy) = try await handler() guard let header = res.headers[.contentLength], let length = Int(header) else { let bytes = try await res.bytes() - return (HandlerData.bytes(bytes), .origin) + return (HandlerData.bytes(bytes), cachePolicy) } return await (HandlerData.body(res.body.body, length: length), cachePolicy) } From f36f4183ae5b136b1b1a5824a8182ad7c0770e5b Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 16:14:02 -0400 Subject: [PATCH 05/15] Use short syntax --- Sources/Compute/Fastly/FastlyCache.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 210f9ef7..99a5d775 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -16,30 +16,30 @@ extension Fastly { let (res, cachePolicy) = try await handler() guard let header = res.headers[.contentLength], let length = Int(header) else { let bytes = try await res.bytes() - return (HandlerData.bytes(bytes), cachePolicy) + return (.bytes(bytes), cachePolicy) } - return await (HandlerData.body(res.body.body, length: length), cachePolicy) + return await (.body(res.body.body, length: length), cachePolicy) } } public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { let (bytes, cachePolicy) = try await handler() - return (HandlerData.bytes(bytes), cachePolicy) + return (.bytes(bytes), cachePolicy) } } public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { let (text, cachePolicy) = try await handler() - return (HandlerData.bytes(.init(text.utf8)), cachePolicy) + return (.bytes(.init(text.utf8)), cachePolicy) } } public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> ReadableBody { return try await getOrSet(key) { let (data, cachePolicy) = try await handler() - return (HandlerData.bytes(data.bytes), cachePolicy) + return (.bytes(data.bytes), cachePolicy) } } @@ -47,7 +47,7 @@ extension Fastly { return try await getOrSet(key) { let (json, cachePolicy) = try await handler() let data = try JSONSerialization.data(withJSONObject: json) - return (HandlerData.bytes(data.bytes), cachePolicy) + return (.bytes(data.bytes), cachePolicy) } } @@ -55,7 +55,7 @@ extension Fastly { return try await getOrSet(key) { let (json, cachePolicy) = try await handler() let data = try JSONSerialization.data(withJSONObject: json) - return (HandlerData.bytes(data.bytes), cachePolicy) + return (.bytes(data.bytes), cachePolicy) } } } From 5b6356e566ff3b79b3a99b87c0fa6b7adca3197f Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 16:24:43 -0400 Subject: [PATCH 06/15] Cancel trx on error --- Sources/Compute/Fastly/FastlyCache.swift | 50 ++++++++++++++---------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 99a5d775..b6d55248 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -63,11 +63,11 @@ extension Fastly { extension Fastly.Cache { - internal enum HandlerData { + public enum HandlerData { case body(_ body: Fastly.Body, length: Int) case bytes(_ bytes: [UInt8]) - var length: Int { + public var length: Int { switch self { case .body(_, let length): return length @@ -77,7 +77,7 @@ extension Fastly.Cache { } } - internal static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> ReadableBody { + public static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> ReadableBody { // Open the transaction let trx = try await Transaction.lookup(key) @@ -90,22 +90,30 @@ extension Fastly.Cache { return ReadableWasiBody(body) } - // If its not usable then begin executing handler with new value - let (data, cachePolicy) = try await handler() + do { + // If its not usable then begin executing handler with new value + let (data, cachePolicy) = try await handler() - // Get an instance to the insert handle - var writer = try await trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) + // Get an instance to the insert handle + var writer = try await trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) - // Append bytes from handler to writeable body - switch data { - case .body(let body, _): - try writer.body.append(body) - case .bytes(let bytes): - try writer.body.write(bytes) - } + // Append bytes from handler to writeable body + switch data { + case .body(let body, _): + try writer.body.append(body) + case .bytes(let bytes): + try writer.body.write(bytes) + } - // Get handle to reabable body out from cache - return try await ReadableWasiBody(writer.transaction.getBody()) + // Get handle to reabable body out from cache + return try await ReadableWasiBody(writer.transaction.getBody()) + } catch { + // Cancel the transaction if something went wrong + try await trx.cancel() + + // Rethrow original error + throw error + } } } @@ -118,7 +126,7 @@ extension Fastly.Cache { self.handle = handle } - internal static func lookup(_ key: String) async throws -> Transaction { + public static func lookup(_ key: String) async throws -> Transaction { var handle: WasiHandle = 0 let options = CacheLookupOptions.reserved var config = CacheLookupConfig() @@ -126,7 +134,7 @@ extension Fastly.Cache { return Transaction(handle) } - internal func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) async throws -> (body: Fastly.Body, transaction: Transaction) { + public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) async throws -> (body: Fastly.Body, transaction: Transaction) { var bodyHandle: WasiHandle = 0 var cacheHandle: WasiHandle = 0 let options: CacheWriteOptions = [.initialAgeNs, .staleWhileRevalidateNs, .length] @@ -138,13 +146,13 @@ extension Fastly.Cache { return (.init(bodyHandle), .init(cacheHandle)) } - internal func getState() async throws -> CacheState { + public func getState() async throws -> CacheState { var value: UInt8 = 0 try wasi(fastly_cache__cache_get_state(handle, &value)) return CacheState(rawValue: value) } - internal func getBody() async throws -> Fastly.Body { + public func getBody() async throws -> Fastly.Body { var bodyHandle: WasiHandle = 0 let options = CacheGetBodyOptions.reserved var config = CacheGetBodyConfig() @@ -152,7 +160,7 @@ extension Fastly.Cache { return .init(bodyHandle) } - internal func cancel() async throws { + public func cancel() async throws { try wasi(fastly_cache__cache_transaction_cancel(handle)) } } From 806dd930daf683b09f7aa5ac53db5af34b8d2b7a Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 17:11:30 -0400 Subject: [PATCH 07/15] Move to public Cache --- Sources/Compute/Cache.swift | 84 +++++++++++ Sources/Compute/Fastly/FastlyCache.swift | 134 +++++++----------- .../ComputeRuntime/include/ComputeRuntime.h | 9 ++ 3 files changed, 143 insertions(+), 84 deletions(-) create mode 100644 Sources/Compute/Cache.swift diff --git a/Sources/Compute/Cache.swift b/Sources/Compute/Cache.swift new file mode 100644 index 00000000..a3e00469 --- /dev/null +++ b/Sources/Compute/Cache.swift @@ -0,0 +1,84 @@ +// +// Cache.swift +// +// +// Created by Andrew Barba on 9/13/23. +// + +public struct Cache: Sendable { + + public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (res, cachePolicy) = try await handler() + guard let header = res.headers[.contentLength], let length = Int(header) else { + let bytes = try await res.bytes() + return (.bytes(bytes), cachePolicy) + } + return await (.body(res.body.body, length: length), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (bytes, cachePolicy) = try await handler() + return (.bytes(bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (text, cachePolicy) = try await handler() + return (.bytes(.init(text.utf8)), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (data, cachePolicy) = try await handler() + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (json, cachePolicy) = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } + + public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> Entry { + let trx = try await Fastly.Cache.getOrSet(key) { + let (json, cachePolicy) = try await handler() + let data = try JSONSerialization.data(withJSONObject: json) + return (.bytes(data.bytes), cachePolicy) + } + return try .init(trx) + } +} + +extension Cache { + + public struct Entry: Sendable { + + public let body: ReadableBody + + public let age: Int + + public let hits: Int + + public let contentLength: Int + + internal init(_ trx: Fastly.Cache.Transaction) throws { + self.body = try ReadableWasiBody(trx.getBody()) + self.age = try trx.getAge() + self.hits = try trx.getHits() + self.contentLength = try trx.getLength() + } + } +} diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index b6d55248..0b67024a 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -8,61 +8,48 @@ import ComputeRuntime extension Fastly { - public struct Cache: Sendable { - public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { - let (res, cachePolicy) = try await handler() - guard let header = res.headers[.contentLength], let length = Int(header) else { - let bytes = try await res.bytes() - return (.bytes(bytes), cachePolicy) - } - return await (.body(res.body.body, length: length), cachePolicy) - } - } + public static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> Transaction { + // Open the transaction + let trx = try Transaction.lookup(key) - public static func getOrSet(_ key: String, _ handler: () async throws -> ([UInt8], CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { - let (bytes, cachePolicy) = try await handler() - return (.bytes(bytes), cachePolicy) - } - } + // Get current transaction state + let state = try trx.getState() - public static func getOrSet(_ key: String, _ handler: () async throws -> (String, CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { - let (text, cachePolicy) = try await handler() - return (.bytes(.init(text.utf8)), cachePolicy) + // If state is usable then return the body from cache + if state == .found || state == .usable { + return trx } - } - public static func getOrSet(_ key: String, _ handler: () async throws -> (Data, CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { + do { + // If its not usable then begin executing handler with new value let (data, cachePolicy) = try await handler() - return (.bytes(data.bytes), cachePolicy) - } - } - public static func getOrSet(_ key: String, _ handler: () async throws -> ([String: Sendable], CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { - let (json, cachePolicy) = try await handler() - let data = try JSONSerialization.data(withJSONObject: json) - return (.bytes(data.bytes), cachePolicy) - } - } + // Get an instance to the insert handle + var writer = try trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) + + // Append bytes from handler to writeable body + switch data { + case .body(let body, _): + try writer.body.append(body) + case .bytes(let bytes): + try writer.body.write(bytes) + } - public static func getOrSet(_ key: String, _ handler: () async throws -> ([Sendable], CachePolicy)) async throws -> ReadableBody { - return try await getOrSet(key) { - let (json, cachePolicy) = try await handler() - let data = try JSONSerialization.data(withJSONObject: json) - return (.bytes(data.bytes), cachePolicy) + return writer.transaction + } catch { + // Cancel the transaction if something went wrong + try trx.cancel() + + // Rethrow original error + throw error } } } } extension Fastly.Cache { - public enum HandlerData { case body(_ body: Fastly.Body, length: Int) case bytes(_ bytes: [UInt8]) @@ -76,45 +63,6 @@ extension Fastly.Cache { } } } - - public static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> ReadableBody { - // Open the transaction - let trx = try await Transaction.lookup(key) - - // Get current transaction state - let state = try await trx.getState() - - // If state is usable then return the body from cache - if state == .found || state == .usable { - let body = try await trx.getBody() - return ReadableWasiBody(body) - } - - do { - // If its not usable then begin executing handler with new value - let (data, cachePolicy) = try await handler() - - // Get an instance to the insert handle - var writer = try await trx.insertAndStreamBack(cachePolicy: cachePolicy, length: data.length) - - // Append bytes from handler to writeable body - switch data { - case .body(let body, _): - try writer.body.append(body) - case .bytes(let bytes): - try writer.body.write(bytes) - } - - // Get handle to reabable body out from cache - return try await ReadableWasiBody(writer.transaction.getBody()) - } catch { - // Cancel the transaction if something went wrong - try await trx.cancel() - - // Rethrow original error - throw error - } - } } extension Fastly.Cache { @@ -126,7 +74,7 @@ extension Fastly.Cache { self.handle = handle } - public static func lookup(_ key: String) async throws -> Transaction { + public static func lookup(_ key: String) throws -> Transaction { var handle: WasiHandle = 0 let options = CacheLookupOptions.reserved var config = CacheLookupConfig() @@ -134,7 +82,7 @@ extension Fastly.Cache { return Transaction(handle) } - public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) async throws -> (body: Fastly.Body, transaction: Transaction) { + public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) throws -> (body: Fastly.Body, transaction: Transaction) { var bodyHandle: WasiHandle = 0 var cacheHandle: WasiHandle = 0 let options: CacheWriteOptions = [.initialAgeNs, .staleWhileRevalidateNs, .length] @@ -146,13 +94,31 @@ extension Fastly.Cache { return (.init(bodyHandle), .init(cacheHandle)) } - public func getState() async throws -> CacheState { + public func getState() throws -> CacheState { var value: UInt8 = 0 try wasi(fastly_cache__cache_get_state(handle, &value)) return CacheState(rawValue: value) } - public func getBody() async throws -> Fastly.Body { + public func getAge() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_age_ns(handle, &value)) + return .init(value / 1_000_000_000) + } + + public func getLength() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_length(handle, &value)) + return .init(value) + } + + public func getHits() throws -> Int { + var value: UInt64 = 0 + try wasi(fastly_cache__cache_get_hits(handle, &value)) + return .init(value) + } + + public func getBody() throws -> Fastly.Body { var bodyHandle: WasiHandle = 0 let options = CacheGetBodyOptions.reserved var config = CacheGetBodyConfig() @@ -160,7 +126,7 @@ extension Fastly.Cache { return .init(bodyHandle) } - public func cancel() async throws { + public func cancel() throws { try wasi(fastly_cache__cache_transaction_cancel(handle)) } } diff --git a/Sources/ComputeRuntime/include/ComputeRuntime.h b/Sources/ComputeRuntime/include/ComputeRuntime.h index d7aadadd..e6eadf8f 100644 --- a/Sources/ComputeRuntime/include/ComputeRuntime.h +++ b/Sources/ComputeRuntime/include/ComputeRuntime.h @@ -318,6 +318,15 @@ int fastly_cache__cache_transaction_cancel(WasiHandle handle); WASM_IMPORT("fastly_cache", "get_state") int fastly_cache__cache_get_state(WasiHandle handle, uint8_t *ret); +WASM_IMPORT("fastly_cache", "get_length") +int fastly_cache__cache_get_length(WasiHandle handle, uint64_t *ret); + +WASM_IMPORT("fastly_cache", "get_age_ns") +int fastly_cache__cache_get_age_ns(WasiHandle handle, uint64_t *ret); + +WASM_IMPORT("fastly_cache", "get_hits") +int fastly_cache__cache_get_hits(WasiHandle handle, uint64_t *ret); + WASM_IMPORT("fastly_cache", "get_body") int fastly_cache__cache_get_body(WasiHandle handle, uint32_t options_mask, CacheGetBodyConfig *config, From dfe99c9f5b3753ecf72fb196b686420da4b9ed48 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 17:20:12 -0400 Subject: [PATCH 08/15] Fix usable --- Sources/Compute/Fastly/FastlyCache.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 0b67024a..7d20fb48 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -18,7 +18,7 @@ extension Fastly { let state = try trx.getState() // If state is usable then return the body from cache - if state == .found || state == .usable { + if state.contains(.usable) { return trx } From 56a0ba640fde38c91b25736b3278d7660625b1f4 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 17:25:59 -0400 Subject: [PATCH 09/15] Use empty options set --- Sources/Compute/Fastly/FastlyCache.swift | 4 ++-- Sources/Compute/Fastly/FastlyTypes.swift | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index 7d20fb48..acd75b0e 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -76,7 +76,7 @@ extension Fastly.Cache { public static func lookup(_ key: String) throws -> Transaction { var handle: WasiHandle = 0 - let options = CacheLookupOptions.reserved + let options = CacheLookupOptions.none var config = CacheLookupConfig() try wasi(fastly_cache__cache_transaction_lookup(key, key.utf8.count, options.rawValue, &config, &handle)) return Transaction(handle) @@ -120,7 +120,7 @@ extension Fastly.Cache { public func getBody() throws -> Fastly.Body { var bodyHandle: WasiHandle = 0 - let options = CacheGetBodyOptions.reserved + let options = CacheGetBodyOptions.none var config = CacheGetBodyConfig() try wasi(fastly_cache__cache_get_body(handle, options.rawValue, &config, &bodyHandle)) return .init(bodyHandle) diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index c73cd4a4..4905766a 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -392,6 +392,8 @@ public struct CacheState: OptionSet { public static let stale = CacheState(rawValue: 1 << 2) public static let mustInsertOrUpdate = CacheState(rawValue: 1 << 3) + public static let none: CacheState = [] + public let rawValue: UInt8 public init(rawValue: UInt8) { self.rawValue = rawValue @@ -437,6 +439,8 @@ public struct CacheWriteOptions: OptionSet, Sendable { public static let userMetadata = CacheWriteOptions(rawValue: 1 << 7) public static let sensitiveData = CacheWriteOptions(rawValue: 1 << 8) + public static let none: CacheWriteOptions = [] + public let rawValue: UInt32 public init(rawValue: UInt32) { self.rawValue = rawValue @@ -447,6 +451,8 @@ public struct CacheLookupOptions: OptionSet, Sendable { public static let reserved = CacheLookupOptions(rawValue: 1 << 0) public static let requestHeaders = CacheLookupOptions(rawValue: 1 << 1) + public static let none: CacheLookupOptions = [] + public let rawValue: UInt32 public init(rawValue: UInt32) { self.rawValue = rawValue @@ -458,6 +464,8 @@ public struct CacheGetBodyOptions: OptionSet, Sendable { public static let from = CacheLookupOptions(rawValue: 1 << 1) public static let to = CacheLookupOptions(rawValue: 1 << 2) + public static let none: CacheGetBodyOptions = [] + public let rawValue: UInt32 public init(rawValue: UInt32) { self.rawValue = rawValue From 94c1cc4d9e7fc39c2484b7b57da929156a74d5ee Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 13 Sep 2023 17:40:38 -0400 Subject: [PATCH 10/15] Deps --- Package.resolved | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.resolved b/Package.resolved index cc9085cf..7914bd79 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-crypto", "state" : { - "revision" : "33a20e650c33f6d72d822d558333f2085effa3dc", - "version" : "2.5.0" + "revision" : "60f13f60c4d093691934dc6cfdf5f508ada1f894", + "version" : "2.6.0" } } ], From 956fc626115ffb4a513d4dfead747f79e2f54ca9 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Fri, 22 Sep 2023 10:53:46 -0400 Subject: [PATCH 11/15] Add stubs --- Sources/Compute/Fastly/FastlyStubs.swift | 62 ++++++++++++++++++------ Sources/Compute/JWT/JWT.swift | 3 +- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/Sources/Compute/Fastly/FastlyStubs.swift b/Sources/Compute/Fastly/FastlyStubs.swift index f2d1d475..51d0a093 100644 --- a/Sources/Compute/Fastly/FastlyStubs.swift +++ b/Sources/Compute/Fastly/FastlyStubs.swift @@ -11,22 +11,7 @@ /// avoid link failure. /// When running with Compute runtime library, they are ignored completely. #if !arch(wasm32) - -/* TYPES */ - -struct DynamicBackendConfig: Sendable { - var host_override: UnsafePointer! = nil - var host_override_len: Int = 0 - var connect_timeout_ms: Int = 0 - var first_byte_timeout_ms: Int = 0 - var between_bytes_timeout_ms: Int = 0 - var ssl_min_version: Int = 0 - var ssl_max_version: Int = 0 - var cert_hostname: UnsafePointer! = nil - var cert_hostname_len: Int = 0 - var sni_hostname: UnsafePointer! = nil - var sni_hostname_len: Int = 0 -} +import ComputeRuntime /* FASTLY_ABI */ @@ -170,4 +155,49 @@ func fastly_http_resp__framing_headers_mode_set(_ resp_handle: WasiHandle, _ mod func fastly_http_resp__http_keepalive_mode_set(_ resp_handle: WasiHandle, _ mode: UInt32) -> Int32 { fatalError() } +func fastly_cache__cache_transaction_lookup( + _ cache_key: UnsafePointer!, + _ cache_key_len: Int, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_transaction_insert_and_stream_back( + _ handle: WasiHandle, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret_body: UnsafeMutablePointer!, + _ ret_cache: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_state( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_age_ns( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_length( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_get_hits( + _ handle: WasiHandle, + _ ret: UnsafeMutablePointer! +) -> Int32{ fatalError() } + +func fastly_cache__cache_get_body( + _ handle: WasiHandle, + _ options_mask: UInt32, + _ config: UnsafeMutablePointer!, + _ ret: UnsafeMutablePointer! +) -> Int32 { fatalError() } + +func fastly_cache__cache_transaction_cancel(_ handle: WasiHandle) -> Int32 { fatalError() } + #endif diff --git a/Sources/Compute/JWT/JWT.swift b/Sources/Compute/JWT/JWT.swift index da0f62fc..9b0bd708 100644 --- a/Sources/Compute/JWT/JWT.swift +++ b/Sources/Compute/JWT/JWT.swift @@ -151,6 +151,7 @@ extension JWT { @discardableResult public func verify( key: String, + using algorithm: Algorithm? = nil, issuer: String? = nil, subject: String? = nil, expiration: Bool = true @@ -159,7 +160,7 @@ extension JWT { let input = token.components(separatedBy: ".").prefix(2).joined(separator: ".") // Ensure the signatures match - try verifySignature(input, signature: signature, key: key, using: algorithm) + try verifySignature(input, signature: signature, key: key, using: algorithm ?? self.algorithm) // Ensure the jwt is not expired if expiration, self.expired == true { From 4a328a47e1efd188251ccc0bd19d034fb3a11c8b Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Fri, 22 Sep 2023 11:17:51 -0400 Subject: [PATCH 12/15] Add cache state --- Sources/Compute/Cache.swift | 3 +++ Sources/Compute/Fastly/FastlyTypes.swift | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/Compute/Cache.swift b/Sources/Compute/Cache.swift index a3e00469..da1cc462 100644 --- a/Sources/Compute/Cache.swift +++ b/Sources/Compute/Cache.swift @@ -74,11 +74,14 @@ extension Cache { public let contentLength: Int + public let state: CacheState + internal init(_ trx: Fastly.Cache.Transaction) throws { self.body = try ReadableWasiBody(trx.getBody()) self.age = try trx.getAge() self.hits = try trx.getHits() self.contentLength = try trx.getLength() + self.state = try trx.getState() } } } diff --git a/Sources/Compute/Fastly/FastlyTypes.swift b/Sources/Compute/Fastly/FastlyTypes.swift index 4905766a..24c9bd0d 100644 --- a/Sources/Compute/Fastly/FastlyTypes.swift +++ b/Sources/Compute/Fastly/FastlyTypes.swift @@ -372,7 +372,7 @@ public struct BackendConfigOptions: OptionSet, Sendable { } } -public struct CacheOverrideTag: OptionSet { +public struct CacheOverrideTag: OptionSet, Sendable { public static let pass = CacheOverrideTag(rawValue: 1 << 0) public static let ttl = CacheOverrideTag(rawValue: 1 << 1) public static let swr = CacheOverrideTag(rawValue: 1 << 2) @@ -386,7 +386,7 @@ public struct CacheOverrideTag: OptionSet { } } -public struct CacheState: OptionSet { +public struct CacheState: OptionSet, Sendable { public static let found = CacheState(rawValue: 1 << 0) public static let usable = CacheState(rawValue: 1 << 1) public static let stale = CacheState(rawValue: 1 << 2) From ffc4a3e8f4ae8d632e1c1de069f68aadb5630995 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 27 Sep 2023 16:16:42 -0400 Subject: [PATCH 13/15] Fix initial age --- Sources/Compute/Fastly/FastlyCache.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index acd75b0e..f38865c5 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -85,7 +85,7 @@ extension Fastly.Cache { public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) throws -> (body: Fastly.Body, transaction: Transaction) { var bodyHandle: WasiHandle = 0 var cacheHandle: WasiHandle = 0 - let options: CacheWriteOptions = [.initialAgeNs, .staleWhileRevalidateNs, .length] + let options: CacheWriteOptions = [.staleWhileRevalidateNs, .length] var config = CacheWriteConfig() config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000 config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 From a3f736e31b06092a9ed07f288efbd28a53a6c2da Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Wed, 27 Sep 2023 16:46:21 -0400 Subject: [PATCH 14/15] Try closing writer --- Sources/Compute/Fastly/FastlyBody.swift | 4 ++++ Sources/Compute/Fastly/FastlyCache.swift | 5 ++++- Sources/Compute/Fastly/FastlyStubs.swift | 2 ++ Sources/ComputeRuntime/include/ComputeRuntime.h | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/Compute/Fastly/FastlyBody.swift b/Sources/Compute/Fastly/FastlyBody.swift index 2aaf5991..e6015007 100644 --- a/Sources/Compute/Fastly/FastlyBody.swift +++ b/Sources/Compute/Fastly/FastlyBody.swift @@ -33,6 +33,10 @@ extension Fastly { try wasi(fastly_http_body__close(handle)) } + public mutating func abandon() throws { + try wasi(fastly_http_body__abandon(handle)) + } + @discardableResult public mutating func write(_ bytes: [UInt8], location: BodyWriteEnd = .back) throws -> Int { defer { used = true } diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index f38865c5..a9f1dd1a 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -18,7 +18,7 @@ extension Fastly { let state = try trx.getState() // If state is usable then return the body from cache - if state.contains(.usable) { + guard state.contains(.mustInsertOrUpdate) else { return trx } @@ -37,6 +37,9 @@ extension Fastly { try writer.body.write(bytes) } + // Finish the body + try writer.body.close() + return writer.transaction } catch { // Cancel the transaction if something went wrong diff --git a/Sources/Compute/Fastly/FastlyStubs.swift b/Sources/Compute/Fastly/FastlyStubs.swift index 51d0a093..ae89ef88 100644 --- a/Sources/Compute/Fastly/FastlyStubs.swift +++ b/Sources/Compute/Fastly/FastlyStubs.swift @@ -57,6 +57,8 @@ func fastly_http_body__append(_ dest: WasiHandle, _ src: WasiHandle) -> Int32 { func fastly_http_body__close(_ handle: WasiHandle) -> Int32 { fatalError() } +func fastly_http_body__abandon(_ handle: WasiHandle) -> Int32 { fatalError() } + func fastly_http_body__write(_ handle: WasiHandle, _ data: UnsafePointer!, _ data_len: Int, _ body_end: Int32, _ nwritten: UnsafeMutablePointer!) -> Int32 { fatalError() } func fastly_http_body__read(_ handle: WasiHandle, _ data: UnsafeMutablePointer!, _ data_max_len: Int, _ nwritten: UnsafeMutablePointer!) -> Int32 { fatalError() } diff --git a/Sources/ComputeRuntime/include/ComputeRuntime.h b/Sources/ComputeRuntime/include/ComputeRuntime.h index e6eadf8f..864243b0 100644 --- a/Sources/ComputeRuntime/include/ComputeRuntime.h +++ b/Sources/ComputeRuntime/include/ComputeRuntime.h @@ -78,6 +78,9 @@ int fastly_http_body__append(WasiHandle dest, WasiHandle src); WASM_IMPORT("fastly_http_body", "close") int fastly_http_body__close(WasiHandle handle); +WASM_IMPORT("fastly_http_body", "abandon") +int fastly_http_body__abandon(WasiHandle handle); + WASM_IMPORT("fastly_http_body", "write") int fastly_http_body__write(WasiHandle handle, const uint8_t* data, size_t data_len, int body_end, size_t* nwritten); From fea2fe1f98ab3020f58d7747f07630187f6c7c86 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Thu, 28 Sep 2023 19:34:55 -0400 Subject: [PATCH 15/15] Optional length --- Sources/Compute/Cache.swift | 5 +---- Sources/Compute/Fastly/FastlyCache.swift | 22 +++++++++++++++------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Sources/Compute/Cache.swift b/Sources/Compute/Cache.swift index da1cc462..ed627553 100644 --- a/Sources/Compute/Cache.swift +++ b/Sources/Compute/Cache.swift @@ -10,10 +10,7 @@ public struct Cache: Sendable { public static func getOrSet(_ key: String, _ handler: () async throws -> (FetchResponse, CachePolicy)) async throws -> Entry { let trx = try await Fastly.Cache.getOrSet(key) { let (res, cachePolicy) = try await handler() - guard let header = res.headers[.contentLength], let length = Int(header) else { - let bytes = try await res.bytes() - return (.bytes(bytes), cachePolicy) - } + let length = res.headers[.contentLength].flatMap(Int.init) return await (.body(res.body.body, length: length), cachePolicy) } return try .init(trx) diff --git a/Sources/Compute/Fastly/FastlyCache.swift b/Sources/Compute/Fastly/FastlyCache.swift index a9f1dd1a..13d08687 100644 --- a/Sources/Compute/Fastly/FastlyCache.swift +++ b/Sources/Compute/Fastly/FastlyCache.swift @@ -10,7 +10,9 @@ import ComputeRuntime extension Fastly { public struct Cache: Sendable { - public static func getOrSet(_ key: String, _ handler: () async throws -> (HandlerData, CachePolicy)) async throws -> Transaction { + public typealias InsertResult = (data: HandlerData, cachePolicy: CachePolicy) + + public static func getOrSet(_ key: String, _ handler: () async throws -> InsertResult) async throws -> Transaction { // Open the transaction let trx = try Transaction.lookup(key) @@ -54,10 +56,10 @@ extension Fastly { extension Fastly.Cache { public enum HandlerData { - case body(_ body: Fastly.Body, length: Int) + case body(_ body: Fastly.Body, length: Int? = nil) case bytes(_ bytes: [UInt8]) - public var length: Int { + public var length: Int? { switch self { case .body(_, let length): return length @@ -85,14 +87,20 @@ extension Fastly.Cache { return Transaction(handle) } - public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int) throws -> (body: Fastly.Body, transaction: Transaction) { + public func insertAndStreamBack(cachePolicy: CachePolicy, length: Int?) throws -> (body: Fastly.Body, transaction: Transaction) { var bodyHandle: WasiHandle = 0 var cacheHandle: WasiHandle = 0 - let options: CacheWriteOptions = [.staleWhileRevalidateNs, .length] + var options: CacheWriteOptions = [] var config = CacheWriteConfig() config.max_age_ns = .init(cachePolicy.maxAge) * 1_000_000_000 - config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 - config.length = .init(length) + if cachePolicy.staleMaxAge > 0 { + options.insert(.staleWhileRevalidateNs) + config.stale_while_revalidate_ns = .init(cachePolicy.staleMaxAge) * 1_000_000_000 + } + if let length { + options.insert(.length) + config.length = .init(length) + } try wasi(fastly_cache__cache_transaction_insert_and_stream_back(handle, options.rawValue, &config, &bodyHandle, &cacheHandle)) return (.init(bodyHandle), .init(cacheHandle)) }