Files
C-exp-collection/server-exp/weblet.c

279 lines
8.5 KiB
C
Raw Normal View History

2026-05-28 08:48:01 +08:00
#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
/* 全局变量说明 */
/* 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);
}
/* 设置Ctrl+C信号处理 */
if(signal(SIGINT,ctrlc_handler)==SIG_ERR)
(void)printf("错误: 无法设置SIGINT信号处理\n");
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);
process_trans(conn_sock,hit); // 处理HTTP事务
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]="http/1.1";
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
int rhn=0; // 请求头计数器
printf2("第%d次请求开始\n",hit);
/* 读取请求行和请求头 */
rio_readinitb(&rio, fd);
rio_readlineb(&rio, buf, MAXLINE);
printf3("请求头%d:%s", ++rhn, buf);
//判断第一个请求行是否正好有三个单词构成如GET / http/1.1
/*int wn=0; char *p=buf;
while(*p==' ' || *p=='\t') p++;
while(*p) {
if((*p==' '||*p=='\t') && *(p+1)!=' ') wn++;
p++;
}
if (wn==2) */
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]); // 关闭写端
}