1use gorust::go;
2use grorm::DeriveModel;
3use grorm::{
4 ConnectionConfig, ConnectionPool as DbConnectionPool, Error, PostgresDriverFactory,
5 QueryBuilder,
6};
7use grweb::{
8 middleware::{CORSMiddleware, LoggerMiddleware, RecoveryMiddleware},
9 AppConfig, Context, Message, Response, Router, Server, WebSocket,
10};
11use serde::Serialize;
12use serde_json::json;
13use std::sync::Arc;
14
15#[derive(Debug, DeriveModel, Serialize)]
16#[table("res_user")]
17struct ResUser {
18 id: i64, #[index] name: String,
21 #[unique] email: String,
23 age: i32,
24}
25impl ResUser {
26 fn new(name: String, email: String, age: i32) -> Self {
27 Self {
28 id: 0,
29 name,
30 email,
31 age,
32 }
33 }
34
35 fn create(&self, ctx: &Context) -> Result<(), Error> {
36 let db_pool = ctx.get_db_pool();
37 let mut conn = db_pool.get()?;
38 let mut qb = QueryBuilder::<ResUser>::new(conn.driver_mut());
39 qb.create_table()?;
40 qb.insert(self)?;
41 Ok(())
42 }
43}
44
45fn hello_handler(ctx: Context) -> Response {
46 let default_name = "World".to_string();
47 let name = ctx.param("name").unwrap_or(&default_name);
48 Response::html(format!("<h1>Hello, {}!</h1>", name))
49}
50
51fn headers_handler(ctx: Context) -> Response {
52 let mut body = String::from("<h1>Request Headers</h1><ul>");
53 for (k, v) in &ctx.headers {
54 body.push_str(&format!("<li><b>{}</b>: {}</li>", k, v));
55 }
56 body.push_str("</ul>");
57 Response::html(body)
58}
59
60fn form_handler(ctx: Context) -> Response {
61 let values = ctx.form_values();
62 let mut body = String::from("<h1>Form Data</h1><ul>");
63 for (k, v) in &values {
64 body.push_str(&format!("<li><b>{}</b>: {}</li>", k, v));
65 }
66 body.push_str("</ul>");
67 Response::html(body)
68}
69
70fn form_page_handler(_ctx: Context) -> Response {
71 let html = r#"<!DOCTYPE html>
72<html>
73<head><meta charset="utf-8"><title>Form Test</title></head>
74<body>
75<h1>Form Test</h1>
76<h2>URL-Encoded Form</h2>
77<form action="/form" method="post">
78 <input name="username" placeholder="Username"><br>
79 <input name="email" placeholder="Email"><br>
80 <input name="age" placeholder="Age"><br>
81 <button type="submit">Submit URL-Encoded</button>
82</form>
83<h2>Multipart Form,create user</h2>
84<form action="/api/user" method="post" enctype="multipart/form-data">
85 <input name="name" placeholder="Name"><br>
86 <input name="email" placeholder="Email"><br>
87 <input name="age" placeholder="Age"><br>
88 <button type="submit">Submit Multipart</button>
89</form>
90</body>
91</html>"#;
92 Response::html(html)
93}
94
95fn ws_handler(mut ws: WebSocket) {
96 let welcome = r#"{"type":"welcome","message":"Connected to WebSocket server"}"#;
97 ws.send_text(welcome);
98
99 loop {
100 match ws.read_message() {
101 Some(Message::Text(text)) => {
102 let reply = format!(r#"{{"type":"echo","data":"{}"}}"#, text);
103 ws.send_text(&reply);
104 }
105 Some(Message::Binary(data)) => {
106 ws.send_binary(&data);
107 }
108 Some(Message::Ping(data)) => {
109 ws.send_pong(&data);
110 }
111 Some(Message::Close(_)) => {
112 break;
113 }
114 _ => break,
115 }
116 }
117}
118
119fn pool_stats_handler(ctx: Context) -> Response {
120 match ctx.pool_stats() {
121 Some(stats) => {
122 let json = json!({
123 "active_connections": stats.active_connections,
124 "total_connections": stats.total_connections,
125 "rejected_connections": stats.rejected_connections,
126 "max_connections": if stats.max_connections == 0 { "unlimited".to_string() } else { stats.max_connections.to_string() },
127 });
128 Response::json(json.to_string())
129 }
130 None => Response::json(r#"{"error":"pool not available"}"#),
131 }
132}
133
134fn create_user_handler(ctx: Context) -> Result<Response, Error> {
135 let values = ctx.form_values();
136 let user = ResUser::new(
137 values["name"].to_string(),
138 values["email"].to_string(),
139 values["age"].parse::<i32>().unwrap(),
140 );
141 user.create(&ctx)?;
142 Ok(Response::json(user))
143}
144
145fn get_users_handler(ctx: Context) -> Result<Response, Error> {
146 let mut conn = ctx.get_db_pool().get()?;
148 let mut db = QueryBuilder::<ResUser>::new(conn.driver_mut());
149 let users = db.find_all()?;
150 Ok(Response::json(users))
151}
152
153fn slow_handler(_ctx: Context) -> Response {
154 use std::time::Duration;
155 std::thread::sleep(Duration::from_secs(20));
156 Response::html("<h1>Slow response completed!</h1>".to_string())
157}
158
159fn handle_home(_ctx: Context) -> Response {
161 let body = r#"<html>
162 <head>
163 <title>GRweb Web Server</title>
164 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
165 </head>
166 <body>
167 <h1>🚀 Welcome to GRweb Web Server</h1>
168 <p>High-performance web server powered by GRweb!</p>
169
170 <h2>Available Routes:</h2>
171 <ul>
172 <li><a href="/">/</a> - Home</li>
173 <li><a href="/hello">/hello</a> - Hello page</li>
174 <li><a href="/json">/json</a> - JSON response</li>
175 <li><a href="/about">/about</a> - About page</li>
176 <li><a href="/status">/status</a> - Server status</li>
177 <li><a href="/form">/form</a> - Form test</li>
178 <li><a href="/headers">/headers</a> - Request headers test</li>
179 <li><a href="/user/123">/user/123</a> - Dynamic user (123)</li>
180 <li><a href="/user/alice">/user/alice</a> - Dynamic user (alice)</li>
181 <li><a href="/post/2024/01/hello-world">/post/2024/01/hello-world</a> - Dynamic post</li>
182 </ul>
183
184 <h2>Performance:</h2>
185 <ul>
186 <li>QPS: ~95,000</li>
187 <li>Latency: ~0.59ms</li>
188 <li>Memory: ~3MB</li>
189 </ul>
190 </body>
191 </html>"#;
192 Response::html(body)
193}
194
195fn handle_hello(_ctx: Context) -> Response {
196 let body = r#"<html>
197 <head>
198 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
199 </head>
200 <body>
201 <h1>👋 Hello from GRweb!</h1>
202 <p>Served by goroutine with high performance!</p>
203 <p>This request was handled by a lightweight M:N scheduler.</p>
204 <p><a href="/">← Back to home</a></p>
205 </body>
206 </html>"#;
207 Response::html(body)
208}
209
210fn handle_json(_ctx: Context) -> Response {
211 let body = r#"{
212 "status": "ok",
213 "message": "Hello from GRweb",
214 "framework": "GRweb",
215 "version": "0.2.0",
216 "performance": {
217 "qps": 95000,
218 "latency_ms": 0.59,
219 "max_latency_ms": 4.82
220 },
221 "features": [
222 "goroutine",
223 "channel",
224 "M:N scheduling",
225 "work stealing",
226 "non-blocking I/O",
227 "dynamic routing"
228 ],
229 "database_connected": true
230 }"#;
231 Response::json(body)
232}
233
234fn handle_about(_ctx: Context) -> Response {
235 let body = r#"<html>
236 <head>
237 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
238 </head>
239 <body>
240 <h1>About GRweb</h1>
241 <p>GRweb is a lightweight, high-performance runtime for Rust that provides Go-like concurrency.</p>
242
243 <h2>Core Features:</h2>
244 <ul>
245 <li><strong>M:N Goroutine Scheduling</strong> - Efficient user-space threads</li>
246 <li><strong>Work Stealing</strong> - Automatic load balancing</li>
247 <li><strong>Channel-based Communication</strong> - Safe concurrent message passing</li>
248 <li><strong>Non-blocking I/O</strong> - High throughput networking</li>
249 <li><strong>Low Memory Footprint</strong> - ~3MB base memory</li>
250 <li><strong>High Throughput</strong> - 95,000 req/s</li>
251 <li><strong>Low Latency</strong> - ~0.59ms average</li>
252 <li><strong>Database Integration</strong> - Integrated connection pooling</li>
253 </ul>
254
255 <h2>Architecture:</h2>
256 <ul>
257 <li>G (Goroutine) - User-space task</li>
258 <li>P (Processor) - Logical CPU core</li>
259 <li>M (Machine) - OS thread</li>
260 <li>Scheduler - Work stealing across Ps</li>
261 </ul>
262
263 <p><a href="/">← Back to home</a></p>
264 </body>
265 </html>"#;
266 Response::html(body)
267}
268
269fn handle_status(_ctx: Context) -> Response {
270 let mut body = String::new();
271 body.push_str(
272 r#"<html>
273 <head>
274 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
275 <title>Server Status</title>
276 </head>
277 <body>
278 <h1>📊 Server Status</h1>
279 <table border="1" cellpadding="10">
280 <tr><th>Metric</th><th>Value</th></tr>
281 <tr><td>Framework</td><td>GRweb v0.2.0</td></tr>
282 <tr><td>Status</td><td style="color:green">✓ Running</td></tr>
283 <tr><td>QPS</td><td>~95,000</td></tr>
284 <tr><td>Average Latency</td><td>~0.59ms</td></tr>
285 <tr><td>Max Latency</td><td>~4.82ms</td></tr>
286 <tr><td>Memory Usage</td><td>~3MB</td></tr>
287 <tr><td>Concurrency Model</td><td>M:N Goroutines</td></tr>
288 <tr><td>Scheduler</td><td>Work Stealing</td></tr>
289 <tr><td>Database Pool</td><td>Integrated</td></tr>
290 </table>
291 <p><a href="/">← Back to home</a></p>
292 </body>
293 </html>"#,
294 );
295 Response::html(body)
296}
297
298fn handle_user(ctx: Context) -> Response {
299 let user_id = ctx.param("id").map(|s| s.as_str()).unwrap_or("unknown");
300 let mut body = String::new();
302 body.push_str(
303 r#"<html>
304 <head>
305 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
306 </head>
307 <body>
308 <h1>👤 User Profile</h1>
309 <p><strong>User ID:</strong> "#,
310 );
311 body.push_str(user_id);
312 body.push_str(
313 r#"</p>
314 <p><strong>Page:</strong> Dynamic route handling</p>
315 <p>This page demonstrates dynamic routing with path parameters.</p>
316 <p><a href="/">← Back to home</a></p>
317 </body>
318 </html>"#,
319 );
320
321 Response::html(body)
322}
323
324fn handle_post(ctx: Context) -> Response {
325 let year = ctx.param("year").map(|s| s.as_str()).unwrap_or("unknown");
326 let month = ctx.param("month").map(|s| s.as_str()).unwrap_or("unknown");
327 let slug = ctx.param("slug").map(|s| s.as_str()).unwrap_or("unknown");
328
329 let mut body = String::new();
330 body.push_str(
331 r#"<html>
332 <head>
333 <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
334 </head>
335 <body>
336 <h1>📝 Blog Post</h1>
337 <p><strong>Date:</strong> "#,
338 );
339 body.push_str(year);
340 body.push_str("-");
341 body.push_str(month);
342 body.push_str(
343 r#"</p>
344 <p><strong>Slug:</strong> "#,
345 );
346 body.push_str(slug);
347 body.push_str(
348 r#"</p>
349 <p>This is a dynamically routed blog post page.</p>
350 <p>The routing pattern <code>/post/:year/:month/:slug</code> matches this URL.</p>
351 <p><a href="/">← Back to home</a></p>
352 </body>
353 </html>"#,
354 );
355
356 Response::html(body)
357}
358
359fn grweb_print() {
360 println!("╔════════════════════════════════════════════════════════════╗");
361 println!("║ grweb Web Server ║");
362 println!("║ High-performance HTTP Server ║");
363 println!("╚════════════════════════════════════════════════════════════╝");
364 println!();
365 println!("📍 Server running at: http://127.0.0.1:9030");
366 println!();
367 println!("📋 Available Routes:");
368 println!(" ┌─────────────────────────────────────────────────────────┐");
369 println!(" │ Static Routes: │");
370 println!(" │ GET / - Home page │");
371 println!(" │ GET /hello - Hello page │");
372 println!(" │ GET /json - JSON response │");
373 println!(" │ GET /about - About page │");
374 println!(" │ GET /status - Server status │");
375 println!(" │ GET /form - Form page │");
376 println!(" │ GET /headers - Headers page │");
377 println!(" │ GET /static/* - Static file server │");
378 println!(" │ │");
379 println!(" │ Dynamic Routes: │");
380 println!(" │ GET /user/:id - User profile (dynamic ID) │");
381 println!(" │ GET /post/:year/:month/:slug - Blog post │");
382 println!(" └─────────────────────────────────────────────────────────┘");
383 println!();
384 println!("💡 Examples:");
385 println!(" curl http://127.0.0.1:9030/user/123");
386 println!(" curl http://127.0.0.1:9030/post/2024/01/hello-world");
387 println!();
388 println!("⚡ Performance: ~95,000 req/s | Latency: ~0.59ms");
389 println!("📚 Database: Integrated connection pool");
390 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
391 println!();
392}
393
394fn main() -> Result<(), Box<dyn std::error::Error>> {
395 let config = AppConfig::load("src/bin/config.toml").expect("Failed to load config");
396
397 let dbconf = &config.database;
398 println!("{:?}", dbconf);
399 let dbconfig = ConnectionConfig::new(
400 &dbconf.host,
401 dbconf.port,
402 &dbconf.username,
403 &dbconf.password,
404 &dbconf.database,
405 );
406
407 let db_pool = Arc::new(DbConnectionPool::new(
409 PostgresDriverFactory,
410 dbconfig,
411 dbconf.max_size,
412 ));
413
414 let mut router = Router::new_with_db(db_pool.clone());
415
416 router.use_middleware(LoggerMiddleware);
417 router.use_middleware(RecoveryMiddleware);
418 router.use_middleware(CORSMiddleware::new(
419 config.cors.allowed_origins.clone(),
420 config.cors.allowed_methods.clone(),
421 config.cors.allowed_headers.clone(),
422 ));
423
424 router.get("/", handle_home);
426 router.get("/hello/:name", hello_handler);
427 router.post("/api/user", create_user_handler);
428 router.get("/api/users", get_users_handler);
429 router.get("/slow", slow_handler);
430 router.get("/about", handle_about);
431 router.get("/status", handle_status);
432 router.get("/json", handle_json);
433 router.get("/user/:id", handle_user);
434 router.get("/post/:year/:month/:slug", handle_post);
435 router.get("/hello", handle_hello);
436 router.get("/headers", headers_handler);
437 router.get("/form", form_page_handler);
438 router.post("/form", form_handler);
439
440 router.websocket("/ws", ws_handler);
441
442 router.get("/pool/stats", pool_stats_handler);
443
444 router.serve_static("/static", &config.server.static_dir);
445
446 go(grweb_print);
448 let server = Server::new(config, router);
450 if let Err(e) = server.run() {
451 eprintln!("Server error: {}", e);
452 }
453 Ok(())
454}