|
/* |
|
* H2O TLS/HTTP2対応最小サーバー with FastCGI |
|
* Debian 12 Bookworm用 |
|
*/ |
|
#include <errno.h> |
|
#include <limits.h> |
|
#include <netinet/in.h> |
|
#include <signal.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> /* strdup, strtok用 */ |
|
#include <unistd.h> /* access用 */ |
|
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
#include <sys/un.h> /* Unix socket用 */ |
|
#include <openssl/ssl.h> |
|
#include <openssl/err.h> |
|
#include "h2o.h" |
|
#include "h2o/http1.h" |
|
#include "h2o/http2.h" |
|
|
|
/* FastCGI関数の存在チェックは実際の使用時にエラーで判断 */ |
|
|
|
static h2o_globalconf_t config; |
|
static h2o_context_t ctx; |
|
static h2o_accept_ctx_t accept_ctx; |
|
|
|
/* シンプルなハンドラー関数 */ |
|
static int hello_handler(h2o_handler_t *self, h2o_req_t *req) |
|
{ |
|
static h2o_generator_t generator = {NULL, NULL}; |
|
|
|
if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) |
|
return -1; |
|
|
|
/* レスポンス作成 */ |
|
h2o_iovec_t body = h2o_strdup(&req->pool, |
|
"Hello, World!\nProtocol: HTTP/", SIZE_MAX); |
|
|
|
/* プロトコルバージョン追加 */ |
|
if (req->version == 0x0200) { |
|
h2o_iovec_t version = h2o_strdup(&req->pool, "2.0\n", SIZE_MAX); |
|
body = h2o_concat(&req->pool, body, version); |
|
} else { |
|
h2o_iovec_t version = h2o_strdup(&req->pool, "1.1\n", SIZE_MAX); |
|
body = h2o_concat(&req->pool, body, version); |
|
} |
|
|
|
req->res.status = 200; |
|
req->res.reason = "OK"; |
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, |
|
NULL, H2O_STRLIT("text/plain; charset=utf-8")); |
|
|
|
h2o_start_response(req, &generator); |
|
h2o_send(req, &body, 1, 1); |
|
|
|
return 0; |
|
} |
|
|
|
/* FastCGI設定構造体 */ |
|
struct fastcgi_config_t { |
|
char *host; |
|
uint16_t port; |
|
char *socket_path; |
|
char *spawn_command; |
|
}; |
|
|
|
/* FastCGIハンドラー登録(TCP接続) */ |
|
static h2o_pathconf_t *register_fastcgi_tcp(h2o_hostconf_t *hostconf, |
|
const char *path, |
|
const char *host, |
|
uint16_t port, |
|
const char *document_root) |
|
{ |
|
h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); |
|
|
|
/* FastCGI設定構造体の初期化 */ |
|
h2o_fastcgi_config_vars_t vars = {0}; |
|
vars.io_timeout = 30000; /* 30秒タイムアウト */ |
|
vars.keepalive_timeout = 5000; /* 5秒キープアライブ */ |
|
|
|
/* ドキュメントルートを設定(h2o_iovec_tとして) */ |
|
vars.document_root = h2o_iovec_init(document_root, strlen(document_root)); |
|
|
|
/* TCP接続でFastCGIハンドラーを登録 */ |
|
h2o_fastcgi_register_by_hostport(pathconf, host, port, &vars); |
|
|
|
printf("FastCGI TCP handler registered for path: %s -> %s:%d\n", path, host, port); |
|
printf("Document root: %.*s\n", (int)vars.document_root.len, vars.document_root.base); |
|
return pathconf; |
|
} |
|
|
|
/* FastCGIハンドラー登録(Unix socket) */ |
|
static h2o_pathconf_t *register_fastcgi_socket(h2o_hostconf_t *hostconf, |
|
const char *path, |
|
const char *socket_path) |
|
{ |
|
h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); |
|
|
|
/* FastCGI設定構造体の初期化 */ |
|
h2o_fastcgi_config_vars_t vars = {0}; |
|
vars.io_timeout = 30000; |
|
vars.keepalive_timeout = 5000; |
|
|
|
/* Unix socketのアドレス構造体を作成 */ |
|
struct sockaddr_un addr; |
|
memset(&addr, 0, sizeof(addr)); |
|
addr.sun_family = AF_UNIX; |
|
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); |
|
|
|
/* Unix socketでFastCGIハンドラーを登録 */ |
|
h2o_fastcgi_register_by_address(pathconf, (struct sockaddr*)&addr, sizeof(addr), &vars); |
|
|
|
return pathconf; |
|
} |
|
|
|
/* spawn機能が利用できないことを示すハンドラー */ |
|
static int spawn_unavailable_handler(h2o_handler_t *self, h2o_req_t *req) |
|
{ |
|
static h2o_generator_t generator = {NULL, NULL}; |
|
|
|
h2o_iovec_t body = h2o_strdup(&req->pool, |
|
"FastCGI spawn process feature is not available in this H2O build.\n" |
|
"Use TCP or Unix socket connection instead.\n", |
|
SIZE_MAX); |
|
|
|
req->res.status = 503; |
|
req->res.reason = "Service Unavailable"; |
|
h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, |
|
NULL, H2O_STRLIT("text/plain; charset=utf-8")); |
|
|
|
h2o_start_response(req, &generator); |
|
h2o_send(req, &body, 1, 1); |
|
return 0; |
|
} |
|
|
|
/* FastCGIハンドラー登録(spawn process) - この機能は利用不可 */ |
|
static h2o_pathconf_t *register_fastcgi_spawn(h2o_hostconf_t *hostconf, |
|
const char *path, |
|
const char *command) |
|
{ |
|
h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); |
|
h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); |
|
|
|
handler->on_req = spawn_unavailable_handler; |
|
return pathconf; |
|
} |
|
|
|
|
|
/* ハンドラー登録 */ |
|
static h2o_pathconf_t *register_handler(h2o_hostconf_t *hostconf, |
|
const char *path, |
|
int (*on_req)(h2o_handler_t *, h2o_req_t *)) |
|
{ |
|
h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, path, 0); |
|
h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); |
|
handler->on_req = on_req; |
|
return pathconf; |
|
} |
|
|
|
#if H2O_USE_LIBUV |
|
/* libuv使用時のコールバック */ |
|
static void on_accept(uv_stream_t *listener, int status) |
|
{ |
|
uv_tcp_t *conn; |
|
h2o_socket_t *sock; |
|
|
|
if (status != 0) |
|
return; |
|
|
|
conn = h2o_mem_alloc(sizeof(*conn)); |
|
uv_tcp_init(listener->loop, conn); |
|
|
|
if (uv_accept(listener, (uv_stream_t *)conn) != 0) { |
|
uv_close((uv_handle_t *)conn, (uv_close_cb)free); |
|
return; |
|
} |
|
|
|
sock = h2o_uv_socket_create((uv_stream_t *)conn, (uv_close_cb)free); |
|
h2o_accept(&accept_ctx, sock); |
|
} |
|
|
|
static int create_listener(void) |
|
{ |
|
static uv_tcp_t listener; |
|
struct sockaddr_in addr; |
|
int r; |
|
|
|
uv_tcp_init(ctx.loop, &listener); |
|
uv_ip4_addr("0.0.0.0", 8443, &addr); /* HTTPS用ポート */ |
|
|
|
if ((r = uv_tcp_bind(&listener, (struct sockaddr *)&addr, 0)) != 0) { |
|
fprintf(stderr, "uv_tcp_bind: %s\n", uv_strerror(r)); |
|
goto Error; |
|
} |
|
if ((r = uv_listen((uv_stream_t *)&listener, 128, on_accept)) != 0) { |
|
fprintf(stderr, "uv_listen: %s\n", uv_strerror(r)); |
|
goto Error; |
|
} |
|
|
|
printf("Server listening on https://0.0.0.0:8443\n"); |
|
return 0; |
|
|
|
Error: |
|
uv_close((uv_handle_t *)&listener, NULL); |
|
return r; |
|
} |
|
|
|
#else |
|
/* evloop使用時のコールバック */ |
|
static void on_accept(h2o_socket_t *listener, const char *err) |
|
{ |
|
h2o_socket_t *sock; |
|
|
|
if (err != NULL) { |
|
return; |
|
} |
|
|
|
if ((sock = h2o_evloop_socket_accept(listener)) == NULL) |
|
return; |
|
|
|
h2o_accept(&accept_ctx, sock); |
|
} |
|
|
|
static int create_listener(void) |
|
{ |
|
struct sockaddr_in addr; |
|
int fd, reuseaddr_flag = 1; |
|
h2o_socket_t *sock; |
|
|
|
memset(&addr, 0, sizeof(addr)); |
|
addr.sin_family = AF_INET; |
|
addr.sin_addr.s_addr = INADDR_ANY; /* 全てのインターフェースで待受 */ |
|
addr.sin_port = htons(8443); /* HTTPS用ポート */ |
|
|
|
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 || |
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 || |
|
bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || |
|
listen(fd, SOMAXCONN) != 0) { |
|
perror("socket setup failed"); |
|
return -1; |
|
} |
|
|
|
sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ); |
|
h2o_socket_read_start(sock, on_accept); |
|
|
|
printf("Server listening on https://0.0.0.0:8443\n"); |
|
return 0; |
|
} |
|
#endif |
|
|
|
/* SSL/TLS設定 */ |
|
static int setup_ssl(const char *cert_file, const char *key_file) |
|
{ |
|
/* OpenSSL初期化 */ |
|
SSL_load_error_strings(); |
|
SSL_library_init(); |
|
OpenSSL_add_all_algorithms(); |
|
|
|
/* SSL コンテキスト作成 */ |
|
accept_ctx.ssl_ctx = SSL_CTX_new(TLS_server_method()); |
|
if (!accept_ctx.ssl_ctx) { |
|
fprintf(stderr, "Failed to create SSL context\n"); |
|
return -1; |
|
} |
|
|
|
/* セキュアな設定 */ |
|
SSL_CTX_set_options(accept_ctx.ssl_ctx, |
|
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); |
|
SSL_CTX_set_min_proto_version(accept_ctx.ssl_ctx, TLS1_2_VERSION); |
|
|
|
/* 証明書とプライベートキーの読み込み */ |
|
if (SSL_CTX_use_certificate_file(accept_ctx.ssl_ctx, cert_file, SSL_FILETYPE_PEM) != 1) { |
|
fprintf(stderr, "Failed to load certificate file: %s\n", cert_file); |
|
ERR_print_errors_fp(stderr); |
|
return -1; |
|
} |
|
|
|
if (SSL_CTX_use_PrivateKey_file(accept_ctx.ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { |
|
fprintf(stderr, "Failed to load private key file: %s\n", key_file); |
|
ERR_print_errors_fp(stderr); |
|
return -1; |
|
} |
|
|
|
/* HTTP/2プロトコルネゴシエーション設定 */ |
|
#if H2O_USE_NPN |
|
h2o_ssl_register_npn_protocols(accept_ctx.ssl_ctx, h2o_http2_npn_protocols); |
|
#endif |
|
#if H2O_USE_ALPN |
|
h2o_ssl_register_alpn_protocols(accept_ctx.ssl_ctx, h2o_http2_alpn_protocols); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
/* 自己署名証明書生成(開発用) */ |
|
static int generate_self_signed_cert(const char *cert_file, const char *key_file) |
|
{ |
|
printf("Generating self-signed certificate...\n"); |
|
|
|
char cmd[512]; |
|
snprintf(cmd, sizeof(cmd), |
|
"openssl req -x509 -newkey rsa:2048 -keyout %s -out %s -days 365 -nodes " |
|
"-subj '/CN=localhost' 2>/dev/null", key_file, cert_file); |
|
|
|
if (system(cmd) != 0) { |
|
fprintf(stderr, "Failed to generate self-signed certificate\n"); |
|
return -1; |
|
} |
|
|
|
printf("Certificate generated: %s, %s\n", cert_file, key_file); |
|
return 0; |
|
} |
|
|
|
/* 使用方法表示 */ |
|
static void print_usage(const char *prog_name) |
|
{ |
|
printf("Usage: %s [cert_file key_file]\n", prog_name); |
|
printf("FastCGI Examples:\n"); |
|
printf(" PHP-FPM (TCP): Start with 'sudo systemctl start php8.2-fpm' and access /php/\n"); |
|
printf(" PHP-FPM (Socket): Use Unix socket and access /socket/\n"); |
|
printf(" Note: Spawn process feature is not available in this H2O build\n"); |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
h2o_hostconf_t *hostconf; |
|
const char *cert_file = "server.crt"; |
|
const char *key_file = "server.key"; |
|
char doc_root[512]; /* 絶対パス用バッファ */ |
|
|
|
/* 絶対パスでドキュメントルートを取得 */ |
|
if (getcwd(doc_root, sizeof(doc_root)) == NULL) { |
|
perror("getcwd"); |
|
return 1; |
|
} |
|
strcat(doc_root, "/public"); |
|
|
|
/* コマンドライン引数でファイル名を指定可能 */ |
|
if (argc >= 3) { |
|
cert_file = argv[1]; |
|
key_file = argv[2]; |
|
} |
|
|
|
/* SIGPIPEを無視 */ |
|
signal(SIGPIPE, SIG_IGN); |
|
|
|
/* 証明書ファイルが存在しない場合は生成 */ |
|
if (access(cert_file, F_OK) != 0 || access(key_file, F_OK) != 0) { |
|
if (generate_self_signed_cert(cert_file, key_file) != 0) { |
|
return 1; |
|
} |
|
} |
|
|
|
/* H2O設定初期化 */ |
|
h2o_config_init(&config); |
|
hostconf = h2o_config_register_host(&config, |
|
h2o_iovec_init(H2O_STRLIT("default")), |
|
65535); |
|
|
|
/* ハンドラー登録(順序が重要!) */ |
|
|
|
/* 1. 最初に具体的なパスのFastCGIハンドラーを登録 */ |
|
register_fastcgi_tcp(hostconf, "/php", "127.0.0.1", 9000, doc_root); |
|
register_fastcgi_socket(hostconf, "/socket", "/var/run/php/php8.2-fpm.sock"); |
|
register_fastcgi_spawn(hostconf, "/spawn", "/bin/cat"); |
|
|
|
/* 2. 最後に汎用ハンドラーを登録(フォールバック) */ |
|
register_handler(hostconf, "/api", hello_handler); |
|
register_handler(hostconf, "/", hello_handler); |
|
|
|
/* コンテキスト初期化 */ |
|
#if H2O_USE_LIBUV |
|
uv_loop_t loop; |
|
uv_loop_init(&loop); |
|
h2o_context_init(&ctx, &loop, &config); |
|
#else |
|
h2o_context_init(&ctx, h2o_evloop_create(), &config); |
|
#endif |
|
|
|
/* SSL設定 */ |
|
if (setup_ssl(cert_file, key_file) != 0) { |
|
fprintf(stderr, "SSL setup failed\n"); |
|
goto Error; |
|
} |
|
|
|
/* accept コンテキスト設定 */ |
|
accept_ctx.ctx = &ctx; |
|
accept_ctx.hosts = config.hosts; |
|
|
|
/* リスナー作成 */ |
|
if (create_listener() != 0) { |
|
fprintf(stderr, "Failed to create listener: %s\n", strerror(errno)); |
|
goto Error; |
|
} |
|
|
|
printf("Server supports HTTP/1.1 and HTTP/2 over TLS with FastCGI\n"); |
|
printf("Document root: %s\n", doc_root); |
|
printf("PHP file location: %s/php/test.php\n", doc_root); |
|
printf("Test endpoints:\n"); |
|
printf(" Basic: curl -k https://localhost:8443/api\n"); |
|
printf(" PHP (TCP): curl -k https://localhost:8443/php/test.php\n"); |
|
printf(" PHP (Socket): curl -k https://localhost:8443/socket/test.php\n"); |
|
printf(" Spawn (unavailable): curl -k https://localhost:8443/spawn/\n"); |
|
printf("\n"); |
|
print_usage(argv[0]); |
|
|
|
/* イベントループ実行 */ |
|
#if H2O_USE_LIBUV |
|
uv_run(ctx.loop, UV_RUN_DEFAULT); |
|
#else |
|
while (h2o_evloop_run(ctx.loop, INT32_MAX) == 0) |
|
; |
|
#endif |
|
|
|
return 0; |
|
|
|
Error: |
|
return 1; |
|
} |