#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 process_trans(int fd,int hit); void *serve_client(void *vargp); typedef struct _client_data_t { int conn_sock; //客户连接socket int hit; //第几个客户 } client_data_t; /* 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; struct sockaddr_in clientaddr; int hit; // 请求计数器 socklen_t clientlen; client_data_t *cdp; pthread_t tid; /* 检查命令行参数 */ 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); cdp = malloc(sizeof(client_data_t)); cdp->conn_sock = accept(listen_sock, (SA *)&clientaddr, &clientlen); cdp->hit=hit; pthread_create(&tid, NULL, serve_client, (void *)cdp); } exit(0); } /* thread routine */ void * serve_client (void *vargp) { int hit,conn_sock; client_data_t cd; cd = *(client_data_t *)vargp; pthread_detach(pthread_self()); free(vargp); process_trans(cd.conn_sock,cd.hit); // 处理HTTP事务 close(conn_sock); return NULL; } /* 处理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, "错误请求"); sprintf(body, "%s\r\n", body); sprintf(body, "%s

%s %s

\r\n", body, errnum, shortmsg); sprintf(body, "%s

%s: %s

\r\n", body, description, cause); sprintf(body, "%s
weblet Web服务器\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]); // 关闭写端 }