Skip to main content

dioxus_cloudflare/
context.rs

1//! Thread-local storage for Cloudflare Worker request context.
2//!
3//! Workers are single-threaded per isolate — one request at a time — so
4//! thread-locals are safe for storing per-request state. [`set_context`] is
5//! called at the start of each request by [`crate::handle`], and
6//! [`clear_context`] is called when the request completes.
7
8use std::cell::RefCell;
9
10use worker::Env;
11
12thread_local! {
13    static WORKER_ENV: RefCell<Option<Env>> = const { RefCell::new(None) };
14    static WORKER_REQ: RefCell<Option<worker::Request>> = const { RefCell::new(None) };
15    static RESPONSE_COOKIES: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
16}
17
18/// Access the Cloudflare Worker [`Env`] from inside any `#[server]` function.
19///
20/// Provides typed access to D1 databases, KV namespaces, R2 buckets,
21/// Durable Objects, Queues, and all other Worker bindings.
22///
23/// # Panics
24///
25/// Panics if called outside a Worker request context (i.e., before
26/// [`crate::handle`] has been called for the current request).
27///
28/// # Example
29///
30/// ```rust,ignore
31/// use dioxus_cloudflare::cf;
32///
33/// #[server]
34/// pub async fn get_user(id: String) -> Result<User, ServerFnError> {
35///     let db = cf::env().d1("DB")?;
36///     // ...
37/// }
38/// ```
39pub fn env() -> Env {
40    WORKER_ENV.with(|cell| {
41        cell.borrow()
42            .clone()
43            .expect("cf::env() called outside Worker request context")
44    })
45}
46
47/// Access the raw Cloudflare Worker [`worker::Request`] for the current request.
48///
49/// Useful for reading cookies, headers, IP address, or other request metadata
50/// that isn't passed through the server function arguments.
51///
52/// # Panics
53///
54/// Panics if called outside a Worker request context.
55///
56/// # Example
57///
58/// ```rust,ignore
59/// use dioxus_cloudflare::cf;
60///
61/// #[server]
62/// pub async fn who_am_i() -> Result<String, ServerFnError> {
63///     let req = cf::req();
64///     let ip = req.headers().get("CF-Connecting-IP")?.unwrap_or_default();
65///     Ok(ip)
66/// }
67/// ```
68pub fn req() -> worker::Request {
69    WORKER_REQ.with(|cell| {
70        let borrow = cell.borrow();
71        let r = borrow
72            .as_ref()
73            .expect("cf::req() called outside Worker request context");
74        // worker::Request doesn't implement Clone (the trait). It has an
75        // inherent .clone() that returns Result<Request, JsValue>.
76        r.clone()
77            .expect("failed to clone Worker request")
78    })
79}
80
81/// Store the Worker [`Env`] and [`worker::Request`] for the current request.
82///
83/// Called by [`crate::handle`] at the start of each request. Not part of the
84/// public API.
85pub(crate) fn set_context(env: Env, req: worker::Request) {
86    WORKER_ENV.with(|cell| *cell.borrow_mut() = Some(env));
87    WORKER_REQ.with(|cell| *cell.borrow_mut() = Some(req));
88}
89
90/// Queue a raw `Set-Cookie` header value to be appended to the response.
91///
92/// Called by [`crate::cookie::set_cookie`] and [`crate::cookie::clear_cookie`].
93pub(crate) fn push_cookie(value: String) {
94    RESPONSE_COOKIES.with(|cell| cell.borrow_mut().push(value));
95}
96
97/// Drain all queued `Set-Cookie` values, returning them for the handler
98/// to append to the outgoing `worker::Response`.
99pub(crate) fn take_cookies() -> Vec<String> {
100    RESPONSE_COOKIES.with(|cell| cell.borrow_mut().drain(..).collect())
101}
102
103/// Clear the stored context after the request completes.
104///
105/// Called by [`crate::handle`] after the response is sent. Prevents stale
106/// state from leaking into subsequent requests on the same isolate.
107pub(crate) fn clear_context() {
108    WORKER_ENV.with(|cell| *cell.borrow_mut() = None);
109    WORKER_REQ.with(|cell| *cell.borrow_mut() = None);
110    RESPONSE_COOKIES.with(|cell| cell.borrow_mut().clear());
111}