#include "common.h"

void process_trans(int fd,int hit);
void read_requesthdrs(rio_t *rp,int hit,int nrh);
int is_static(char *uri);
void parse_static_uri(char *uri, char *filename);
void parse_dynamic_uri(char *uri, char *filename, char *cgiargs);
void feed_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void feed_dynamic(int fd, char *filename, char *cgiargs);
void error_request(int fd, char *cause, char *errnum, 
		 char *shortmsg, char *description);
		 
/* 支持的文件扩展名及其对应的MIME类型 */
struct {
    char *ext;       // 文件扩展名
    char *filetype;  // MIME类型
} extensions[] = {
    {".gif", "image/gif"},
    {".jpg", "image/jpg"},
    {".jpeg", "image/jpeg"},
    {".png", "image/png"},
    {".ico", "image/ico"},
    {".zip", "image/zip"},
    {".gz", "image/gz"},
    {".tar", "image/tar"},
    {".htm", "text/html"},
    {".html", "text/html"},
    {0, 0}  // 结束标志
};

/* 调试模式宏定义 */
#ifdef DEBUG
#define printf2(format, var) printf(format,var)
#define printf3(format,var1,var2) printf(format,var1,var2)
#else
#define printf2(format, var) 
#define printf3(format,var1,var2)
#endif

/* 全局变量说明 */

void sigchld_handler(int sig) 
{
    while (waitpid(-1, 0, WNOHANG) > 0);
    return;
}

typedef void (*sighandler_t) (int sig) ;

void Signal(int sig, sighandler_t handler){
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;  // 确保被信号中断的系统调用自动重启
    sigaction(SIGCHLD, &sa, NULL);
}


/* Ctrl+C信号处理函数 */
void ctrlc_handler(int sig)
{
    printf("您按下了Ctrl+C终止了Web服务器\n");
    exit(0);
}

/* 主函数：监听端口并处理客户端连接 */

int main(int argc, char **argv) 
{
    int listen_sock, conn_sock, port;
    int hit;  		// 请求计数器
    socklen_t clientlen;
    struct sockaddr_in clientaddr;

    /* 检查命令行参数 */
    if (argc != 2) {
        fprintf(stderr, "用法: %s <端口号>\n", argv[0]);
        exit(1);
    }
    port = atoi(argv[1]);

    listen_sock = open_listen_sock(port);

    for (hit=1; ; hit++) {             //hit为第几次请求，或第几次http事务
        clientlen = sizeof(clientaddr);
        conn_sock = accept(listen_sock, (SA *)&clientaddr, &clientlen);

        
        if (fork() == 0) { 
            close(listen_sock);     /* Child process closes its listening socket */
            process_trans(conn_sock,hit);  // 处理HTTP事务
            close(conn_sock);     /* Child process closes connection with client */
            exit(0);                /* Child process exits */
        }

        close(conn_sock);
    }
    exit(0);
}

/* 处理HTTP事务的核心函数 */
void process_trans(int fd,int hit) 
{
    int static_flag;      // 是否为静态资源标志
    struct stat sbuf;     // 文件状态信息
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio;
    int rhn=0;     // 请求头计数器
    
    /* 设置Ctrl+C信号处理 */
    if(signal(SIGINT,ctrlc_handler)==SIG_ERR) 
        (void)printf("错误: 无法设置SIGINT信号处理\n");
    
    printf2("第%d次请求开始\n",hit);
    
    /* 读取请求行和请求头 */
    rio_readinitb(&rio, fd);
    rio_readlineb(&rio, buf, MAXLINE);
    printf3("请求头%d:%s", ++rhn, buf);
    
    sscanf(buf, "%s %s %s", method, uri, version);
    if (strcasecmp(method, "GET")) {  // 仅支持GET方法
        error_request(fd, method, "501", "未实现", 
                     "本服务器不支持该请求方法");
        return;
    }
    read_requesthdrs(&rio,hit,rhn);  // 读取剩余请求头

    static_flag = is_static(uri);  // 判断资源类型
    if(static_flag)
        parse_static_uri(uri, filename);  // 解析静态资源路径
    else
        parse_dynamic_uri(uri, filename, cgiargs);  // 解析动态资源路径和参数

    /* 检查文件是否存在及权限 */
    if (stat(filename, &sbuf) < 0) {
        error_request(fd, filename, "404", "未找到", 
                     "服务器无法找到该文件");
        return;
    }

    /* 根据资源类型发送响应 */
    if (static_flag) {  // 静态资源处理
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
            error_request(fd, filename, "403", "禁止访问", 
                         "服务器无权读取该文件");
            return;
        }
        feed_static(fd, filename, sbuf.st_size);  // 发送静态文件内容
    }
    else {  // 动态资源处理
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
            error_request(fd, filename, "403", "禁止访问", 
                         "服务器无法执行该CGI程序");
            return;
        }
        feed_dynamic(fd, filename, cgiargs);  // 执行CGI程序并发送输出
    }
}

