C言語で簡易HTTPサーバーを作りHTML公開（備忘録）

## 実証
このCプログラムは、TCPソケットを利用してポート8080でHTTPリクエストを受け取り、public/ ディレクトリ内の静的ファイルをHTTPレスポンスとして返す簡易Webサーバーです。
socket → bind → listen → accept → read（リクエスト）→ open + read（ファイル）→ write（レスポンス）という一連の流れに従って、Webアクセスを処理しています。

### ファイル構造
```
c_http_server/
├── server.c # サーバー実装
├── server
└── public/
   └── index.html # 公開するHTML
```

### 起動
```
ubuntu@ubuntu:~/kaihatsu/server$ gcc server.c -o server

ubuntu@ubuntu:~/kaihatsu/server$ ./server
サーバーが起動しました: http://localhost:8080
LAN内の他の端末からはIPアドレスを指定してアクセスしてください
```

### サーバー側のIPアドレスを調べる
```
ubuntu@ubuntu:~$ hostname -I
192.168.68.96 
ubuntu@ubuntu:~$ 
```
### 端末(ターミナル)から接続
```
ubuntu@ubuntu:~$ curl http://192.168.68.96:8080
<!DOCTYPE html>
<html>
<head>
    <title>C HTTP Server</title>
    <link rel="stylesheet" href="/style.css">
</head>
<body>
    <h1>C Language Server</h1>
    <script src="/app.js"></script>
</body>
</html>
ubuntu@ubuntu:~$ 
```
### 携帯から接続
ブラウザを開き
http://192.168.68.96:8080
と入力する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4054974/5f7db4ec-e573-41fb-aa52-3ad52316c2f3.png)

## Cコード
```server.c
/*----------------------------------------------------------
HTTPサーバーの実装
C言語で記述された単一スレッドのWebサーバー
8080ポートで待機し、publicディレクトリ内の静的ファイルを配信
------------------------------------------------------------*/
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>  // 文字列操作
#include <unistd.h>  // システムコール（read/writeなど）
#include <arpa/inet.h>  // ネットワーク関連操作
#include <sys/socket.h> // ソケット
#include <sys/stat.h>   // ファイル状態情報
#include <fcntl.h>      // ファイル制御

// 設定定数
#define PORT 8080          // 待機ポート番号
#define BUFFER_SIZE 2048   // バッファサイズ
#define ROOT_DIR "public/" // 静的ファイルのルートディレクトリ

/*----------------------------------------------------------
コンテンツタイプを取得する関数
引数 path: リクエストされたファイルパス
戻り値: MIMEタイプ文字列
------------------------------------------------------------*/
const char* get_content_type(const char *path) {
    const char *dot = strrchr(path, '.'); // 拡張子の検索
    if (dot) {
        if (strcmp(dot, ".css") == 0) return "text/css";
        if (strcmp(dot, ".js") == 0) return "application/javascript";
    }
    return "text/html"; // デフォルトはHTML
}

/*----------------------------------------------------------
HTTPレスポンスを送信する関数
引数 client_fd: クライアントのソケットディスクリプタ
     status: HTTPステータスコード
     content_type: Content-Typeヘッダの値
     body: レスポンスボディ
     content_length: コンテンツの長さ
------------------------------------------------------------*/
void send_response(int client_fd, int status, const char *content_type, 
                   const char *body, int content_length) {
    char header[BUFFER_SIZE];
    // HTTPヘッダの生成
    snprintf(header, sizeof(header), 
        "HTTP/1.1 %d OK\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %d\r\n\r\n",
        status, content_type, content_length);
    
    // ヘッダとボディの送信
    write(client_fd, header, strlen(header));
    write(client_fd, body, content_length);
}

/*----------------------------------------------------------
クエストを処理する関数
引数 client_fd: クライアントのソケットディスクリプタ
------------------------------------------------------------*/
void handle_request(int client_fd) {
    char buffer[BUFFER_SIZE] = {0};
    read(client_fd, buffer, sizeof(buffer)); // リクエストの読み取り
    
    // リクエストラインの解析（例: GET /index.html HTTP/1.1）
    char method[16], path[256];
    sscanf(buffer, "%s %s", method, path);
    
    // ルートパスの場合はindex.htmlを表示
    if (strcmp(path, "/") == 0) strcpy(path, "/index.html");
    
    // ローカルファイルパスの生成
    char full_path[512];
    snprintf(full_path, sizeof(full_path), "%s%s", ROOT_DIR, path + 1);
    
    // ファイルオープン
    int file_fd = open(full_path, O_RDONLY);
    if (file_fd == -1) { // ファイルが存在しない場合
        send_response(client_fd, 404, "text/plain", "Not Found", 9);
        return;
    }
    
    // ファイル内容の読み込み
    struct stat st;
    fstat(file_fd, &st); // ファイル情報の取得
    char *content = malloc(st.st_size); // メモリ確保
    read(file_fd, content, st.st_size);
    
    // レスポンス送信
    send_response(client_fd, 200, get_content_type(path), content, st.st_size);
    
    // リソースの解放
    free(content);
    close(file_fd);
}

/*----------------------------------------------------------
メイン関数
------------------------------------------------------------*/
int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    
    // ソケットの作成
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("ソケットの作成に失敗しました");
        exit(EXIT_FAILURE);
    }
    
    // ソケットオプションの設定（アドレスの再利用を許可）
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // アドレス構造体の設定
    address.sin_family = AF_INET;          // IPv4
    address.sin_addr.s_addr = INADDR_ANY;   // すべてのインターフェース
    address.sin_port = htons(PORT);         // ポート番号
    
    // ソケットのバインド
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("バインドに失敗しました");
        exit(EXIT_FAILURE);
    }
    
    // 接続待機開始 待機接続数を3に設定
    if (listen(server_fd, 3) < 0) {
        perror("リッスンに失敗しました");
        exit(EXIT_FAILURE);
    }
    
    printf("サーバーが起動しました: http://localhost:%d\n", PORT);
    printf("LAN内の他の端末からはIPアドレスを指定してアクセスしてください\n");
    
    // メインループ（接続を受け付けて処理）
    while (1) {
        socklen_t addrlen = sizeof(address);
        // 接続を受け付ける
        if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0) {
            perror("接続受け付けに失敗しました");
            continue;
        }
        handle_request(client_fd); // リクエスト処理
        close(client_fd);          // クライアント接続を閉じる
    }
    
    return 0;
}
```