1use expressjs::express_state;
2use expressjs::prelude::*;
3use hyper::header::{self, HeaderValue};
4use local_ip_address::local_ip;
5use log::info;
6use serde_json::json;
7use std::sync::atomic::{AtomicU32, Ordering};
8
9fn setup_logger() -> Result<(), Box<dyn std::error::Error>> {
10 use std::io::Write;
11 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
12 .format(|buf, record| {
13 writeln!(
14 buf,
15 "{} [{}] {}",
16 chrono::Utc::now().format("%Y-%m-%d %H:%M:%S"),
17 record.level(),
18 record.args()
19 )
20 })
21 .try_init()?;
22 Ok(())
23}
24
25#[derive(Debug, Default)]
26pub struct State {
27 request_count: AtomicU32,
28}
29
30express_state!(STATE, State);
31
32#[tokio::main]
33async fn main() {
34 setup_logger().unwrap();
35
36 const PORT: u16 = 9000;
37 let mut app = express();
38
39 app.use_global(NormalizePathMiddleware::new())
41 .use_global(CorsMiddleware::permissive())
42 .use_global(LoggingMiddleware)
43 .use_with("/css/{*p}", StaticServeMiddleware::new("."))
44 .use_with("/expressjs_tests/{*p}", StaticServeMiddleware::new("."));
45
46 app.get("/", async |_req, res| {
50 let html = r#"
51 <!DOCTYPE html>
52 <html lang="en">
53 <head>
54 <meta charset="UTF-8">
55 <meta name="viewport" content="width=device-width, initial-scale=1.0">
56 <title>Welcome</title>
57 <link rel="stylesheet" href="/css/index.css"/>
58 </head>
59 <body>
60 <h1>Welcome to expressjs</h1>
61 <p>This is a minimal HTML page served from Rust.</p>
62 <ul>
63 <li><a href="/json">JSON Endpoint</a></li>
64 <li><a href="/redirect">Redirect to Home</a></li>
65 <li><a href="status">Trigger 400</a></li>
66 <li><a href="hello">Say Hello</a></li>
67 </ul>
68 </body>
69 </html>
70 "#;
71
72 res.status_code(200)
73 .content_type("text/html; charset=utf-8")
74 .send_html(html)
75 });
76
77 app.get("/count", async |_req, res| {
79 let count = STATE.request_count.fetch_add(1, Ordering::Relaxed);
80
81 res.send_json(&json!({
82 "request_count": count,
83 "message": "Request count retrieved successfully"
84 }))
85 });
86
87 app.get("/json", async |_req, res| {
89 res.header(
90 hyper::header::CACHE_CONTROL,
91 HeaderValue::from_static("public, max-age=3600"),
92 )
93 .send_json(&json!({
94 "message": "Hello from JSON!",
95 "status": "success",
96 "version": "1.0"
97 }))
98 });
99
100 app.get("/redirect", async |_req, res| res.redirect("/"));
102
103 app.get("/status", async |_req, res| {
105 res.status_code(400).send_text("400 Bad Request")
106 });
107
108 app.get("/status/{status}", async |req, res| {
110 let value = req.params().get("status").unwrap_or("unknown");
111 res.send_text(format!("Status is {value}"))
112 });
113
114 app.get("/file", async |_req, res| {
116 res.send_file("./Cargo.lock").await
117 });
118
119 app.get("/hello", async |_req, res| {
126 res.body("Hello, world!")
127 .header(
128 header::CACHE_CONTROL,
129 HeaderValue::from_static("public, max-age=86400"),
130 )
131 .header(header::CONTENT_TYPE, HeaderValue::from_static("text/html"))
132 });
133
134 app.route("/api/v1/user")
136 .get(async |_req, res| res.send_text("Get User"))
137 .post(async |_req, res| res.send_text("Post User"));
138
139 app.all("/ping", async |_req, res| res.send_text("pong"));
141
142 app.not_found(async |_req, res| {
144 res.status_code(404)
145 .send_text("Custom 404: Page not found!")
146 });
147
148 app.listen(PORT, async |port| {
150 let local_ip = local_ip().unwrap();
151 info!("🚀 Server running!");
152 info!("📍 Local: http://localhost:{port}/");
153 info!("🌐 Network: http://{local_ip}:{port}/");
154 })
155 .await
156}