/* 判断URI是否对应静态资源 */
int is_static(char *uri)
{
    return strstr(uri, "cgi-bin") == NULL;  // 不含cgi-bin则为静态资源
}

/* 构造HTTP错误响应 */
void error_request(int fd, char *cause, char *errnum, 
                 char *shortmsg, char *description) 
{
    char buf[MAXLINE], body[MAXBUF];

    /* 构建错误页面内容 */
    sprintf(body, "<html><title>错误请求</title>");
    sprintf(body, "%s<body bgcolor=\"#ffffff\">\r\n", body);
    sprintf(body, "%s<h1>%s %s</h1>\r\n", body, errnum, shortmsg);
    sprintf(body, "%s<p>%s: %s</p>\r\n", body, description, cause);
    sprintf(body, "%s<hr><em>weblet Web服务器</em>\r\n", body);

    /* 发送HTTP响应头 */
    sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-type: text/html\r\n");
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
    rio_writen(fd, buf, strlen(buf));
    
    /* 发送错误页面内容 */
    rio_writen(fd, body, strlen(body));
}

/* 读取请求头直到空行 */
void read_requesthdrs(rio_t *rp,int hit, int rhn) 
{
    char buf[MAXLINE];
    rio_readlineb(rp, buf, MAXLINE);
    while(strcmp(buf, "\r\n")) {
        printf3("请求头%d:%s", ++rhn, buf);
        rio_readlineb(rp, buf, MAXLINE);
    }
    printf2("第%d次请求结束\n\n",hit); 
}

/* 解析静态资源URI到文件路径 */
void parse_static_uri(char *uri, char *filename) 
{
    strcpy(filename, ".");
    strcat(filename, uri);
    if (uri[strlen(uri)-1] == '/')
        strcat(filename, "index.html");  // 默认返回index.html
}

/* 解析动态资源URI到文件路径和参数 */
void parse_dynamic_uri(char *uri, char *filename, char *cgiargs)
{
    char *ptr = index(uri, '?');
    if (ptr) {
        strcpy(cgiargs, ptr+1);  // 提取参数部分请求头
        *ptr = '\0';  // 截断URI
    } else {
        strcpy(cgiargs, "");
    }
    strcpy(filename, ".");
    strcat(filename, uri);
}

/* 发送静态文件内容 */
void feed_static(int fd, char *filename, int filesize) 
{
    int srcfd; 
    char *srcp, filetype[MAXLINE], buf[MAXBUF];

    /* 发送HTTP响应头 */
    get_filetype(filename, filetype);
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    sprintf(buf, "%sServer: weblet Web Server\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    rio_writen(fd, buf, strlen(buf));

    /* 发送文件内容 */
    srcfd = open(filename, O_RDONLY, 0);
    srcp = mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    close(srcfd);
    rio_writen(fd, srcp, filesize);
    munmap(srcp, filesize);
}

/* 根据文件扩展名获取MIME类型 */
void get_filetype(char *filename, char *filetype) 
{
    int i, len;
    strcpy(filetype, "text/html");  // 默认类型
    for (i = 0; extensions[i].ext != 0; i++) {
        len = strlen(extensions[i].ext);
        if (!strcmp(&filename[strlen(filename)-len], extensions[i].ext)) {
            strcpy(filetype, extensions[i].filetype);
            break;
        }
    }    
}

/* 启动子进程执行CGI程序 */
void feed_dynamic(int fd, char *filename, char *cgiargs) 
{
    char buf[MAXLINE], *emptylist[] = { NULL };
    int pfd[2];

    /* 构造HTTP响应头 */
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: weblet Web Server\r\n");
    rio_writen(fd, buf, strlen(buf));
    
    /* 创建管道并启动子进程 */
    (void)pipe(pfd);
    if (fork() == 0) {  // 子进程
        close(pfd[1]);  // 关闭写端
        dup2(pfd[0], STDIN_FILENO);  // 重定向标准输入
        dup2(fd, STDOUT_FILENO);  // 重定向标准输出到客户端
        execve(filename, emptylist, environ);  // 执行CGI程序
    }

    close(pfd[0]);  // 父进程关闭读端
    (void)write(pfd[1], cgiargs, strlen(cgiargs)+1);  // 传递参数
    wait(NULL);  // 等待子进程结束
    close(pfd[1]);  // 关闭写端
}
