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