/**
 * @file simp_httpd.c
 * @author maxwell@void
 * @version 0.1
 * @date 2024-07-11
 * 
 * @copyright Copyright (c) 2024
 * 功能：
 * 1. 实现一个简单的HTTP服务器，可以处理GET请求，并返回指定文件的内容。
 * 2. 支持命令行参数，可以指定端口号和目录。
 * 3. 支持线程池，可以处理多个客户端连接。
 * 4. 支持SIGPIPE信号，可以忽略客户端断开连接时产生的错误。
 * 
 * 编译：gcc -o sim_httpd simp_httpd.c -lpthread
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>

#define BUFFER_SIZE 1024

void handle_client(int client_socket);
void send_file(int client_socket, const char *filename);

// 客户端处理线程
void *client_thread(void *arg) {
    int client_socket = *(int *)arg;
    free(arg); // 释放分配的内存
    handle_client(client_socket);
    close(client_socket);
    return NULL;
}

int main(int argc, char *argv[]) {
    int server_socket, *client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_addr_len = sizeof(client_addr);
    int port = 8080; // 默认端口
    char *directory = "."; // 默认目录

    // 解析命令行参数
    int opt;
    while ((opt = getopt(argc, argv, "p:d:")) != -1) {
        switch (opt) {
            case 'p':
                port = atoi(optarg);
                break;
            case 'd':
                directory = optarg;
                break;
            default:
                fprintf(stderr, "Usage: %s [-p port] [-d directory]\n", argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    // 改变当前工作目录
    if (chdir(directory) != 0) {
        perror("chdir failed");
        exit(EXIT_FAILURE);
    }

    // 忽略 SIGPIPE 信号
    signal(SIGPIPE, SIG_IGN);

    // 创建服务器套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);
    

    // 绑定套接字到指定端口
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("Bind failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_socket, 10) == -1) {
        perror("Listen failed");
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d, serving directory %s...\n", port, directory);

    // 接受并处理客户端连接
    while (1) {
        client_socket = malloc(sizeof(int));
        if (client_socket == NULL) {
            perror("Malloc failed");
            continue;
        }
        *client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
        if (*client_socket == -1) {
            perror("Accept failed");
            free(client_socket);
            continue;
        }
        printf("Client connected: %s\n", inet_ntoa(client_addr.sin_addr));

        pthread_t thread_id;
        if (pthread_create(&thread_id, NULL, client_thread, client_socket) != 0) {
            perror("Thread creation failed");
            close(*client_socket);
            free(client_socket);
        } else {
            pthread_detach(thread_id); // 分离线程
        }
    }

    close(server_socket);
    return 0;
}

void handle_client(int client_socket) {
    char buffer[BUFFER_SIZE];
    int received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
    if (received < 0) {
        perror("Receive failed");
        return;
    }

    buffer[received] = '\0';
    printf("Request: %s\n", buffer);

    // 简单解析HTTP GET请求
    char method[16], path[256], protocol[16];
    sscanf(buffer, "%s %s %s", method, path, protocol);

    if (strcmp(method, "GET") == 0) {
        // 去掉路径中的'/'
        if (path[0] == '/') {
            memmove(path, path + 1, strlen(path));
        }
        send_file(client_socket, path);
    } else {
        const char *response = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
        send(client_socket, response, strlen(response), 0);
    }
}

void send_file(int client_socket, const char *filename) {
    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        const char *response = "HTTP/1.1 404 Not Found\r\n\r\n";
        send(client_socket, response, strlen(response), 0);
        return;
    }

    fseek(file, 0, SEEK_END);
    long file_size = ftell(file);
    fseek(file, 0, SEEK_SET);

    char header[BUFFER_SIZE];
    snprintf(header, BUFFER_SIZE, "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\n\r\n", file_size);
    send(client_socket, header, strlen(header), 0);

    char file_buffer[BUFFER_SIZE];
    size_t bytes_read;
    while ((bytes_read = fread(file_buffer, 1, BUFFER_SIZE, file)) > 0) {
        if (send(client_socket, file_buffer, bytes_read, 0) == -1) {
            perror("Send failed");
            break;
        }
    }

    fclose(file);
}
