Implement HTTP handlers / webhooks in Rust modules#4636
Merged
Conversation
Codex-assisted changes to implement HTTP handlers / webhooks, based on proposal discussed elsewhere. I've done a reasonably thorough review of these changes for code quality, though they remain largely untested as of this commit. I've made a few minor changes to the LLM's output, and left TODO comments anywhere I feel more substative changes will be necessary. Most notably, we'll need more testing, documentation, and better detection of suspicious-or-invalid path components. TypeScript, C# and C++ support is also not included, as per title. There are also notes that this initial implementation uses `Vec<Route>` for its router, meaning route matching is O(num_routes), and router construction (with error checking) is O(num_routes ^ 2). I don't expect this to matter much in the short term, as I expect modules to define a pretty small number of routes.
fbbe5b6 to
3f61a28
Compare
Added a smoketest that publishes a module with some routes, then makes requests against those routes and makes some simple assertions about the responses. This revealed a bug introduced by the previous commit in the `/v1/database PUT` route, which was incorrectly not getting the `anon_auth_middleware` applied.
And relatedly, make the bindings tests run with `--feature unstable` in CI. I was sadly unable to find a better construction to emit tyck code from the macros than the existing one. I also did a drive-by fix for an invalid doctest on `HttpClient`.
Restrict HTTP routes to allow only characters permitted by a new function, `spacetimedb_lib::http::character_is_acceptable_for_route_path`, which currently is restricted to ASCII lowercase, ASCII digit and `-_~/`. (Slash is allowed because it's the path segment separator character.) This is checked both when constructing the `Router` and during `ModuleDef` validation.
bfops
approved these changes
Apr 16, 2026
Collaborator
bfops
left a comment
There was a problem hiding this comment.
my code-owned files LGTM:
tools/ci/src/main.rs
# Conflicts: # crates/client-api/src/routes/database.rs
Contributor
Author
|
I had Codex vibe-code up a simple static webpage hosted entirely in a SpacetimeDB module. This was easy enough and mostly totally worked, but we ran into a few silly issues which I'll work to fix over the next few days. In particular:
|
…s-webhooks Resolved conflicts in: crates/core/src/host/module_host.rs crates/core/src/host/wasm_common/module_host_actor.rs
This was referenced May 14, 2026
…s-webhooks Resolved conflicts related to the new module def section for mounts: crates/bindings-csharp/Runtime/Internal/Autogen/RawModuleDefV10Section.g.cs crates/lib/src/db/raw_def/v10.rs crates/schema/src/def.rs crates/schema/src/def/validate/v10.rs
So that SpacetimeDB-cloud can apply proxy middleware.
# Description of Changes Adding C# HTTP handlers based on #4636 - adds the C# handler/router API `[SpacetimeDB.HttpHandler]`, `[SpacetimeDB.HttpRouter]` - wires C# HTTP handlers into module definition/build/runtime registration - mirrors the Rust/TypeScript HTTP smoketests and adds C# docs coverage - updates the HTTP handlers docs with C# examples - refactored ProcedureContext to allow for a central location for WithTx/TryWithTx to support HandlerContext - routes use a generated `Handlers.*` tokens to avoid raw strings # API and ABI breaking changes Adds new APIs for the HTTP handler and should not be breaking # Expected complexity level and risk 3 - this hit the binding, module registration, and had a decent refactor for ProcedureContext # Testing - [x] Expanded `crates/smoketests/tests/smoketests/http_routes.rs` with C# mirrors of the Rust HTTP route tests I also did some manual testing with a throw away project, and will be adding to the `module-test` after all languages are caught up on HTTP handlers.
…s-webhooks Resolved conflicts due to reverting RawModuleDefV10 mounts: crates/bindings-csharp/Runtime/Internal/Autogen/RawModuleDefV10Section.g.cs crates/lib/src/db/raw_def/v10.rs crates/schema/src/def.rs crates/schema/src/def/validate/v10.rs
Based on #4636 . # Description of Changes This commit adds host support for registering HTTP handlers in V8 modules, and a minimal draft of TypeScript bindings support for the same. The TypeScript bindings support is fully vibe-coded and unreviewed, and is present only to allow a new smoketest, which is added to the `http_routes` suite. The host changes were also AI-assisted, but I reviewed and polished them. # API and ABI breaking changes Adds new TypeScript "ABI." Also adds a new API. # Expected complexity level and risk 2: pretty simple extensions to TypeScript execution, which largely mirror existing `call_procedure` machinery. # Testing - [x] New smoketest. --------- Co-authored-by: Jason Larabie <jason@clockworklabs.io>
# Description of Changes Adding C++ HTTP handlers based on #4636 - adds the C++ handler/router API `SPACETIMEDB_HTTP_HANDLER()`, `SPACETIMEDB_HTTP_ROUTER()` - wires C++ HTTP handlers into module definition/build/runtime registration - mirrors the Rust/TypeScript HTTP smoketests and docs coverage - update to the the HTTP handlers docs examples # API and ABI breaking changes New APIs for http handler and router behind `SPACETIMEDB_UNSTABLE_FEATURES` and should not be breaking. # Expected complexity level and risk 3 - this touches the C++ binding surface, runtime/module registration, and the smoketest/docs paths, so there is more churn than I’d like, but it is still fairly contained to the C++ HTTP work. # Testing - [x] Expanded the C++ compile-surface tests - [x] Added C++ unit coverage for HTTP conversion helpers - [x] Expanded `crates/smoketests/tests/smoketests/http_routes.rs` with C++ mirrors of the Rust HTTP route tests - [x] Added a C++ docs-example smoketest for `docs/docs/00200-core-concepts/00200-functions/00600-HTTP- handlers.md` I also did some manual testing with a throw away project --------- Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Resolved conflicts re: Wasmtime module instantiation in: crates/core/src/host/wasm_common/module_host_actor.rs crates/core/src/host/wasmtime/wasmtime_module.rs
This reverts commit ffb3fb0. This was supposed to be on a different branch. Oops.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Changes
Adds support to Rust modules and the SpacetimeDB host for defining HTTP handlers and registering them to routes.
User-facing API
In a Rust module, users can annotate functions with the new macro
#[spacetimedb::http::handler]. A function annotated this way must accept exactly two arguments, of types&mut spacetimedb::http::HandlerContextandspacetimedb::http::Request(which is a type alias forhttp::Request<spacetimedb::http::Body>. It must also returnspacetimedb::http::Response(which is a type alias forhttp::Response<spacetimedb::http::Body>).Once the user has defined an HTTP handler, they can register it to a route by annotating a function with
#[spacetimedb::http::router]. Such a function must take no arguments and return aspacetimedb::http::Router. (The original design put this annotation on astaticvariable rather than a function, but that turned out to be undesirable because it required that constructing aRouterbeconst.)Routerexposes various methods for registering handlers to routes.All of a database's user-defined routes are exposed under
/v1/database/:name_or_identity/route/{*path}.Example
See the new smoketest for a more exhaustive example.
A simpler example, which stores arbitrary byte data in a table via a
POSTrequest, returns an ID, and then retrieves that same data via aGETrequest with a query parameter:Design and implementation notes
staticorconstitem. This is becausestaticorconstinitializers must beconst, and it turns out to be a pain to make all of theRouterconstructors beconst fns.#[handler]macro clobbers the original function name with aconstvariable of typeHttpHandler. This is unfortunate, but AFAICT necessary, 'cause we need to pass the string identifier for the handler to theRouter, not the function pointer, and Rust allows no (stable and reliable) way to get a unique string identifier out of a function item/value, nor to attach data or implement traits for function items/values. The alternative(s) would involve changing the signature of theRoutermethods to have uglier and more complex callsites, e.g. like.get("/retrieve", retrieve::handler()),.get("/retrieve", handler!(retrieve))or.get::<retrieve>("/retrieve"). I believe that registering handlers will be much more common than calling their functions, so I've chosen to make it so that registering them gets the convenient syntax, even though the inability to call them directly will be somewhat surprising.Authorizationheaders in requests before invoking the user-defined handler. This is required to allow arbitrary user-programmable handling ofAuthorizationheaders, including those in formations which SpacetimeDB would reject. As a result of this,HandlerContextdoesn't expose asenderorsender_connection_id.-_~/.Routeris currently aVec<Route>, meaning that resolving a request to a route isO(num_routes). Registering a route checks against each previous route for uniqueness, meaning that constructing a router isO(num_routes ^ 2). There are TODO comments to use a trie, but I think this can wait, as I expect most databases to register few routes.indexto serve the index/root of a website and it broke.Still TODO
API and ABI breaking changes
New APIs, currently flagged as
unstable, which will eventually need stability guarantees. No (intentional) breaking changes, or changes to existing APIs at all.Expected complexity level and risk
3? Changes to our HTTP routing to support the user-defined routes.
Testing