Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created June 17, 2025 17:43
Show Gist options
  • Select an option

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

Select an option

Save masakielastic/fb4daf0b28eeb06809dd39aac0b20aa6 to your computer and use it in GitHub Desktop.
libuv で HTTP/1 + TLS サーバー

libuv で HTTP/1 + TLS サーバー

ビルドします。

gcc -o https_server https_server.c -luv -lssl -lcrypto

起動します。

.https_server

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

curl -k -v https://localhost:8443
*   Trying 127.0.0.1:8443...
* Connected to localhost (127.0.0.1) port 8443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: O=mkcert development certificate; OU=masakielastic@penguin
*  start date: Jun 17 17:34:43 2025 GMT
*  expire date: Sep 17 17:34:43 2027 GMT
*  issuer: O=mkcert development CA; OU=masakielastic@penguin; CN=mkcert masakielastic@penguin
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/7.88.1
> Accept: */*
> 
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 13
< Connection: close
< 
* Closing connection 0
* TLSv1.3 (OUT), TLS alert, close notify (256):
Hello, World!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define DEFAULT_PORT 8443
#define DEFAULT_BACKLOG 128
#define CERT_FILE "localhost.pem"
#define KEY_FILE "localhost-key.pem"
// HTTP レスポンス
static const char http_response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 13\r\n"
"Connection: close\r\n"
"\r\n"
"Hello, World!";
static SSL_CTX *ssl_ctx;
typedef struct {
uv_tcp_t tcp;
SSL *ssl;
BIO *read_bio;
BIO *write_bio;
int handshake_completed;
} client_t;
typedef struct {
uv_write_t req;
uv_buf_t buf;
} write_req_t;
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) {
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
void on_close(uv_handle_t* handle) {
client_t *client = (client_t*)handle;
if (client->ssl) {
SSL_free(client->ssl);
}
free(client);
}
void on_write(uv_write_t *req, int status) {
write_req_t *wr = (write_req_t*)req;
free(wr->buf.base);
free(wr);
if (status) {
fprintf(stderr, "Write error %s\n", uv_strerror(status));
uv_close((uv_handle_t*)req->handle, on_close);
}
}
int flush_ssl_write_bio(client_t *client) {
char buffer[4096];
int bytes = BIO_read(client->write_bio, buffer, sizeof(buffer));
if (bytes > 0) {
write_req_t *req = (write_req_t*)malloc(sizeof(write_req_t));
req->buf = uv_buf_init((char*)malloc(bytes), bytes);
memcpy(req->buf.base, buffer, bytes);
int r = uv_write((uv_write_t*)req, (uv_stream_t*)&client->tcp, &req->buf, 1, on_write);
if (r) {
free(req->buf.base);
free(req);
return -1;
}
return bytes;
}
return 0;
}
void do_ssl_handshake(client_t *client) {
int ret = SSL_accept(client->ssl);
// SSL_accept後に必ずwrite_bioをフラッシュ
flush_ssl_write_bio(client);
if (ret == 1) {
client->handshake_completed = 1;
printf("SSL handshake completed\n");
} else {
int err = SSL_get_error(client->ssl, ret);
if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE) {
fprintf(stderr, "SSL handshake failed: %d\n", err);
ERR_print_errors_fp(stderr);
uv_close((uv_handle_t*)&client->tcp, on_close);
}
}
}
void process_http_request(client_t *client, const char *data, size_t len) {
if (len >= 3 && strncmp(data, "GET", 3) == 0) {
printf("Processing GET request\n");
int ret = SSL_write(client->ssl, http_response, sizeof(http_response) - 1);
if (ret > 0) {
flush_ssl_write_bio(client);
// レスポンス送信後に接続を閉じる
uv_close((uv_handle_t*)&client->tcp, on_close);
} else {
int err = SSL_get_error(client->ssl, ret);
fprintf(stderr, "SSL_write failed: %d\n", err);
uv_close((uv_handle_t*)&client->tcp, on_close);
}
} else {
printf("Non-GET request, closing connection\n");
uv_close((uv_handle_t*)&client->tcp, on_close);
}
}
void on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
client_t *client = (client_t*)stream;
if (nread < 0) {
if (nread != UV_EOF) {
fprintf(stderr, "Read error %s\n", uv_err_name(nread));
}
uv_close((uv_handle_t*)client, on_close);
goto cleanup;
}
if (nread > 0) {
// 受信データをSSLのread_bioに書き込み
BIO_write(client->read_bio, buf->base, nread);
if (!client->handshake_completed) {
do_ssl_handshake(client);
} else {
// ハンドシェイク完了後、アプリケーションデータを読み取り
char ssl_buffer[4096];
int bytes = SSL_read(client->ssl, ssl_buffer, sizeof(ssl_buffer));
if (bytes > 0) {
process_http_request(client, ssl_buffer, bytes);
} else {
int err = SSL_get_error(client->ssl, bytes);
if (err != SSL_ERROR_WANT_READ) {
fprintf(stderr, "SSL_read failed: %d\n", err);
uv_close((uv_handle_t*)&client->tcp, on_close);
}
}
// SSL_read後もwrite_bioをフラッシュ(Alert等のため)
flush_ssl_write_bio(client);
}
}
cleanup:
if (buf->base) {
free(buf->base);
}
}
void on_new_connection(uv_stream_t *server, int status) {
if (status < 0) {
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
return;
}
client_t *client = (client_t*)malloc(sizeof(client_t));
memset(client, 0, sizeof(client_t));
uv_tcp_init(uv_default_loop(), &client->tcp);
if (uv_accept(server, (uv_stream_t*)&client->tcp) == 0) {
printf("New connection accepted\n");
// SSL構造体の初期化
client->ssl = SSL_new(ssl_ctx);
client->read_bio = BIO_new(BIO_s_mem());
client->write_bio = BIO_new(BIO_s_mem());
client->handshake_completed = 0;
SSL_set_bio(client->ssl, client->read_bio, client->write_bio);
SSL_set_accept_state(client->ssl);
uv_read_start((uv_stream_t*)&client->tcp, alloc_buffer, on_read);
} else {
free(client);
}
}
int init_ssl() {
// OpenSSL 3.0対応の初期化
ssl_ctx = SSL_CTX_new(TLS_server_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
return -1;
}
// 証明書と秘密鍵を読み込み
if (SSL_CTX_use_certificate_file(ssl_ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
fprintf(stderr, "Failed to load certificate file: %s\n", CERT_FILE);
ERR_print_errors_fp(stderr);
return -1;
}
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, KEY_FILE, SSL_FILETYPE_PEM) <= 0) {
fprintf(stderr, "Failed to load private key file: %s\n", KEY_FILE);
ERR_print_errors_fp(stderr);
return -1;
}
// 秘密鍵と証明書の整合性をチェック
if (!SSL_CTX_check_private_key(ssl_ctx)) {
fprintf(stderr, "Private key does not match certificate\n");
return -1;
}
printf("SSL context initialized successfully\n");
return 0;
}
int main() {
if (init_ssl() < 0) {
return 1;
}
uv_loop_t *loop = uv_default_loop();
uv_tcp_t server;
uv_tcp_init(loop, &server);
struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
int r = uv_listen((uv_stream_t*)&server, DEFAULT_BACKLOG, on_new_connection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
return 1;
}
printf("HTTPS server listening on port %d\n", DEFAULT_PORT);
printf("Certificate file: %s\n", CERT_FILE);
printf("Private key file: %s\n", KEY_FILE);
int result = uv_run(loop, UV_RUN_DEFAULT);
SSL_CTX_free(ssl_ctx);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment