Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Last active March 21, 2026 20:40
Show Gist options
  • Select an option

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

Select an option

Save masakielastic/c91dba6845d09ae04727ae5008037c79 to your computer and use it in GitHub Desktop.
embed PHP と Stream API で TLS HTTP/1 サーバー。最新版は https://github.com/masakielastic/php-embed-stream-http
#include <php.h>
#include <Zend/zend_smart_str.h>
#include <sapi/embed/php_embed.h>
#define SERVER_ADDR "tls://127.0.0.1:8443"
#define SERVER_CERT_PEM "localhost.pem"
#define SERVER_KEY_PEM "localhost-key.pem"
static int run_min_server(void);
static php_stream_context *create_tls_server_context(void);
static void set_context_string_option(php_stream_context *context, const char *wrapper, const char *option, const char *value);
static void set_context_bool_option(php_stream_context *context, const char *wrapper, const char *option, bool value);
int main(int argc, char **argv)
{
PHP_EMBED_START_BLOCK(argc, argv)
(void) run_min_server();
PHP_EMBED_END_BLOCK()
return 0;
}
static void set_context_string_option(php_stream_context *context, const char *wrapper, const char *option, const char *value)
{
zval zv;
ZVAL_STRING(&zv, value);
php_stream_context_set_option(context, (char *) wrapper, (char *) option, &zv);
zval_ptr_dtor(&zv);
}
static void set_context_bool_option(php_stream_context *context, const char *wrapper, const char *option, bool value)
{
zval zv;
ZVAL_BOOL(&zv, value ? 1 : 0);
php_stream_context_set_option(context, (char *) wrapper, (char *) option, &zv);
}
static php_stream_context *create_tls_server_context(void)
{
php_stream_context *context;
context = php_stream_context_alloc();
if (!context) {
return NULL;
}
/*
* local_cert may contain the certificate and private key in one PEM file.
* local_pk is kept here to make split-file experiments easier.
*/
set_context_string_option(context, "ssl", "local_cert", SERVER_CERT_PEM);
set_context_string_option(context, "ssl", "local_pk", SERVER_KEY_PEM);
/*
* These are mainly useful for local experiments with self-signed certs.
* Tighten them for real deployments.
*/
set_context_bool_option(context, "ssl", "allow_self_signed", true);
set_context_bool_option(context, "ssl", "verify_peer", false);
set_context_bool_option(context, "ssl", "verify_peer_name", false);
return context;
}
static int run_min_server(void)
{
php_stream *server = NULL;
php_stream *client = NULL;
php_stream_context *context = NULL;
zend_string *err_text = NULL;
zend_string *peer = NULL;
int errcode = 0;
context = create_tls_server_context();
if (!context) {
php_printf("TLS context create failed\n");
return FAILURE;
}
server = php_stream_xport_create(
SERVER_ADDR,
sizeof(SERVER_ADDR) - 1,
REPORT_ERRORS,
STREAM_XPORT_SERVER | STREAM_XPORT_BIND | STREAM_XPORT_LISTEN,
NULL,
NULL,
context,
&err_text,
&errcode
);
if (!server) {
php_printf("server create failed: %s (%d)\n",
err_text ? ZSTR_VAL(err_text) : "unknown", errcode);
if (err_text) {
zend_string_release(err_text);
}
return FAILURE;
}
php_printf("TLS server listening on %s\n", SERVER_ADDR);
php_printf("certificate: %s\n", SERVER_CERT_PEM);
php_printf("private key: %s\n", SERVER_KEY_PEM);
for (;;) {
zend_string *accept_error = NULL;
if (php_stream_xport_accept(server, &client, &peer, NULL, NULL, NULL, &accept_error) < 0) {
php_printf("accept failed: %s\n",
accept_error ? ZSTR_VAL(accept_error) : "unknown");
if (accept_error) {
zend_string_release(accept_error);
}
break;
}
if (client) {
char buf[4096];
ssize_t n;
n = php_stream_read(client, buf, sizeof(buf) - 1);
if (n > 0) {
smart_str request_log = {0};
smart_str response = {0};
smart_str body = {0};
buf[n] = '\0';
smart_str_appends(&body, "hello over TLS\n");
smart_str_0(&body);
smart_str_appends(&request_log, "request from ");
if (peer) {
smart_str_append(&request_log, peer);
} else {
smart_str_appends(&request_log, "<unknown>");
}
smart_str_appends(&request_log, ":\n");
smart_str_appendl(&request_log, buf, (size_t) n);
smart_str_appends(&request_log, "\n");
smart_str_0(&request_log);
php_printf("%s", ZSTR_VAL(request_log.s));
smart_str_appends(&response, "HTTP/1.1 200 OK\r\n");
smart_str_appends(&response, "Content-Type: text/plain\r\n");
smart_str_append_printf(&response, "Content-Length: %zu\r\n", ZSTR_LEN(body.s));
smart_str_appends(&response, "Connection: close\r\n");
smart_str_appends(&response, "\r\n");
smart_str_append(&response, body.s);
smart_str_0(&response);
php_stream_write(client, ZSTR_VAL(response.s), ZSTR_LEN(response.s));
smart_str_free(&request_log);
smart_str_free(&response);
}
php_stream_close(client);
client = NULL;
}
if (peer) {
zend_string_release(peer);
peer = NULL;
}
}
php_stream_close(server);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment