1use askama::Template;
2use futures_lite::prelude::*;
3use std::time::Duration;
4use trillium::{Conn, Handler};
5use trillium_askama::AskamaConnExt;
6use trillium_caching_headers::{
7 CacheControlDirective::{MaxAge, Public},
8 CachingHeadersExt,
9};
10use trillium_conn_id::log_formatter::conn_id;
11use trillium_logger::apache_common;
12use trillium_proxy::Proxy;
13use trillium_router::{Router, RouterConnExt};
14use trillium_rustls::RustlsConfig;
15use trillium_sessions::{MemoryStore, SessionConnExt};
16use trillium_smol::ClientConfig;
17use trillium_static_compiled::static_compiled;
18use trillium_websockets::{Message, WebSocket, WebSocketConn};
19
20#[derive(Template)]
21#[template(path = "hello.html")]
22struct HelloTemplate<'a> {
23 name: &'a str,
24}
25
26async fn request_count(conn: Conn) -> Conn {
27 let count = conn.session().get::<usize>("count").unwrap_or_default();
28 conn.with_response_header("request-count", count.to_string())
29 .with_session("count", count + 1)
30}
31
32async fn with_cache_control(conn: Conn) -> Conn {
33 conn.with_cache_control([MaxAge(Duration::from_secs(604800)), Public])
34 .with_vary([trillium::KnownHeaderName::UserAgent])
35}
36
37fn app() -> impl Handler {
38 (
39 with_cache_control,
40 trillium_logger::logger().with_formatter(apache_common(conn_id, "-")),
41 trillium_compression::compression(),
42 trillium_conn_id::conn_id(),
43 trillium_method_override::method_override(),
44 trillium_head::head(),
45 trillium_caching_headers::caching_headers(),
46 trillium_cookies::cookies(),
47 trillium_sessions::sessions(MemoryStore::new(), b"01234567890123456789012345678901123"),
48 request_count,
49 router(),
50 static_compiled!("$CARGO_MANIFEST_DIR/public").with_index_file("index.html"),
51 )
52}
53
54fn router() -> impl Handler {
55 Router::new()
56 .get("/hello", "hi")
57 .post("/", |mut conn: Conn| async move {
58 let body = conn.request_body_string().await.unwrap();
59 conn.ok(format!("request body: {body}"))
60 })
61 .get("/template/:name", |conn: Conn| async move {
62 if let Some(name) = conn.param("name").map(String::from) {
63 conn.render(HelloTemplate { name: &name })
64 } else {
65 conn
66 }
67 })
68 .get("/hello/:planet", |conn: Conn| async move {
69 if let Some(planet) = conn.param("planet") {
70 let response = format!("hello, {planet}");
71 conn.ok(response)
72 } else {
73 conn
74 }
75 })
76 .get(
77 "/ws",
78 WebSocket::new(|mut ws: WebSocketConn| async move {
79 while let Some(Ok(Message::Text(input))) = ws.next().await {
80 log::info!("received message {:?}", &input);
81 let output: String = input.chars().rev().collect();
82 let _ = ws.send_string(format!("{} | {}", &input, &output)).await;
83 }
84 }),
85 )
86 .get(
87 "/httpbin/*",
88 Proxy::new(
89 RustlsConfig::default().with_tcp_config(ClientConfig::default()),
90 "https://httpbin.org",
91 ),
92 )
93}
94
95fn main() {
96 env_logger::init();
97 trillium_smol::run(app());
98}
99
100#[cfg(test)]
101mod test {
102 use trillium_testing::prelude::*;
103
104 use super::app;
105
106 #[test]
107 fn test_index() {
108 let app = app();
109 let mut conn = get("/").on(&app);
110 assert_ok!(&conn);
111 assert_body_contains!(&mut conn, "<h1>Welcome to trillium!</h1>");
112 }
113
114 #[test]
115 fn test_hello_hi() {
116 let app = app();
117 assert_ok!(get("/hello").on(&app), "hi");
118 }
119
120 #[test]
121 fn test_post_index() {
122 let app = app();
123 assert_ok!(
124 post("/").with_request_body("hey").on(&app),
125 "request body: hey"
126 );
127 }
128
129 #[test]
130 fn test_askama_templating() {
131 let app = app();
132 assert_body_contains!(
133 get("/template/trillium").on(&app),
134 "<h1>hi there, trillium</h1>"
135 );
136
137 assert_body_contains!(
138 get("/template/dear-reader").on(&app),
139 "<h1>hi there, dear-reader</h1>"
140 );
141 }
142}