Files
C-exp-collection/server-exp2/webletsp.c
2026-06-18 18:13:12 +08:00

294 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; // 确保被信号中断的系统调用自动重启
sigaction(sig, &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]);
//设置SIGHLD的信号处理函数用于收割结束的子进程
Signal(SIGCHLD, sigchld_handler);
Signal(SIGPIPE, SIG_IGN);
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]); // 关闭写端
}