-
-
Notifications
You must be signed in to change notification settings - Fork 106
/
Copy pathmain.rs
97 lines (86 loc) · 3 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::cell::RefCell;
use actix_web::http::header::ContentType;
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};
use minijinja::value::{Rest, Value};
use minijinja::{context, path_loader, Environment, Error, ErrorKind};
thread_local! {
static CURRENT_REQUEST: RefCell<Option<HttpRequest>> = RefCell::default()
}
/// Binds the given request to a thread local for `url_for`.
fn with_bound_req<F, R>(req: &HttpRequest, f: F) -> R
where
F: FnOnce() -> R,
{
CURRENT_REQUEST.with(|current_req| *current_req.borrow_mut() = Some(req.clone()));
let rv = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
CURRENT_REQUEST.with(|current_req| current_req.borrow_mut().take());
match rv {
Ok(rv) => rv,
Err(panic) => std::panic::resume_unwind(panic),
}
}
struct AppState {
env: minijinja::Environment<'static>,
}
impl AppState {
/// Helper function to render a template to an HTTP response with a bound request.
pub fn render_template(&self, name: &str, req: &HttpRequest, ctx: Value) -> HttpResponse {
with_bound_req(req, || {
let tmpl = self.env.get_template(name).unwrap();
let rv = tmpl.render(ctx).unwrap();
HttpResponse::Ok()
.content_type(ContentType::html())
.body(rv)
})
}
}
/// Helper function that is added to templates to invoke `url_for` on the bound request.
fn url_for(name: &str, args: Rest<String>) -> Result<Value, Error> {
CURRENT_REQUEST.with(|current_req| {
Ok(current_req
.borrow()
.as_ref()
.ok_or_else(|| {
Error::new(
ErrorKind::InvalidOperation,
"url_for requires an http request",
)
})?
.url_for(name, &args[..])
.map_err(|err| {
Error::new(ErrorKind::InvalidOperation, "failed to generate url").with_source(err)
})?
.to_string()
.into())
})
}
async fn index(app_state: web::Data<AppState>, req: HttpRequest) -> impl Responder {
app_state.render_template("index.html", &req, context! { name => "World" })
}
async fn user(
app_state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<(u64,)>,
) -> impl Responder {
app_state.render_template("user.html", &req, context! { user_id => path.0 })
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mut env = Environment::new();
env.set_loader(path_loader("templates"));
env.add_function("url_for", url_for);
let state = web::Data::new(AppState { env });
HttpServer::new(move || {
App::new()
.app_data(state.clone())
.service(web::resource("/").name("index").route(web::get().to(index)))
.service(
web::resource("/user/{user_id}")
.name("user")
.route(web::get().to(user)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}