tiny_http で HTTP/1 サーバーの PHP 拡張
[package ]
name = " tiny_http_php"
version = " 0.1.0"
edition = " 2021"
[lib ]
crate-type = [" cdylib" ]
[dependencies ]
ext-php-rs = " 0.15"
tiny_http = " 0.12"
#![ cfg_attr( windows, feature( abi_vectorcall) ) ]
use ext_php_rs:: exception:: PhpException ;
use ext_php_rs:: prelude:: * ;
use ext_php_rs:: types:: ZendCallable ;
use std:: io:: Read ;
use tiny_http:: { Header , Response , Server } ;
fn sanitize_no_nul ( mut s : String ) -> String {
// PHP 側へ例外メッセージとして渡す文字列に NUL が混ざると fatal になるので除去
s. retain ( |c| c != '\0' ) ;
s
}
/// tiny_http で HTTP/1 サーバーを起動し、各リクエストごとに PHP callable を呼び出して本文を返します。
///
/// callable のシグネチャ例:
/// function (string $method, string $path, string $body): string
///
/// @param string $addr 例: "127.0.0.1:8080"
/// @param callable $handler
#[ php_function]
pub fn tiny_http_serve ( addr : String , handler : ZendCallable ) -> Result < ( ) , PhpException > {
let server = Server :: http ( & addr)
. map_err ( |e| PhpException :: default ( sanitize_no_nul ( format ! ( "bind failed: {e}" ) ) ) ) ?;
// Content-Type: text/plain; charset=utf-8
let ct = Header :: from_bytes ( "Content-Type" , "text/plain; charset=utf-8" )
. map_err ( |e| PhpException :: default ( ... format !( "header failed: {e}" ) ...) ) ?;
for mut req in server. incoming_requests ( ) {
let method = req. method ( ) . as_str ( ) . to_string ( ) ;
let path = req. url ( ) . to_string ( ) ;
// 最大 1MB だけ読む(最小安全策)
let mut body_buf = Vec :: new ( ) ;
let _ = req
. as_reader ( )
. take ( 1024 * 1024 )
. read_to_end ( & mut body_buf) ;
let body = String :: from_utf8_lossy ( & body_buf) . into_owned ( ) ;
// PHP callable を呼ぶ(失敗したら例外が投げられる)
let z = handler
. try_call ( vec ! [ & method, & path, & body] )
. map_err ( |e| PhpException :: default ( sanitize_no_nul ( format ! ( "handler failed: {e}" ) ) ) ) ?;
// 戻り値は「文字列ならそれ」を採用(それ以外は空文字)
let resp_body = z. string ( ) . unwrap_or_default ( ) ;
let resp = Response :: from_string ( resp_body)
. with_status_code ( 200 )
. with_header ( ct. clone ( ) ) ;
// 送信(失敗しても次へ)
let _ = req. respond ( resp) ;
}
Ok ( ( ) )
}
#[ php_module]
pub fn get_module ( module : ModuleBuilder ) -> ModuleBuilder {
module. function ( wrap_function ! ( tiny_http_serve) )
}
<?php
// 実行例:
// php -d extension=target/debug/libtiny_http_php.so server.php
tiny_http_serve ("127.0.0.1:8080 " , function (string $ method , string $ path , string $ body ): string {
return "method= {$ method }\npath= {$ path }\nbody= {$ body }\n" ;
});