ビルドしてサーバーを起動させます。
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; | |
} |