Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active June 18, 2025 07:51
Show Gist options
  • Select an option

  • Save masakielastic/ac8d99eb1362b45a20f60b822fe2b250 to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/ac8d99eb1362b45a20f60b822fe2b250 to your computer and use it in GitHub Desktop.
libh2o で FastCGI + HTTP/2 サーバー

libh2o で FastCGI + HTTP/2 サーバー

環境

テスト環境は Debian 12 Bookworm です。次のパッケージをインストールします。

sudo apt install libh2o-dev libuv1-dev

ビルド

gcc -o server server.c -lh2o -lssl -lcrypto -luv -lpthread

起動

./server

テスト

php-fpm と通信します。次のような PHP スクリプトを用意します。

cat public/php/test.php
<?php

echo "Hello from public/php dir\n";

curl で HTTP リクエストを送信します。

curl -k https://localhost:8443/php/test.php
Hello from public/php dir

php-fpm が稼働しているかどうかチェックするには cgi-fcgi を使います。

export SCRIPT_FILENAME=$PWD/public/php/test.php
cgi-fcgi -bind -connect localhost:9000
Content-type: text/html; charset=UTF-8

Hello from public/php dir
/*
* 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment