ocre/runtime/mod.rs
1//! Code that calls the Workers JavaScript runtime. It only runs inside
2//! workerd, so it is exercised by the end-to-end tests
3//! (`crates/ocre-cli/tests/system/e2e.rs`) rather than by native unit tests.
4
5pub(crate) mod cache;
6#[cfg(target_arch = "wasm32")]
7mod crypto;
8mod ctx;
9mod d1;
10pub(crate) mod errors;
11#[cfg(feature = "graphql")]
12mod graphql;
13pub(crate) mod jobs;
14pub(crate) mod jwt;
15pub(crate) mod mail;
16pub(crate) mod oauth;
17#[cfg(feature = "push")]
18pub(crate) mod push;
19mod query;
20#[cfg(feature = "realtime")]
21pub(crate) mod realtime;
22pub(crate) mod resumable;
23pub(crate) mod secrets;
24pub(crate) mod security;
25pub(crate) mod storage;
26pub(crate) mod webhooks;
27
28#[cfg(target_arch = "wasm32")]
29pub(crate) use crypto::{pbkdf2_sha256, unix_millis};
30pub use ctx::Ctx;
31pub use d1::Db;
32#[cfg(feature = "graphql")]
33pub use graphql::routes as graphql_routes;
34
35use axum::Router;
36use tower_service::Service;
37use worker::{Env, HttpRequest, web_sys};
38
39use crate::{protect, session};
40
41/// Runs one request through the application router: the Worker `fetch` entry point.
42///
43/// Builds a [`Ctx`] from `env`, gives it to `routes` as axum state, and wraps
44/// the router with the middleware every Ocre app runs (outermost first):
45///
46/// 1. Security headers on every response (`X-Content-Type-Options: nosniff`,
47/// `X-Frame-Options: SAMEORIGIN`, `Referrer-Policy`, HSTS on HTTPS...); a
48/// handler's own value wins.
49/// 2. Host authorization: when the [`ALLOWED_HOSTS`](crate::ALLOWED_HOSTS)
50/// Worker variable is set, other hosts get `403 Forbidden`.
51/// 3. CORS for the origins listed in the [`ALLOWED_ORIGINS`](crate::ALLOWED_ORIGINS)
52/// Worker variable (no CORS layer when it is empty).
53/// 4. Cross-origin request protection (CSRF) without tokens: unsafe requests a
54/// browser sends from another site (`Sec-Fetch-Site`, or `Origin` against
55/// `Host`) get `403 Forbidden`.
56/// 5. The encrypted cookie [`Session`](crate::Session), keyed from the
57/// [`SECRET_KEY_BASE`](crate::SECRET_KEY_BASE) secret (and, during a
58/// rotation, [`SECRET_KEY_BASE_PREVIOUS`](crate::SECRET_KEY_BASE_PREVIOUS)).
59///
60/// A missing or short `SECRET_KEY_BASE` does not fail every request: only
61/// handlers that touch the session get [`Error::Internal`](crate::Error::Internal),
62/// naming the fix (`ocre secret`, `.dev.vars`).
63///
64/// The request also carries the [`Ctx`] as an extension, so the app's own
65/// middleware (`axum::middleware::from_fn`) can reach the bindings with an
66/// `Extension(ctx): Extension<Ctx>` argument, as handlers do with `State`.
67///
68/// Files from [`storage::serve`](crate::storage::serve) go out as R2's own
69/// stream, so the Worker spends no CPU copying them and `Content-Length` is
70/// kept.
71///
72/// Around the router, `serve` picks the request id ([`RequestId`](crate::RequestId))
73/// and tags [`Ctx::log`] with it, the method and the path; answers with an
74/// `X-Request-Id` header; reports an [`Error::Internal`](crate::Error::Internal)
75/// response through [`Ctx::errors`] (logged as `[ocre] <message>`, then
76/// sent to the [`errors`](crate::errors) subscribers); and logs
77/// `GET /posts 200 in 40 ms (db: 3 queries, 12 ms)` at `debug`. Debug
78/// builds (`ocre dev`) also add a `Server-Timing` header (D1 time and
79/// total, in the browser's Network panel) and show the development error
80/// page: a 500 page with the internal message, the request's details
81/// (secrets filtered) and the D1 statements it ran, or `error.detail` in a
82/// JSON error. Release builds (`ocre deploy`) never show internal details.
83///
84/// # Errors
85///
86/// Returns a [`worker::Error`] only when the response cannot be converted to a
87/// JavaScript `Response`. Handler errors are responses (an HTML page or JSON),
88/// not `Err`.
89///
90/// # Free plan
91///
92/// One call per Worker request (100,000 a day); the middleware itself reads no
93/// D1 rows and no KV keys: sessions live in the cookie.
94///
95/// # Examples
96///
97/// `src/lib.rs` of a generated app:
98///
99/// ```no_run
100/// use axum::{Router, routing::get};
101/// use ocre::Ctx;
102///
103/// fn routes() -> Router<Ctx> {
104/// Router::new().route("/up", get(|| async { "OK" }))
105/// }
106///
107/// #[worker::event(fetch)]
108/// async fn fetch(
109/// req: worker::HttpRequest,
110/// env: worker::Env,
111/// _ctx: worker::Context,
112/// ) -> worker::Result<worker::web_sys::Response> {
113/// ocre::serve(routes(), req, env).await
114/// }
115/// # fn main() {}
116/// ```
117pub async fn serve(routes: Router<Ctx>, req: HttpRequest, env: Env) -> worker::Result<web_sys::Response> {
118 let var = |name: &str| env.var(name).ok().map(|var| var.to_string());
119 let secret = |name: &str| env.secret(name).ok().map(|secret| secret.to_string());
120 let config = protect::Config {
121 keys: session::keys_from_secrets(secret(session::SECRET_KEY_BASE), secret(session::SECRET_KEY_BASE_PREVIOUS)),
122 allowed_origins: protect::parse_origins(var(protect::ALLOWED_ORIGINS)),
123 allowed_hosts: protect::parse_hosts(var(protect::ALLOWED_HOSTS)),
124 };
125 let replicas = crate::replicas::enabled(var(crate::replicas::REPLICAS_VAR).as_deref());
126 let started = crate::clock::now_millis();
127 let dev = cfg!(debug_assertions);
128 let request_id = crate::request::request_id(req.headers());
129 let details = crate::errors::RequestDetails::new(&request_id, req.method().as_str(), req.uri(), req.headers(), dev);
130 let ctx = Ctx::new(env).with_log(details.logger());
131 if replicas {
132 ctx.memo().start_replicas(req.method(), req.headers());
133 }
134 let mut req = req;
135 req.extensions_mut().insert(ctx.clone());
136 req.extensions_mut().insert(crate::RequestId(request_id));
137 let mut app = protect::wrap(routes.with_state(ctx.clone()), config);
138 let response = app.call(req).await?;
139 let total_ms = crate::clock::now_millis() - started;
140 let mut response = crate::errors::finish(response, &details, ctx.errors(), ctx.timings(), total_ms, dev).await;
141 errors::flush(&ctx).await;
142 // After a write, the visitor's next requests resume from its bookmark.
143 for (binding, bookmark) in ctx.memo().bookmarks() {
144 if let Some(cookie) = crate::replicas::bookmark_cookie(&binding, &bookmark) {
145 response.headers_mut().append(axum::http::header::SET_COOKIE, cookie);
146 }
147 }
148 match response.extensions_mut().remove::<storage::R2Stream>() {
149 Some(stream) => storage::into_js_response(response, stream),
150 None => worker::response_to_wasm(response),
151 }
152}
153
154/// Waits `duration` without using CPU (JavaScript's `setTimeout`): pacing for [`sse`](crate::sse) streams and polling.
155///
156/// On Workers, time spent waiting is not CPU time: the free plan's 10 ms
157/// per request only counts the work between waits. An HTTP request may wait
158/// as long as its client stays connected; a queue or scheduled invocation
159/// is limited to 15 minutes of wall time. The returned future is `Send`, so
160/// it can be awaited in handlers and in [`sse::stream`](crate::sse::stream)
161/// steps. Only runs on Workers (natively it panics, like every binding).
162///
163/// # Examples
164///
165/// ```no_run
166/// use std::time::Duration;
167///
168/// async fn slow() -> &'static str {
169/// ocre::sleep(Duration::from_millis(500)).await;
170/// "done"
171/// }
172/// # let _ = slow;
173/// ```
174pub fn sleep(duration: std::time::Duration) -> impl Future<Output = ()> + Send {
175 worker::send::SendFuture::new(worker::Delay::from(duration))
176}