File
Blob: src/workerd/server/fallback-service.h
| 1 | #pragma once |
| 2 | |
| 3 | #include <workerd/server/workerd.capnp.h> |
| 4 | |
| 5 | #include <kj/common.h> |
| 6 | #include <kj/map.h> |
| 7 | #include <kj/mutex.h> |
| 8 | #include <kj/one-of.h> |
| 9 | #include <kj/string.h> |
| 10 | #include <kj/thread.h> |
| 11 | |
| 12 | namespace workerd::fallback { |
| 13 | |
| 14 | // The fallback service is a mechanism used only in workerd local development. |
| 15 | // It is used to use an external http service to resolve module specifiers |
| 16 | // dynamically if the module is not found in the static bundles. A worker |
| 17 | // must be configured to use the fallback service and workerd must be started |
| 18 | // with the --experimental CLI flag. |
| 19 | // |
| 20 | // There are two versions of the fallback service protocol: |
| 21 | // |
| 22 | // V1: The request is sent to the fallback service as a GET request using |
| 23 | // query strings to pass the details. The specifier and referrer are treated |
| 24 | // as strings. Import attributes are not included. |
| 25 | // |
| 26 | // V2: The request is sent to the fallback service as a POST request using |
| 27 | // JSON to pass the details. The specifier and referrer are treated as URLs. |
| 28 | // Import attributes are included. |
| 29 | // |
| 30 | // The fallback service may return either a JSON string describing the module |
| 31 | // configuration, a 301 redirect to a different module specifier, or an error. |
| 32 | enum class ImportType { |
| 33 | // The import is a static or dynamic import |
| 34 | IMPORT, |
| 35 | // The import is a CommonJs-style require() |
| 36 | REQUIRE, |
| 37 | // The import originated from inside the runtime |
| 38 | INTERNAL, |
| 39 | }; |
| 40 | |
| 41 | enum class Version { |
| 42 | // With V1 of the fallback service, the request is sent to the fallback |
| 43 | // service as a GET request using query strings to pass the details. |
| 44 | // The specifier and referrer are treated as strings. Import attributes |
| 45 | // are not included. |
| 46 | V1, |
| 47 | // With V2 of the fallback service, the request is sent to the fallback |
| 48 | // service as a POST request using JSON to pass the details. The specifier |
| 49 | // and referrer are treated as URLs. Import attributes are included. |
| 50 | V2, |
| 51 | }; |
| 52 | |
| 53 | using ModuleOrRedirect = |
| 54 | kj::Maybe<kj::OneOf<kj::String, kj::Own<server::config::Worker::Module::Reader>>>; |
| 55 | |
| 56 | // A persistent client for the fallback service that uses a single background |
| 57 | // thread with a long-lived HTTP client for all module resolution requests. |
| 58 | // This avoids creating a new OS thread, DNS lookup, and TCP connection for |
| 59 | // each request, which can exhaust ephemeral ports when many modules are |
| 60 | // resolved concurrently (e.g. running many test files with vitest-pool-workers). |
| 61 | // |
| 62 | // IMPORTANT: This class supports only one caller at a time. tryResolve() will |
| 63 | // assert if called concurrently. Module resolution in workerd is single-threaded |
| 64 | // per isolate/registry so this is safe in practice. |
| 65 | class FallbackServiceClient { |
| 66 | public: |
| 67 | explicit FallbackServiceClient(kj::String address); |
| 68 | ~FallbackServiceClient() noexcept(false); |
| 69 | |
| 70 | KJ_DISALLOW_COPY_AND_MOVE(FallbackServiceClient); |
| 71 | |
| 72 | ModuleOrRedirect tryResolve(Version version, |
| 73 | ImportType type, |
| 74 | kj::StringPtr specifier, |
| 75 | kj::StringPtr rawSpecifier, |
| 76 | kj::StringPtr referrer, |
| 77 | const kj::HashMap<kj::StringPtr, kj::StringPtr>& attributes); |
| 78 | |
| 79 | private: |
| 80 | // Shared state between the calling thread and the background thread. |
| 81 | // Access is serialized through kj::MutexGuarded. |
| 82 | struct SharedState { |
| 83 | // Request fields - valid only when hasRequest is true. |
| 84 | // These contain non-owning references into the caller's stack frame, |
| 85 | // which is safe because the caller blocks until responseReady is set. |
| 86 | Version version = Version::V1; |
| 87 | ImportType type = ImportType::IMPORT; |
| 88 | kj::StringPtr specifier; |
| 89 | kj::StringPtr rawSpecifier; |
| 90 | kj::StringPtr referrer; |
| 91 | const kj::HashMap<kj::StringPtr, kj::StringPtr>* attributes = nullptr; |
| 92 | bool hasRequest = false; |
| 93 | |
| 94 | // Response field - valid only when responseReady is true. |
| 95 | ModuleOrRedirect response; |
| 96 | bool responseReady = false; |
| 97 | |
| 98 | // Set to true to signal the background thread to exit. |
| 99 | bool shutdown = false; |
| 100 | }; |
| 101 | |
| 102 | kj::String ownedAddress; |
| 103 | kj::MutexGuarded<SharedState> state; |
| 104 | kj::Thread thread; |
| 105 | |
| 106 | void threadMain(); |
| 107 | }; |
| 108 | |
| 109 | } // namespace workerd::fallback |