Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created July 10, 2025 19:31
Show Gist options
  • Save masakielastic/138e156660211b15b169ad2c9b188b55 to your computer and use it in GitHub Desktop.
Save masakielastic/138e156660211b15b169ad2c9b188b55 to your computer and use it in GitHub Desktop.
select で TLS 対応の HTTP/1 サーバー。ハンドラー方式

select で TLS 対応の HTTP/1 サーバー。ハンドラー方式

ビルドしてサーバーを起動させます。

gcc -o server server.c -lssl -lcrypto
./server

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

curl -k -v https://localhost:8443
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#define MAX_CLIENTS 10
#define BUFFER_SIZE 4096
#define MAX_HEADERS 32
#define MAX_HEADER_SIZE 256
#define MAX_PATH_SIZE 512
// HTTPヘッダー構造体
typedef struct {
char name[MAX_HEADER_SIZE];
char value[MAX_HEADER_SIZE];
} http_header_t;
// HTTPリクエスト構造体
typedef struct {
char method[16];
char path[MAX_PATH_SIZE];
char version[16];
http_header_t headers[MAX_HEADERS];
int header_count;
char *body;
size_t body_length;
SSL *ssl; // 内部使用
int fd; // 内部使用
} http_request_t;
// HTTPレスポンス構造体
typedef struct {
int status_code;
char status_text[64];
http_header_t headers[MAX_HEADERS];
int header_count;
char *body;
size_t body_length;
size_t body_capacity;
} http_response_t;
// リクエストハンドラー関数型
typedef void (*request_handler_t)(http_request_t *request, http_response_t *response);
// HTTPSサーバー構造体
typedef struct {
int port;
request_handler_t handler;
SSL_CTX *ssl_ctx;
int server_fd;
int running;
} https_server_t;
// クライアント接続情報
typedef struct {
int fd;
SSL *ssl;
int tls_established;
char buffer[BUFFER_SIZE];
size_t buffer_pos;
int headers_complete;
} client_info_t;
// グローバル変数
static client_info_t clients[MAX_CLIENTS];
// ===============================
// HTTPレスポンス関数
// ===============================
http_response_t* http_response_init() {
http_response_t *response = calloc(1, sizeof(http_response_t));
if (response) {
response->status_code = 200;
strcpy(response->status_text, "OK");
response->body_capacity = 1024;
response->body = malloc(response->body_capacity);
if (!response->body) {
free(response);
return NULL;
}
response->body[0] = '\0';
response->body_length = 0;
}
return response;
}
void http_response_free(http_response_t *response) {
if (response) {
free(response->body);
free(response);
}
}
void http_response_status(http_response_t *response, int status) {
response->status_code = status;
switch (status) {
case 200: strcpy(response->status_text, "OK"); break;
case 404: strcpy(response->status_text, "Not Found"); break;
case 500: strcpy(response->status_text, "Internal Server Error"); break;
default: strcpy(response->status_text, "Unknown"); break;
}
}
void http_response_header(http_response_t *response, const char *name, const char *value) {
if (response->header_count < MAX_HEADERS) {
strncpy(response->headers[response->header_count].name, name, MAX_HEADER_SIZE - 1);
strncpy(response->headers[response->header_count].value, value, MAX_HEADER_SIZE - 1);
response->header_count++;
}
}
void http_response_body(http_response_t *response, const char *body, size_t length) {
if (length + 1 > response->body_capacity) {
response->body_capacity = length + 1;
response->body = realloc(response->body, response->body_capacity);
if (!response->body) return;
}
memcpy(response->body, body, length);
response->body[length] = '\0';
response->body_length = length;
}
// ===============================
// HTTPパーシング関数
// ===============================
int parse_request_line(const char *line, http_request_t *request) {
return sscanf(line, "%15s %511s %15s", request->method, request->path, request->version) == 3;
}
int parse_header_line(const char *line, http_header_t *header) {
char *colon = strchr(line, ':');
if (!colon) return 0;
size_t name_len = colon - line;
if (name_len >= MAX_HEADER_SIZE) return 0;
strncpy(header->name, line, name_len);
header->name[name_len] = '\0';
// Skip colon and whitespace
char *value_start = colon + 1;
while (*value_start == ' ' || *value_start == '\t') value_start++;
strncpy(header->value, value_start, MAX_HEADER_SIZE - 1);
header->value[MAX_HEADER_SIZE - 1] = '\0';
// Remove trailing whitespace
char *end = header->value + strlen(header->value) - 1;
while (end > header->value && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
*end-- = '\0';
}
return 1;
}
http_request_t* parse_http_request(const char *raw_request, SSL *ssl, int fd) {
http_request_t *request = calloc(1, sizeof(http_request_t));
if (!request) return NULL;
request->ssl = ssl;
request->fd = fd;
char *lines = strdup(raw_request);
char *line = strtok(lines, "\r\n");
// Parse request line
if (!line || !parse_request_line(line, request)) {
free(lines);
free(request);
return NULL;
}
// Parse headers
while ((line = strtok(NULL, "\r\n")) && strlen(line) > 0) {
if (request->header_count < MAX_HEADERS) {
if (parse_header_line(line, &request->headers[request->header_count])) {
request->header_count++;
}
}
}
free(lines);
return request;
}
void http_request_free(http_request_t *request) {
if (request) {
free(request->body);
free(request);
}
}
// ===============================
// SSL/TLS関数
// ===============================
int init_openssl() {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
return 1;
}
void cleanup_openssl() {
EVP_cleanup();
ERR_free_strings();
}
int generate_certificate() {
EVP_PKEY *pkey = NULL;
X509 *x509 = NULL;
FILE *fp = NULL;
int ret = 0;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) goto cleanup;
if (EVP_PKEY_keygen_init(ctx) <= 0) goto cleanup;
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, 2048) <= 0) goto cleanup;
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) goto cleanup;
x509 = X509_new();
if (!x509) goto cleanup;
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);
X509_set_pubkey(x509, pkey);
X509_NAME *name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*)"JP", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*)"Test", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*)"localhost", -1, -1, 0);
X509_set_issuer_name(x509, name);
if (!X509_sign(x509, pkey, EVP_sha256())) goto cleanup;
fp = fopen("server.key", "wb");
if (!fp) goto cleanup;
PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL);
fclose(fp);
fp = NULL;
fp = fopen("server.crt", "wb");
if (!fp) goto cleanup;
PEM_write_X509(fp, x509);
fclose(fp);
fp = NULL;
printf("Generated self-signed certificate: server.crt and server.key\n");
ret = 1;
cleanup:
if (fp) fclose(fp);
if (ctx) EVP_PKEY_CTX_free(ctx);
if (pkey) EVP_PKEY_free(pkey);
if (x509) X509_free(x509);
return ret;
}
SSL_CTX* create_ssl_context() {
const SSL_METHOD *method = TLS_server_method();
SSL_CTX *ctx = SSL_CTX_new(method);
if (!ctx) {
ERR_print_errors_fp(stderr);
return NULL;
}
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
return ctx;
}
int configure_ssl_context(SSL_CTX *ctx) {
if (access("server.crt", F_OK) != 0 || access("server.key", F_OK) != 0) {
if (!generate_certificate()) {
fprintf(stderr, "Failed to generate certificate\n");
return 0;
}
}
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
if (!SSL_CTX_check_private_key(ctx)) {
fprintf(stderr, "Private key does not match certificate\n");
return 0;
}
return 1;
}
// ===============================
// サーバー関数
// ===============================
int set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
void init_clients() {
for (int i = 0; i < MAX_CLIENTS; i++) {
clients[i].fd = -1;
clients[i].ssl = NULL;
clients[i].tls_established = 0;
clients[i].buffer_pos = 0;
clients[i].headers_complete = 0;
}
}
int find_free_client_slot() {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd == -1) return i;
}
return -1;
}
int find_client_by_fd(int fd) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd == fd) return i;
}
return -1;
}
void remove_client(int slot) {
if (slot < 0 || slot >= MAX_CLIENTS) return;
if (clients[slot].ssl) {
SSL_shutdown(clients[slot].ssl);
SSL_free(clients[slot].ssl);
clients[slot].ssl = NULL;
}
if (clients[slot].fd != -1) {
close(clients[slot].fd);
clients[slot].fd = -1;
}
clients[slot].tls_established = 0;
clients[slot].buffer_pos = 0;
clients[slot].headers_complete = 0;
}
int handle_tls_handshake(int slot) {
int result = SSL_accept(clients[slot].ssl);
if (result == 1) {
clients[slot].tls_established = 1;
printf("TLS handshake completed for client %d\n", clients[slot].fd);
return 1;
} else {
int error = SSL_get_error(clients[slot].ssl, result);
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
return 0;
} else {
printf("TLS handshake failed for client %d: %d\n", clients[slot].fd, error);
ERR_print_errors_fp(stderr);
return -1;
}
}
}
void send_response(http_request_t *request, http_response_t *response) {
char response_buffer[BUFFER_SIZE * 2];
int len = 0;
// Status line
len += snprintf(response_buffer + len, sizeof(response_buffer) - len,
"HTTP/1.1 %d %s\r\n", response->status_code, response->status_text);
// Headers
for (int i = 0; i < response->header_count; i++) {
len += snprintf(response_buffer + len, sizeof(response_buffer) - len,
"%s: %s\r\n", response->headers[i].name, response->headers[i].value);
}
// Content-Length header
len += snprintf(response_buffer + len, sizeof(response_buffer) - len,
"Content-Length: %zu\r\n", response->body_length);
// Connection close header
len += snprintf(response_buffer + len, sizeof(response_buffer) - len,
"Connection: close\r\n");
// End of headers
len += snprintf(response_buffer + len, sizeof(response_buffer) - len, "\r\n");
// Send headers
SSL_write(request->ssl, response_buffer, len);
// Send body
if (response->body_length > 0) {
SSL_write(request->ssl, response->body, response->body_length);
}
}
void handle_client_data(int slot, request_handler_t handler) {
char temp_buffer[1024];
int bytes_read = SSL_read(clients[slot].ssl, temp_buffer, sizeof(temp_buffer) - 1);
if (bytes_read <= 0) {
int error = SSL_get_error(clients[slot].ssl, bytes_read);
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
printf("SSL_read error for client %d: %d\n", clients[slot].fd, error);
}
return;
}
// Add to buffer
if (clients[slot].buffer_pos + bytes_read < BUFFER_SIZE - 1) {
memcpy(clients[slot].buffer + clients[slot].buffer_pos, temp_buffer, bytes_read);
clients[slot].buffer_pos += bytes_read;
clients[slot].buffer[clients[slot].buffer_pos] = '\0';
}
// Check if headers are complete
if (!clients[slot].headers_complete) {
if (strstr(clients[slot].buffer, "\r\n\r\n")) {
clients[slot].headers_complete = 1;
// Parse and handle request
http_request_t *request = parse_http_request(clients[slot].buffer,
clients[slot].ssl,
clients[slot].fd);
if (request) {
http_response_t *response = http_response_init();
if (response) {
handler(request, response);
send_response(request, response);
http_response_free(response);
}
http_request_free(request);
}
}
}
}
// ===============================
// パブリックAPI
// ===============================
https_server_t* https_server_init(int port, request_handler_t handler) {
https_server_t *server = calloc(1, sizeof(https_server_t));
if (!server) return NULL;
server->port = port;
server->handler = handler;
server->running = 0;
// OpenSSL初期化
if (!init_openssl()) {
free(server);
return NULL;
}
// SSL context作成
server->ssl_ctx = create_ssl_context();
if (!server->ssl_ctx) {
free(server);
return NULL;
}
// SSL context設定
if (!configure_ssl_context(server->ssl_ctx)) {
SSL_CTX_free(server->ssl_ctx);
free(server);
return NULL;
}
return server;
}
void https_server_free(https_server_t *server) {
if (server) {
if (server->ssl_ctx) {
SSL_CTX_free(server->ssl_ctx);
}
cleanup_openssl();
free(server);
}
}
int https_server_listen(https_server_t *server) {
struct sockaddr_in server_addr, client_addr;
socklen_t client_len = sizeof(client_addr);
fd_set read_fds, master_fds;
int max_fd;
int opt = 1;
// ソケット作成
server->server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server->server_fd == -1) {
perror("socket");
return -1;
}
if (setsockopt(server->server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
return -1;
}
// サーバーアドレス設定
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(server->port);
if (bind(server->server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
return -1;
}
if (listen(server->server_fd, MAX_CLIENTS) < 0) {
perror("listen");
return -1;
}
if (set_nonblocking(server->server_fd) < 0) {
perror("set_nonblocking");
return -1;
}
init_clients();
printf("HTTPS Server started on port %d\n", server->port);
printf("Access: https://localhost:%d\n", server->port);
FD_ZERO(&master_fds);
FD_SET(server->server_fd, &master_fds);
max_fd = server->server_fd;
server->running = 1;
while (server->running) {
read_fds = master_fds;
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (activity < 0) {
perror("select");
break;
}
for (int fd = 0; fd <= max_fd; fd++) {
if (FD_ISSET(fd, &read_fds)) {
if (fd == server->server_fd) {
// 新しい接続
int client_fd = accept(server->server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd >= 0) {
int slot = find_free_client_slot();
if (slot == -1) {
printf("Maximum clients reached\n");
close(client_fd);
continue;
}
printf("New connection from %s:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));
if (set_nonblocking(client_fd) < 0) {
close(client_fd);
continue;
}
SSL *ssl = SSL_new(server->ssl_ctx);
if (!ssl) {
close(client_fd);
continue;
}
SSL_set_fd(ssl, client_fd);
clients[slot].fd = client_fd;
clients[slot].ssl = ssl;
clients[slot].tls_established = 0;
clients[slot].buffer_pos = 0;
clients[slot].headers_complete = 0;
FD_SET(client_fd, &master_fds);
if (client_fd > max_fd) max_fd = client_fd;
}
} else {
// 既存クライアント
int slot = find_client_by_fd(fd);
if (slot == -1) {
FD_CLR(fd, &master_fds);
close(fd);
continue;
}
if (!clients[slot].tls_established) {
int result = handle_tls_handshake(slot);
if (result < 0) {
FD_CLR(fd, &master_fds);
remove_client(slot);
}
} else {
handle_client_data(slot, server->handler);
// Connection: close なので接続を閉じる
if (clients[slot].headers_complete) {
FD_CLR(fd, &master_fds);
remove_client(slot);
printf("Client %d disconnected\n", fd);
}
}
if (fd == max_fd) {
while (max_fd > server->server_fd && !FD_ISSET(max_fd, &master_fds)) {
max_fd--;
}
}
}
}
}
}
close(server->server_fd);
for (int i = 0; i < MAX_CLIENTS; i++) {
remove_client(i);
}
return 0;
}
void https_server_stop(https_server_t *server) {
if (server) {
server->running = 0;
}
}
// ===============================
// サンプルハンドラー
// ===============================
void handle_request(http_request_t *request, http_response_t *response) {
printf("Request: %s %s %s\n", request->method, request->path, request->version);
// ヘッダー表示
for (int i = 0; i < request->header_count; i++) {
printf("Header: %s: %s\n", request->headers[i].name, request->headers[i].value);
}
if (strcmp(request->path, "/") == 0) {
http_response_status(response, 200);
http_response_header(response, "Content-Type", "text/html");
const char *html = "<html><body><h1>Hello from HTTPS Server!</h1><p>Handler-based implementation</p></body></html>";
http_response_body(response, html, strlen(html));
} else if (strcmp(request->path, "/api/test") == 0) {
http_response_status(response, 200);
http_response_header(response, "Content-Type", "application/json");
const char *json = "{\"message\":\"Hello, World!\",\"status\":\"success\"}";
http_response_body(response, json, strlen(json));
} else {
http_response_status(response, 404);
http_response_header(response, "Content-Type", "text/html");
const char *not_found = "<html><body><h1>404 Not Found</h1></body></html>";
http_response_body(response, not_found, strlen(not_found));
}
}
// ===============================
// メイン関数
// ===============================
int main() {
https_server_t *server = https_server_init(8443, handle_request);
if (!server) {
fprintf(stderr, "Failed to initialize server\n");
return EXIT_FAILURE;
}
int result = https_server_listen(server);
https_server_free(server);
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment