Files

344 lines
12 KiB
C
Raw Permalink Normal View History

2026-06-18 18:13:12 +08:00
#include <arpa/inet.h> // 提供网络地址转换函数
#include <errno.h> // 定义错误码
#include <fcntl.h> // 文件操作相关函数
#include <netinet/in.h> // 定义网络协议的结构体和函数
#include <signal.h> // 提供信号处理函数
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数如exit()
#include <string.h> // 字符串操作函数
#include <sys/socket.h> // 套接字相关函数
#include <sys/time.h> // 提供 gettimeofday 函数
#include <sys/types.h> // 定义数据类型
#include <time.h> // 时间相关函数
#include <unistd.h> // 提供POSIX API如read()、write()
#define VERSION 23 // 服务器版本号
#define BUFSIZE 8096 // 缓冲区大小
#define ERROR 42 // 错误日志类型
#define LOG 44 // 普通日志类型
#define FORBIDDEN 403 // HTTP状态码禁止访问
#define NOTFOUND 404 // HTTP状态码未找到资源
#ifndef SIGCLD
#define SIGCLD SIGCHLD // 为兼容不同系统定义信号别名
#endif
// 支持的文件扩展名及其对应的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} // 结束标志
};
/* Ctrl+C信号处理函数 */
void ctrlc_handler(int sig)
{
printf("您按下了Ctrl+C终止了Web服务器\n");
exit(0);
}
// 计算两个时间点之间的时间差(以毫秒为单位)
long calculate_time_diff(struct timespec start, struct timespec end) {
long diff_sec = end.tv_sec - start.tv_sec; // 秒部分差值
long diff_nsec = end.tv_nsec - start.tv_nsec; // 纳秒部分差值
return diff_sec * 1000 + diff_nsec / 1000000.0; // 转换为毫秒
}
void logger(int type, const char *s1, const char *s2, int socket_fd) {
int fd;
char logbuffer[BUFSIZE * 2];
char timebuffer[64]; // 用于存储格式化时间字符串
// 获取当前时间(秒和微秒)
struct timeval tv;
gettimeofday(&tv, NULL);
// 将秒部分格式化为字符串(年月日 时分秒)
struct tm *tm_info = localtime(&tv.tv_sec);
strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S", tm_info);
// 添加毫秒部分到时间字符串
char full_timebuffer[256];
snprintf(full_timebuffer, sizeof(full_timebuffer), "[%s.%03ld]", timebuffer, tv.tv_usec / 1000);
// 根据日志类型生成日志内容
switch (type) {
case ERROR: // 错误日志
snprintf(logbuffer, sizeof(logbuffer), "%s [ERROR] %s:%s Errno=%d exiting pid=%d",
full_timebuffer, s1, s2, errno, getpid());
break;
case FORBIDDEN: // 403 禁止访问
if (write(socket_fd,
"HTTP/1.1 403 Forbidden\nContent-Length: 185\nConnection: "
"close\nContent-Type: text/html\n\n"
"<html><head>\n<title>403 "
"Forbidden</title>\n</head><body>\n<h1>Forbidden</h1>\n"
"The requested URL, file type or operation is not allowed on this simple "
"static file webserver.\n"
"</body></html>\n",
271) < 0) {
perror("Failed to send 403 Forbidden response");
}
snprintf(logbuffer, sizeof(logbuffer), "%s [FORBIDDEN] %s:%s", full_timebuffer, s1, s2);
break;
case NOTFOUND: // 404 未找到
if (write(socket_fd,
"HTTP/1.1 404 Not Found\nContent-Length: 136\nConnection: "
"close\nContent-Type: text/html\n\n"
"<html><head>\n<title>404 Not Found</title>\n</head><body>\n<h1>Not "
"Found</h1>\n"
"The requested URL was not found on this server.\n</body></html>\n",
224) < 0) {
perror("Failed to send 404 Not Found response");
}
snprintf(logbuffer, sizeof(logbuffer), "%s [NOT FOUND] %s:%s", full_timebuffer, s1, s2);
break;
case LOG: // 普通日志
snprintf(logbuffer, sizeof(logbuffer), "%s [INFO] %s:%s:%d", full_timebuffer, s1, s2,
socket_fd);
break;
}
// 打开日志文件并写入日志内容
#ifdef DEBUG
if ((fd = open("webserver.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
if (write(fd, logbuffer, strlen(logbuffer)) < 0) {
perror("Failed to write log to file");
}
if (write(fd, "\n", 1) < 0) {
perror("Failed to write newline to log file");
}
close(fd);
} else {
perror("Failed to open log file");
}
#endif
}
// 处理HTTP请求的函数
void web(int fd, int hit) {
int j, file_fd, buflen;
long i, ret, len;
char *fstr;
static char buffer[BUFSIZE + 1]; // 静态缓冲区自动初始化为0
// 定义计时变量
struct timespec start, end;
long duration;
logger(LOG, "Process new web request", "", fd);
// 记录开始时间(读取请求)
clock_gettime(CLOCK_MONOTONIC, &start);
ret = read(fd, buffer, BUFSIZE); // 读取客户端请求
if (ret <= 0) { // 读取失败
logger(FORBIDDEN, "Failed to read browser request", "", fd);
return;
}
buffer[ret]='\0';
#ifdef DEBUG
printf("第%d个客户请求:%s\n",hit,buffer);
#endif
// 记录结束时间并计算耗时
clock_gettime(CLOCK_MONOTONIC, &end);
duration = calculate_time_diff(start, end);
logger(LOG, "Time taken for request reading", "", duration);
// 清除换行符和回车符
for (i = 0; i < ret; i++) {
if (buffer[i] == '\r' || buffer[i] == '\n') buffer[i] = '*';
}
// 记录开始时间(解析请求)
clock_gettime(CLOCK_MONOTONIC, &start);
if (strncmp(buffer, "GET ", 4)) { // 检查是否为GET请求
logger(FORBIDDEN, "Only simple GET operation supported", buffer, fd);
return;
}
logger(LOG, "request", buffer, hit);
for (i = 4; i < BUFSIZE; i++) { // 截取文件路径
if (buffer[i] == ' ') {
buffer[i] = 0;
break;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
duration = calculate_time_diff(start, end);
logger(LOG, "Time taken for request parsing", "", duration);
// 检查路径是否合法
for (j = 0; j < i - 1; j++) {
if (buffer[j] == '.' && buffer[j + 1] == '.') {
logger(FORBIDDEN, "Parent directory (..) path names not supported", buffer, fd);
return;
}
}
// 默认返回 index.html
if (!strncmp(&buffer[0], "GET /\0", 6)) strcpy(buffer, "GET /index.html");
// 文件类型解析
buflen = strlen(buffer);
fstr = 0;
clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; extensions[i].ext != 0; i++) {
len = strlen(extensions[i].ext);
if (!strncmp(&buffer[buflen - len], extensions[i].ext, len)) {
fstr = extensions[i].filetype;
break;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
duration = calculate_time_diff(start, end);
logger(LOG, "Time taken for file type detection", "", duration);
if (fstr == 0) {
logger(FORBIDDEN, "File extension type not supported", buffer, fd);
return;
}
// 文件打开
clock_gettime(CLOCK_MONOTONIC, &start);
if ((file_fd = open(&buffer[5], O_RDONLY)) == -1) {
logger(NOTFOUND, "Failed to open file", &buffer[5], fd);
return;
}
clock_gettime(CLOCK_MONOTONIC, &end);
duration = calculate_time_diff(start, end);
logger(LOG, "Time taken for file opening", "", duration);
// 文件读取与传输
logger(LOG, "SEND", &buffer[5], hit);
clock_gettime(CLOCK_MONOTONIC, &start);
len = (long)lseek(file_fd, 0, SEEK_END);
lseek(file_fd, 0, SEEK_SET);
sprintf(buffer,
"HTTP/1.1 200 OK\nServer: nweb/%d.0\nContent-Length: %ld\nConnection: "
"close\nContent-Type: %s\n\n",
VERSION, len, fstr);
logger(LOG, "Header", buffer, hit);
if (write(fd, buffer, strlen(buffer)) < 0) {
logger(ERROR, "Failed to write HTTP header", "", fd);
close(file_fd);
return;
}
// 分块读取文件内容并发送
while ((ret = read(file_fd, buffer, BUFSIZE)) > 0) {
if (write(fd, buffer, ret) < 0) {
logger(ERROR, "Failed to write file content", "", fd);
break;
}
}
clock_gettime(CLOCK_MONOTONIC, &end);
duration = calculate_time_diff(start, end);
logger(LOG, "Time taken for file transfer", "", duration);
#ifdef DEBUG
printf("第%d个客户服务器完成\n",hit);
#endif
// 关闭文件和套接字
close(file_fd);
close(fd);
}
// 主函数,设置服务器并监听客户端连接
int main(int argc, char **argv) {
int i, port, listenfd, socketfd, hit;
socklen_t length;
static struct sockaddr_in cli_addr; // 客户端地址
static struct sockaddr_in serv_addr; // 服务器地址
// 检查命令行参数是否正确
if (argc < 3 || argc > 3 || !strcmp(argv[1], "-?")) {
(void)printf(
"hint: webserver Port-Number Top-Directory\t\tversion %d\n\n"
"\twebserver is a small and very safe mini web server\n"
"\twebserver only servers out file/web pages with extensions named below\n"
"\t and only from the named directory or its sub-directories.\n"
"\tThere is no fancy features = safe and secure.\n\n"
"\tExample: webserver 8181 /home/oslab &\n\n"
"\tOnly Supports:",
VERSION);
for (i = 0; extensions[i].ext != 0; i++) (void)printf(" %s", extensions[i].ext);
(void)printf(
"\n\tNot Supported: URLs including \"..\", Java, Javascript, CGI\n"
"\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
"\tNo warranty given or implied\n\tNigel Griffiths nag@uk.ibm.com\n");
exit(0);
}
/* 设置Ctrl+C信号处理 */
if(signal(SIGINT,ctrlc_handler)==SIG_ERR)
(void)printf("错误: 无法设置SIGINT信号处理\n");
if (!strncmp(argv[2], "/", 2) || !strncmp(argv[2], "/etc", 5) || !strncmp(argv[2], "/bin", 5) ||
!strncmp(argv[2], "/lib", 5) || !strncmp(argv[2], "/tmp", 5) ||
!strncmp(argv[2], "/usr", 5) || !strncmp(argv[2], "/dev", 5) ||
!strncmp(argv[2], "/sbin", 6)) {
(void)printf("ERROR: Bad top directory %s, see nweb -?\n", argv[2]);
exit(3);
}
// 验证并切换到指定目录
if (chdir(argv[2]) == -1) {
perror("Failed to change directory");
exit(4);
}
port = atoi(argv[1]);
if (port < 1 || port > 60000) {
fprintf(stderr, "Invalid port number: %d\n", port);
exit(1);
}
// 设置服务器监听套接字
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(1);
} serv_addr.sin_family = AF_INET;
/* Eliminates "Address already in use" error from bind. */
int optval=1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int)) < 0)
return -1;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
if (bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Bind failed");
exit(1);
}
if (listen(listenfd, 64) < 0) {
perror("Listen failed");
exit(1);
}
// 接受客户端连接
for (hit = 1;; hit++) {
length = sizeof(cli_addr);
if ((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0) {
logger(ERROR, "system call", "accept", 0);
}
web(socketfd, hit);
}
logger(LOG, "webserver exit noramlly", "", 0);
return 0;
}