291 lines
8.9 KiB
C
291 lines
8.9 KiB
C
#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]); // 关闭写端
|
||
}
|