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