| 17 |
17 |
|
//!
|
| 18 |
18 |
|
//! - Mounting, as a fallback. See [`Adapter::into_router`].
|
| 19 |
19 |
|
//! - Reading the body, with a limit, before anything is parsed.
|
|
20 |
+ |
//! - Resolving the state a request is answered against, for the one host that
|
|
21 |
+ |
//! serves many viewers out of one process. See [`Adapter::per_viewer`].
|
| 20 |
22 |
|
//! - The blocking hop. The router is sync per decision 6, so the call happens
|
| 21 |
23 |
|
//! on a blocking thread, which is what an axum handler over a blocking store
|
| 22 |
24 |
|
//! pays anyway.
|
| 64 |
66 |
|
//! # }
|
| 65 |
67 |
|
//! ```
|
| 66 |
68 |
|
|
|
69 |
+ |
use std::future::Future;
|
|
70 |
+ |
use std::pin::Pin;
|
| 67 |
71 |
|
use std::sync::Arc;
|
| 68 |
72 |
|
|
| 69 |
73 |
|
use axum::body::{Body, Bytes};
|
| 70 |
74 |
|
use axum::extract::Request;
|
| 71 |
75 |
|
use axum::response::Response as HttpResponse;
|
|
76 |
+ |
use http::request::Parts;
|
| 72 |
77 |
|
use quasi_http::Refusal;
|
| 73 |
78 |
|
use quasi_router::{Params, Response, RouteError, Router};
|
| 74 |
79 |
|
|
| 75 |
80 |
|
pub use quasi_http::{DEFAULT_BODY_LIMIT, Render, htmx};
|
| 76 |
81 |
|
|
|
82 |
+ |
/// The state one request is answered against, once it is resolved.
|
|
83 |
+ |
///
|
|
84 |
+ |
/// A future, because what a viewer is resolved from is a lookup: a session
|
|
85 |
+ |
/// cookie against a store, a token against an introspection endpoint. That is
|
|
86 |
+ |
/// async work, and it happens here rather than in a handler because a handler
|
|
87 |
+ |
/// is sync by decision 6 and this is the one layer that can await.
|
|
88 |
+ |
///
|
|
89 |
+ |
/// `Err` is the resolution itself failing, not the viewer being anonymous. A
|
|
90 |
+ |
/// signed-out reader is a state the factory builds; a store that would not
|
|
91 |
+ |
/// answer is a [`RouteError`], and the request never reaches the router.
|
|
92 |
+ |
pub type StateFuture<S> = Pin<Box<dyn Future<Output = Result<S, RouteError>> + Send>>;
|
|
93 |
+ |
|
|
94 |
+ |
/// Builds the state one request is answered against.
|
|
95 |
+ |
///
|
|
96 |
+ |
/// The returned future is `'static`, so a factory takes what it needs out of
|
|
97 |
+ |
/// the request parts first and the future owns it. That is the discipline
|
|
98 |
+ |
/// rather than a limitation: a future holding a borrow on the request head
|
|
99 |
+ |
/// would be a second lifetime running through every host that mounts this.
|
|
100 |
+ |
pub type StateFactory<S> = dyn Fn(&Parts) -> StateFuture<S> + Send + Sync;
|
|
101 |
+ |
|
| 77 |
102 |
|
/// Builds the renderer for one request.
|
| 78 |
103 |
|
///
|
| 79 |
104 |
|
/// It is handed the state, the request's parameters and the answer the router
|
| 97 |
122 |
|
PerRequest(Box<RenderFactory<S, R>>),
|
| 98 |
123 |
|
}
|
| 99 |
124 |
|
|
|
125 |
+ |
/// Where the state for a request comes from.
|
|
126 |
+ |
///
|
|
127 |
+ |
/// `Handler<S> = fn(&S, Params)` builds `S` once and reads it for the life of
|
|
128 |
+ |
/// the program, which is correct for the hosts quasi was designed against: a
|
|
129 |
+ |
/// Tauri window, an egui frame and a terminal loop each have one user, so one
|
|
130 |
+ |
/// of everything is the truth. A hosted server is the one host that breaks
|
|
131 |
+ |
/// that assumption, because it has one process and many viewers.
|
|
132 |
+ |
///
|
|
133 |
+ |
/// So the adapter builds `S` per request, and nothing above it moves: the
|
|
134 |
+ |
/// router, the handler signature and the description layer are untouched. See
|
|
135 |
+ |
/// [`Adapter::per_viewer`].
|
|
136 |
+ |
enum States<S> {
|
|
137 |
+ |
Shared(Arc<S>),
|
|
138 |
+ |
PerRequest(Box<StateFactory<S>>),
|
|
139 |
+ |
}
|
|
140 |
+ |
|
| 100 |
141 |
|
/// The router, the app's state and a renderer, mounted as an axum service.
|
| 101 |
142 |
|
pub struct Adapter<S, R> {
|
| 102 |
143 |
|
router: Router<S>,
|
| 103 |
|
- |
state: Arc<S>,
|
|
144 |
+ |
state: States<S>,
|
| 104 |
145 |
|
render: Renderers<S, R>,
|
| 105 |
146 |
|
body_limit: usize,
|
| 106 |
147 |
|
}
|
| 108 |
149 |
|
/// What a request needs, once, behind one `Arc`.
|
| 109 |
150 |
|
struct Context<S, R> {
|
| 110 |
151 |
|
router: Router<S>,
|
| 111 |
|
- |
state: Arc<S>,
|
|
152 |
+ |
state: States<S>,
|
| 112 |
153 |
|
render: Renderers<S, R>,
|
| 113 |
154 |
|
body_limit: usize,
|
| 114 |
155 |
|
}
|
| 127 |
168 |
|
pub fn new(router: Router<S>, state: Arc<S>, render: Arc<R>) -> Self {
|
| 128 |
169 |
|
Self {
|
| 129 |
170 |
|
router,
|
| 130 |
|
- |
state,
|
|
171 |
+ |
state: States::Shared(state),
|
| 131 |
172 |
|
render: Renderers::Shared(render),
|
| 132 |
173 |
|
body_limit: DEFAULT_BODY_LIMIT,
|
| 133 |
174 |
|
}
|
| 178 |
219 |
|
{
|
| 179 |
220 |
|
Self {
|
| 180 |
221 |
|
router,
|
| 181 |
|
- |
state,
|
|
222 |
+ |
state: States::Shared(state),
|
| 182 |
223 |
|
render: Renderers::PerRequest(Box::new(factory)),
|
| 183 |
224 |
|
body_limit: DEFAULT_BODY_LIMIT,
|
| 184 |
225 |
|
}
|
| 185 |
226 |
|
}
|
| 186 |
227 |
|
|
|
228 |
+ |
/// Mount a router that resolves both its state and its renderer per
|
|
229 |
+ |
/// request.
|
|
230 |
+ |
///
|
|
231 |
+ |
/// For the host with many viewers behind one process. The factory is handed
|
|
232 |
+ |
/// the request head and runs in async context, so the session lookup, the
|
|
233 |
+ |
/// viewer and the theme resolved from them are loaded before the router is
|
|
234 |
+ |
/// called, and a handler reads them off `&S` as it reads anything else.
|
|
235 |
+ |
///
|
|
236 |
+ |
/// This is why the handler signature did not have to grow. A request-scoped
|
|
237 |
+ |
/// `S` is the same ruling the host boundary already made, that a host fact a
|
|
238 |
+ |
/// described screen needs is one the app puts in `S` while it still has a
|
|
239 |
+ |
/// handle to ask. All that changes for a server is how long `S` lives, and
|
|
240 |
+ |
/// the alternatives all cost more: a second parameter cascades to every
|
|
241 |
+ |
/// repo that has written a handler, an async handler kind was refused by
|
|
242 |
+ |
/// decision 6 for the hosts that have no runtime, and a capability protocol
|
|
243 |
+ |
/// was settled against when the host boundary was drawn.
|
|
244 |
+ |
///
|
|
245 |
+ |
/// Both factories, because a host that resolves a viewer per request is a
|
|
246 |
+ |
/// host whose renderer has something to say about that viewer: their theme,
|
|
247 |
+ |
/// their chrome, whether the page is signed in. A host that genuinely wants
|
|
248 |
+ |
/// one renderer clones a configured template in the render factory, which is
|
|
249 |
+ |
/// an allocation per request against a store round trip it is already
|
|
250 |
+ |
/// paying.
|
|
251 |
+ |
///
|
|
252 |
+ |
/// ```no_run
|
|
253 |
+ |
/// # use std::sync::Arc;
|
|
254 |
+ |
/// # use quasi_axum::Adapter;
|
|
255 |
+ |
/// # use quasi_router::Router;
|
|
256 |
+ |
/// # struct Store;
|
|
257 |
+ |
/// # impl Store { async fn viewer(&self, _: Option<String>) -> Option<u64> { None } }
|
|
258 |
+ |
/// # struct Viewer { store: Arc<Store>, viewer: Option<u64> }
|
|
259 |
+ |
/// # #[derive(Clone, Default)] struct Html;
|
|
260 |
+ |
/// # impl quasi_axum::Render for Html {
|
|
261 |
+ |
/// # fn screen(&self, _: &quasi_router::Screen) -> String { String::new() }
|
|
262 |
+ |
/// # fn fragment(&self, _: &quasi_router::Node) -> String { String::new() }
|
|
263 |
+ |
/// # }
|
|
264 |
+ |
/// # let store = Arc::new(Store);
|
|
265 |
+ |
/// # let quasi = Router::<Viewer>::new();
|
|
266 |
+ |
/// Adapter::per_viewer(
|
|
267 |
+ |
/// quasi,
|
|
268 |
+ |
/// move |parts| {
|
|
269 |
+ |
/// // Taken out of the head first: the future owns what it needs.
|
|
270 |
+ |
/// let cookie = parts
|
|
271 |
+ |
/// .headers
|
|
272 |
+ |
/// .get(http::header::COOKIE)
|
|
273 |
+ |
/// .and_then(|value| value.to_str().ok())
|
|
274 |
+ |
/// .map(str::to_owned);
|
|
275 |
+ |
/// let store = Arc::clone(&store);
|
|
276 |
+ |
/// Box::pin(async move {
|
|
277 |
+ |
/// let viewer = store.viewer(cookie).await;
|
|
278 |
+ |
/// Ok(Viewer { store, viewer })
|
|
279 |
+ |
/// })
|
|
280 |
+ |
/// },
|
|
281 |
+ |
/// |_viewer, _params, _answer| Html,
|
|
282 |
+ |
/// );
|
|
283 |
+ |
/// ```
|
|
284 |
+ |
#[must_use]
|
|
285 |
+ |
pub fn per_viewer<F, G>(router: Router<S>, state: F, render: G) -> Self
|
|
286 |
+ |
where
|
|
287 |
+ |
F: Fn(&Parts) -> StateFuture<S> + Send + Sync + 'static,
|
|
288 |
+ |
G: Fn(&S, &Params, Option<&Response>) -> R + Send + Sync + 'static,
|
|
289 |
+ |
{
|
|
290 |
+ |
Self {
|
|
291 |
+ |
router,
|
|
292 |
+ |
state: States::PerRequest(Box::new(state)),
|
|
293 |
+ |
render: Renderers::PerRequest(Box::new(render)),
|
|
294 |
+ |
body_limit: DEFAULT_BODY_LIMIT,
|
|
295 |
+ |
}
|
|
296 |
+ |
}
|
|
297 |
+ |
|
| 187 |
298 |
|
/// Read at most this many bytes of a form body.
|
| 188 |
299 |
|
#[must_use]
|
| 189 |
300 |
|
pub fn body_limit(mut self, bytes: usize) -> Self {
|
| 244 |
355 |
|
// was asked and what was answered.
|
| 245 |
356 |
|
let params = incoming.params.clone();
|
| 246 |
357 |
|
|
|
358 |
+ |
// After the envelope is checked and before the router is called. An
|
|
359 |
+ |
// oversized body should not cost a session lookup, and a handler must not
|
|
360 |
+ |
// run before the viewer it reads is resolved.
|
|
361 |
+ |
let state = match &context.state {
|
|
362 |
+ |
States::Shared(state) => Arc::clone(state),
|
|
363 |
+ |
States::PerRequest(factory) => match factory(&parts).await {
|
|
364 |
+ |
Ok(state) => Arc::new(state),
|
|
365 |
+ |
Err(error) => return convert(unresolved(&error)),
|
|
366 |
+ |
},
|
|
367 |
+ |
};
|
|
368 |
+ |
|
| 247 |
369 |
|
// Decision 6: the router is sync. Calling it directly would block the
|
| 248 |
370 |
|
// executor for however long the store takes.
|
| 249 |
371 |
|
let dispatch = {
|
| 250 |
372 |
|
let context = Arc::clone(&context);
|
|
373 |
+ |
let state = Arc::clone(&state);
|
| 251 |
374 |
|
tokio::task::spawn_blocking(move || {
|
| 252 |
|
- |
context.router.handle(
|
| 253 |
|
- |
&context.state,
|
| 254 |
|
- |
incoming.method,
|
| 255 |
|
- |
&incoming.path,
|
| 256 |
|
- |
incoming.params,
|
| 257 |
|
- |
)
|
|
375 |
+ |
context
|
|
376 |
+ |
.router
|
|
377 |
+ |
.handle(&state, incoming.method, &incoming.path, incoming.params)
|
| 258 |
378 |
|
})
|
| 259 |
379 |
|
.await
|
| 260 |
380 |
|
};
|
| 270 |
390 |
|
// Built from the answer rather than before it, so the factory can
|
| 271 |
391 |
|
// fill the regions this screen has and skip the work for the ones
|
| 272 |
392 |
|
// it does not.
|
| 273 |
|
- |
let render = factory(&context.state, ¶ms, outcome.as_ref().ok());
|
|
393 |
+ |
let render = factory(&state, ¶ms, outcome.as_ref().ok());
|
| 274 |
394 |
|
convert(quasi_http::respond(&render, outcome))
|
| 275 |
395 |
|
}
|
| 276 |
396 |
|
}
|
| 277 |
397 |
|
}
|
| 278 |
398 |
|
|
|
399 |
+ |
/// A state factory's failure, which has no description and no renderer.
|
|
400 |
+ |
///
|
|
401 |
+ |
/// Bodyless, for the reason [`quasi_http::refuse`] is: nothing was reached, so
|
|
402 |
+ |
/// there is nothing to say that the status does not already say. A renderer
|
|
403 |
+ |
/// cannot be built either, since the one thing a per-request renderer is handed
|
|
404 |
+ |
/// is the state that could not be resolved.
|
|
405 |
+ |
fn unresolved(error: &RouteError) -> http::Response<Vec<u8>> {
|
|
406 |
+ |
http::Response::builder()
|
|
407 |
+ |
.status(error.class.http_status())
|
|
408 |
+ |
.body(Vec::new())
|
|
409 |
+ |
.expect("a response with no body and no headers is always valid")
|
|
410 |
+ |
}
|
|
411 |
+ |
|
| 279 |
412 |
|
/// An `http` response with a `Vec` body becomes an axum one.
|
| 280 |
413 |
|
fn convert(response: http::Response<Vec<u8>>) -> HttpResponse {
|
| 281 |
414 |
|
let (parts, body) = response.into_parts();
|