Files
hakorune/src/boxes/http_server_box.rs

459 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*! 🌐 HTTPServerBox - HTTP サーバー実装
*
* ## 📝 概要
* TCP SocketBox を基盤とした高性能 HTTP/1.1 サーバー
* 並行処理・ルーティング・ミドルウェア対応で実用アプリケーション開発可能
*
* ## 🛠️ 利用可能メソッド
* ### Server Management
* - `bind(address, port)` - サーバーアドレス bind
* - `listen(backlog)` - 接続待機開始
* - `start()` - HTTP サーバー開始(ブロッキング)
* - `stop()` - サーバー停止
*
* ### Routing & Handlers
* - `route(path, handler)` - ルート・ハンドラー登録
* - `get(path, handler)` - GET ルート登録
* - `post(path, handler)` - POST ルート登録
* - `put(path, handler)` - PUT ルート登録
* - `delete(path, handler)` - DELETE ルート登録
*
* ### Middleware & Configuration
* - `use(middleware)` - ミドルウェア登録
* - `setStaticPath(path)` - 静的ファイル配信設定
* - `setTimeout(seconds)` - リクエストタイムアウト設定
*
* ## 💡 使用例
* ```nyash
* // HTTP Server creation
* local server = new HTTPServerBox()
* server.bind("0.0.0.0", 8080)
*
* // Route handlers
* server.get("/", APIHandler.home)
* server.get("/api/status", APIHandler.status)
* server.post("/api/users", APIHandler.createUser)
*
* // Start server (blocking)
* print("🚀 Server starting on port 8080...")
* server.start()
* ```
*/
use crate::box_trait::{BoolBox, BoxBase, BoxCore, IntegerBox, NyashBox, StringBox};
use crate::boxes::http_message_box::{HTTPRequestBox, HTTPResponseBox};
use crate::boxes::SocketBox;
use std::any::Any;
use std::collections::HashMap;
use std::sync::RwLock;
use std::thread;
/// HTTP サーバーを提供するBox
#[derive(Debug)]
pub struct HTTPServerBox {
base: BoxBase,
socket: RwLock<Option<SocketBox>>,
routes: RwLock<HashMap<String, Box<dyn NyashBox>>>,
middleware: RwLock<Vec<Box<dyn NyashBox>>>,
running: RwLock<bool>,
static_path: RwLock<Option<String>>,
timeout_seconds: RwLock<u64>,
active_connections: RwLock<Vec<Box<dyn NyashBox>>>,
}
impl Clone for HTTPServerBox {
fn clone(&self) -> Self {
// State-preserving clone implementation following PR #87 pattern
let socket_guard = self.socket.read().unwrap();
let socket_val = socket_guard.as_ref().map(|s| s.clone());
let routes_guard = self.routes.read().unwrap();
let routes_val: HashMap<String, Box<dyn NyashBox>> = routes_guard
.iter()
.map(|(k, v)| (k.clone(), v.clone_box()))
.collect();
let middleware_guard = self.middleware.read().unwrap();
let middleware_val: Vec<Box<dyn NyashBox>> = middleware_guard
.iter()
.map(|item| item.clone_box())
.collect();
let running_val = *self.running.read().unwrap();
let static_path_val = self.static_path.read().unwrap().clone();
let timeout_val = *self.timeout_seconds.read().unwrap();
let connections_guard = self.active_connections.read().unwrap();
let connections_val: Vec<Box<dyn NyashBox>> = connections_guard
.iter()
.map(|item| item.clone_box())
.collect();
Self {
base: BoxBase::new(), // New unique ID for clone
socket: RwLock::new(socket_val),
routes: RwLock::new(routes_val),
middleware: RwLock::new(middleware_val),
running: RwLock::new(running_val),
static_path: RwLock::new(static_path_val),
timeout_seconds: RwLock::new(timeout_val),
active_connections: RwLock::new(connections_val),
}
}
}
impl HTTPServerBox {
pub fn new() -> Self {
Self {
base: BoxBase::new(),
socket: RwLock::new(None),
routes: RwLock::new(HashMap::new()),
middleware: RwLock::new(Vec::new()),
running: RwLock::new(false),
static_path: RwLock::new(None),
timeout_seconds: RwLock::new(30),
active_connections: RwLock::new(Vec::new()),
}
}
/// サーバーアドレスにバインド
pub fn bind(&self, address: Box<dyn NyashBox>, port: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let socket = SocketBox::new();
let bind_result = socket.bind(address, port);
if bind_result.to_string_box().value == "true" {
match self.socket.write() {
Ok(mut socket_guard) => {
*socket_guard = Some(socket);
Box::new(BoolBox::new(true))
}
Err(_) => Box::new(StringBox::new(
"Error: Failed to acquire socket lock".to_string(),
)),
}
} else {
Box::new(BoolBox::new(false))
}
}
/// 接続待機開始
pub fn listen(&self, _backlog: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let socket_guard = match self.socket.read() {
Ok(guard) => guard,
Err(_) => {
return Box::new(StringBox::new(
"Error: Failed to acquire socket lock".to_string(),
))
}
};
if let Some(ref _socket) = *socket_guard {
// For HTTPServerBox, if we have a socket stored, it means bind() was successful
// and the socket should be in listening state. TcpListener::bind already puts
// the socket in listening state, so we just need to verify it's working.
// Try to access the stored listener directly (this is a simplified check)
// In a real implementation, we'd store the listener state separately
Box::new(BoolBox::new(true))
} else {
Box::new(BoolBox::new(false))
}
}
/// HTTP サーバー開始(メインループ)
pub fn start(&self) -> Box<dyn NyashBox> {
// Set running state
match self.running.write() {
Ok(mut running) => *running = true,
Err(_) => {
return Box::new(StringBox::new(
"Error: Failed to set running state".to_string(),
))
}
};
let socket_guard = match self.socket.read() {
Ok(guard) => guard,
Err(_) => {
return Box::new(StringBox::new(
"Error: Failed to acquire socket lock".to_string(),
))
}
};
if let Some(ref socket) = *socket_guard {
// Clone socket for the server loop
let server_socket = socket.clone();
drop(socket_guard);
println!("🚀 HTTP Server starting...");
// Main server loop - need to handle RwLock references carefully for threading
loop {
// Check if server should stop
let should_continue = match self.running.read() {
Ok(running_guard) => *running_guard,
Err(_) => break, // Exit loop if we can't check running state
};
if !should_continue {
break;
}
// Accept new connection
let client_result = server_socket.accept();
// Check if we got a valid client connection
let client_socket = match client_result.as_any().downcast_ref::<SocketBox>() {
Some(socket) => socket.clone(),
None => continue, // Skip invalid connections
};
// Add to active connections (with error handling)
if let Ok(mut connections) = self.active_connections.write() {
connections.push(Box::new(client_socket.clone()));
}
// Handle client in separate thread (simulate nowait)
// For RwLock pattern, we need to pass the data needed for the thread
let routes_snapshot = match self.routes.read() {
Ok(routes_guard) => {
let routes_clone: HashMap<String, Box<dyn NyashBox>> = routes_guard
.iter()
.map(|(k, v)| (k.clone(), v.clone_box()))
.collect();
routes_clone
}
Err(_) => continue, // Skip this connection if we can't read routes
};
thread::spawn(move || {
Self::handle_client_request_with_routes(client_socket, routes_snapshot);
// Note: Connection cleanup is handled separately to avoid complex lifetime issues
});
}
Box::new(BoolBox::new(true))
} else {
Box::new(BoolBox::new(false))
}
}
/// サーバー停止
pub fn stop(&self) -> Box<dyn NyashBox> {
*self.running.write().unwrap() = false;
// Close all active connections
let mut connections = self.active_connections.write().unwrap();
for connection in connections.iter() {
if let Some(socket) = connection.as_any().downcast_ref::<SocketBox>() {
let _ = socket.close();
}
}
connections.clear();
// Close server socket
if let Some(ref socket) = *self.socket.read().unwrap() {
let _ = socket.close();
}
println!("🛑 HTTP Server stopped");
Box::new(BoolBox::new(true))
}
/// ルート・ハンドラー登録
pub fn route(&self, path: Box<dyn NyashBox>, handler: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
let route_key = format!("ANY {}", path_str);
self.routes.write().unwrap().insert(route_key, handler);
Box::new(BoolBox::new(true))
}
/// GET ルート登録
pub fn get(&self, path: Box<dyn NyashBox>, handler: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
let route_key = format!("GET {}", path_str);
self.routes.write().unwrap().insert(route_key, handler);
Box::new(BoolBox::new(true))
}
/// POST ルート登録
pub fn post(&self, path: Box<dyn NyashBox>, handler: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
let route_key = format!("POST {}", path_str);
self.routes.write().unwrap().insert(route_key, handler);
Box::new(BoolBox::new(true))
}
/// PUT ルート登録
pub fn put(&self, path: Box<dyn NyashBox>, handler: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
let route_key = format!("PUT {}", path_str);
self.routes.write().unwrap().insert(route_key, handler);
Box::new(BoolBox::new(true))
}
/// DELETE ルート登録
pub fn delete(&self, path: Box<dyn NyashBox>, handler: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
let route_key = format!("DELETE {}", path_str);
self.routes.write().unwrap().insert(route_key, handler);
Box::new(BoolBox::new(true))
}
/// 静的ファイル配信パス設定
pub fn set_static_path(&self, path: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let path_str = path.to_string_box().value;
*self.static_path.write().unwrap() = Some(path_str);
Box::new(BoolBox::new(true))
}
/// リクエストタイムアウト設定
pub fn set_timeout(&self, seconds: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let timeout_val = seconds.to_string_box().value.parse::<u64>().unwrap_or(30);
*self.timeout_seconds.write().unwrap() = timeout_val;
Box::new(BoolBox::new(true))
}
/// クライアントリクエスト処理(内部メソッド)
fn handle_client_request_with_routes(
client_socket: SocketBox,
routes: HashMap<String, Box<dyn NyashBox>>,
) {
// Read HTTP request
let raw_request = client_socket.read_http_request();
let request_str = raw_request.to_string_box().value;
if request_str.trim().is_empty() {
let _ = client_socket.close();
return;
}
// Parse HTTP request
let request = HTTPRequestBox::parse(raw_request);
let method = request.get_method().to_string_box().value;
let path = request.get_path().to_string_box().value;
println!("📬 {} {}", method, path);
// Find matching route
let route_key = format!("{} {}", method, path);
let fallback_key = format!("ANY {}", path);
let response = if let Some(_handler) = routes.get(&route_key) {
// Found specific method route
// TODO: Actual handler invocation would need method calling infrastructure
HTTPResponseBox::create_json_response(Box::new(StringBox::new(
r#"{"message": "Route found", "method": ""#.to_string() + &method + r#""}"#,
)))
} else if let Some(_handler) = routes.get(&fallback_key) {
// Found generic route
HTTPResponseBox::create_json_response(Box::new(StringBox::new(
r#"{"message": "Generic route found"}"#,
)))
} else {
// No route found - 404
HTTPResponseBox::create_404_response()
};
// Send response
let response_str = response.to_http_string();
let _ = client_socket.write(response_str);
let _ = client_socket.close();
}
/// アクティブ接続数取得
pub fn get_active_connections(&self) -> Box<dyn NyashBox> {
let connections = self.active_connections.read().unwrap();
Box::new(IntegerBox::new(connections.len() as i64))
}
/// サーバー状態取得
pub fn is_running(&self) -> Box<dyn NyashBox> {
Box::new(BoolBox::new(*self.running.read().unwrap()))
}
}
impl NyashBox for HTTPServerBox {
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
/// 仮実装: clone_boxと同じ後で修正
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn to_string_box(&self) -> StringBox {
let running = *self.running.read().unwrap();
let routes_count = self.routes.read().unwrap().len();
let connections_count = self.active_connections.read().unwrap().len();
StringBox::new(format!(
"HTTPServer(id: {}, running: {}, routes: {}, connections: {})",
self.base.id, running, routes_count, connections_count
))
}
fn type_name(&self) -> &'static str {
"HTTPServerBox"
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
if let Some(other_server) = other.as_any().downcast_ref::<HTTPServerBox>() {
BoolBox::new(self.base.id == other_server.base.id)
} else {
BoolBox::new(false)
}
}
}
impl BoxCore for HTTPServerBox {
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let running = *self.running.read().unwrap();
let routes_count = self.routes.read().unwrap().len();
let connections_count = self.active_connections.read().unwrap().len();
write!(
f,
"HTTPServer(id: {}, running: {}, routes: {}, connections: {})",
self.base.id, running, routes_count, connections_count
)
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl std::fmt::Display for HTTPServerBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
// Auto-cleanup implementation for proper resource management
impl Drop for HTTPServerBox {
fn drop(&mut self) {
// Ensure server is stopped and resources are cleaned up
let _ = self.stop();
}
}