|
use ext_php_rs::prelude::*; |
|
use hyper::server::conn::http1; |
|
use hyper::service::service_fn; |
|
use hyper::{body::Bytes, Request, Response}; |
|
use hyper_util::rt::TokioIo; |
|
use http_body_util::Full; |
|
use std::convert::Infallible; |
|
use std::net::SocketAddr; |
|
use std::sync::atomic::{AtomicBool, Ordering}; |
|
use std::sync::Mutex; |
|
use tokio::net::TcpListener; |
|
|
|
// グローバルなサーバー状態管理 |
|
static SERVER_RUNNING: AtomicBool = AtomicBool::new(false); |
|
static SERVER_HANDLE: Mutex<Option<tokio::task::JoinHandle<()>>> = Mutex::new(None); |
|
|
|
// シンプルなHTTPレスポンスハンドラー |
|
async fn handle_request( |
|
_req: Request<hyper::body::Incoming>, |
|
) -> Result<Response<Full<Bytes>>, Infallible> { |
|
let response_body = r#" |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Tokio HTTP Server</title> |
|
</head> |
|
<body> |
|
<h1>Hello from Tokio HTTP Server!</h1> |
|
<p>This server is running from a PHP extension written in Rust.</p> |
|
<p>Powered by ext-php-rs + Tokio + Hyper</p> |
|
</body> |
|
</html> |
|
"#; |
|
|
|
Ok(Response::builder() |
|
.status(200) |
|
.header("Content-Type", "text/html; charset=utf-8") |
|
.header("Server", "tokio-http-php-extension/0.1.0") |
|
.body(Full::new(Bytes::from(response_body))) |
|
.unwrap()) |
|
} |
|
|
|
/// PHP関数: start_tokio_server(host, port) |
|
/// TokioベースのHTTP/1サーバーを起動する |
|
#[php_function] |
|
pub fn start_tokio_server(host: Option<String>, port: Option<i64>) -> PhpResult<bool> { |
|
let host = host.unwrap_or_else(|| "127.0.0.1".to_string()); |
|
let port = port.unwrap_or(8080) as u16; |
|
|
|
// 既にサーバーが動いている場合は停止 |
|
if SERVER_RUNNING.load(Ordering::Relaxed) { |
|
stop_tokio_server()?; |
|
} |
|
|
|
// 新しいTokioランタイムを作成してサーバーを起動 |
|
let runtime = tokio::runtime::Runtime::new() |
|
.map_err(|e| PhpException::default(format!("Failed to create Tokio runtime: {}", e)))?; |
|
|
|
let addr: SocketAddr = format!("{}:{}", host, port) |
|
.parse() |
|
.map_err(|e| PhpException::default(format!("Invalid address: {}", e)))?; |
|
|
|
// サーバータスクをスポーン |
|
let handle = runtime.spawn(async move { |
|
if let Err(e) = run_server_with_graceful_shutdown(addr).await { |
|
eprintln!("Server error: {}", e); |
|
} |
|
}); |
|
|
|
// ハンドルを保存 |
|
if let Ok(mut handle_guard) = SERVER_HANDLE.lock() { |
|
*handle_guard = Some(handle); |
|
} |
|
|
|
SERVER_RUNNING.store(true, Ordering::Relaxed); |
|
|
|
// ランタイムを別スレッドで実行 |
|
std::thread::spawn(move || { |
|
runtime.block_on(async { |
|
// サーバーが停止するまで待機 |
|
while SERVER_RUNNING.load(Ordering::Relaxed) { |
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; |
|
} |
|
}); |
|
}); |
|
|
|
Ok(true) |
|
} |
|
|
|
/// Go の ListenAndServe 風の実装:グレースフルシャットダウン付き |
|
async fn run_server_with_graceful_shutdown(addr: SocketAddr) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { |
|
let listener = TcpListener::bind(addr).await?; |
|
println!("Tokio HTTP server listening on http://{}", addr); |
|
|
|
loop { |
|
tokio::select! { |
|
// 新しい接続を受け付け |
|
result = listener.accept() => { |
|
match result { |
|
Ok((stream, _)) => { |
|
let io = TokioIo::new(stream); |
|
|
|
tokio::task::spawn(async move { |
|
if let Err(err) = http1::Builder::new() |
|
.serve_connection(io, service_fn(handle_request)) |
|
.await |
|
{ |
|
eprintln!("Error serving connection: {:?}", err); |
|
} |
|
}); |
|
} |
|
Err(e) => { |
|
eprintln!("Error accepting connection: {}", e); |
|
} |
|
} |
|
} |
|
|
|
// Ctrl+C でグレースフルシャットダウン(クロスプラットフォーム対応) |
|
_ = tokio::signal::ctrl_c() => { |
|
println!("\nReceived Ctrl+C, shutting down gracefully..."); |
|
SERVER_RUNNING.store(false, Ordering::Relaxed); |
|
break; |
|
} |
|
|
|
// サーバー停止フラグをチェック |
|
_ = async { |
|
while SERVER_RUNNING.load(Ordering::Relaxed) { |
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; |
|
} |
|
} => { |
|
println!("Server stop requested"); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
println!("Server shutdown complete"); |
|
Ok(()) |
|
} |
|
|
|
/// PHP関数: stop_tokio_server() |
|
/// 実行中のTokioサーバーを停止する |
|
#[php_function] |
|
pub fn stop_tokio_server() -> PhpResult<bool> { |
|
if !SERVER_RUNNING.load(Ordering::Relaxed) { |
|
return Ok(false); // 既に停止している |
|
} |
|
|
|
SERVER_RUNNING.store(false, Ordering::Relaxed); |
|
|
|
// サーバーハンドルをabort |
|
if let Ok(mut handle_guard) = SERVER_HANDLE.lock() { |
|
if let Some(handle) = handle_guard.take() { |
|
handle.abort(); |
|
} |
|
} |
|
|
|
println!("Tokio HTTP server stopped"); |
|
Ok(true) |
|
} |
|
|
|
/// PHP関数: is_tokio_server_running() |
|
/// サーバーの実行状態を確認する |
|
#[php_function] |
|
pub fn is_tokio_server_running() -> bool { |
|
SERVER_RUNNING.load(Ordering::Relaxed) |
|
} |
|
|
|
/// PHP関数: start_tokio_server_forever(host, port) |
|
/// Go の ListenAndServe 風:グレースフルシャットダウン付きで永続実行 |
|
#[php_function] |
|
pub fn start_tokio_server_forever(host: Option<String>, port: Option<i64>) -> PhpResult<bool> { |
|
let host = host.unwrap_or_else(|| "127.0.0.1".to_string()); |
|
let port = port.unwrap_or(8080) as u16; |
|
|
|
// 既にサーバーが動いている場合は停止 |
|
if SERVER_RUNNING.load(Ordering::Relaxed) { |
|
stop_tokio_server()?; |
|
// 少し待ってから新しいサーバーを起動 |
|
std::thread::sleep(std::time::Duration::from_millis(500)); |
|
} |
|
|
|
let addr: SocketAddr = format!("{}:{}", host, port) |
|
.parse() |
|
.map_err(|e| PhpException::default(format!("Invalid address: {}", e)))?; |
|
|
|
SERVER_RUNNING.store(true, Ordering::Relaxed); |
|
|
|
// ブロッキング実行(Go の ListenAndServe と同様) |
|
let runtime = tokio::runtime::Runtime::new() |
|
.map_err(|e| PhpException::default(format!("Failed to create Tokio runtime: {}", e)))?; |
|
|
|
println!("Starting server on http://{}:{} (Press Ctrl+C to stop)", host, port); |
|
|
|
// ブロッキング実行 |
|
runtime.block_on(async move { |
|
if let Err(e) = run_server_with_graceful_shutdown(addr).await { |
|
eprintln!("Server error: {}", e); |
|
} |
|
}); |
|
|
|
SERVER_RUNNING.store(false, Ordering::Relaxed); |
|
Ok(true) |
|
} |
|
|
|
// PHPエクステンションのモジュール定義 |
|
#[php_module] |
|
pub fn get_module(module: ModuleBuilder) -> ModuleBuilder { |
|
module |
|
} |