#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "threadpool/threadpool.h"
// 服务器配置
#define PORT 80
#define MAX_LISTEN 128
#define MAX_EPOLL MAX_LISTEN+1
#define THREAD_COUNT 10
#define DEFAULT_PAGE "index.html"
#define SEND_BUF 2048
#define RECV_BUF 2048
#define SERVE_STATIC 0 // 处理静态请求
#define SERVE_DYNAMIC 1 // 处理动态请求
// 服务器根目录
static char g_root[256]="";
// 线程池
static ThreadPool g_pool;
// 多路复用io接口(epoll)
static int g_epoll_fd;
static void del_from_epoll(int efd, int fd);
void get_file_type(char *type, char *filepath) {
if(strstr(filepath,".html")) strcpy(type, "text/html");
else if(strstr(filepath, ".gif")) strcpy(type, "image/gif");
else if(strstr(filepath, ".jpg")) strcpy(type, "image/jpeg");
else if(strstr(filepath, ".png")) strcpy(type, "image/png");
else if(strstr(filepath, ".css")) strcpy(type, "text/css");
else if(strstr(filepath, ".js")) strcpy(type, "application/x-javascript");
else strcpy(type, "text/plain");
}
void get_absolute_path(char *abfilepath, char *rt, char *filepath) {
sprintf(abfilepath, "%s%s", rt, filepath);
}
void serve_error(int fd, char *filepath,const char *errnum,
const char *shortmsg, const char *longmsg) {
char head[SEND_BUF], body[SEND_BUF];
memset(body, 0, sizeof(body));
memset(head, 0 ,sizeof(head));
// 网页错误页面
sprintf(body, "<html><head><title>Tiny Web Server Error</title></head>");
sprintf(body, "%s<body><h1><font color='red'>Tiny Web Server Error</font></h1>", body);
sprintf(body, "%s<p>Error code: %s</p>",body, errnum);
sprintf(body, "%s<p>Cause: %s %s</p></body></html>", body,longmsg, filepath);
// http 头
sprintf(head, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);
sprintf(head, "%sContent-type: text/html\r\n", head);
sprintf(head, "%sContent-length: %d\r\n\r\n", head,(int)strlen(body));
// 发送
send(fd, head, strlen(head), MSG_NOSIGNAL);
send(fd, body, strlen(body), MSG_NOSIGNAL);
}
void serve_static(int fd, char *filepath, long filesize) {
int filefd;
char buf[SEND_BUF], filetype[128],*filemap;
memset(filetype, 0, sizeof(filetype));
memset(buf, 0, sizeof(buf));
// 发送http头
get_file_type(filetype, filepath); // 获取文件类型
sprintf(buf, "HTTP/1.1 200 OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sContent-length: %ld\r\n",buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
send(fd, buf, strlen(buf), MSG_NOSIGNAL);
// 发送文件内容
filefd = open(filepath, O_RDONLY);
filemap = (char *)mmap(0, filesize, PROT_READ, MAP_PRIVATE, filefd, 0);
close(filefd);
send(fd, filemap, filesize, MSG_NOSIGNAL);
munmap(filemap,filesize);
}
void serve_dynamic(int fd, char *filepath, char *args) {
// 创建一条进程;设置环境变量QUERY_STRING,将args设置进去; 最后将子进程的标准输出重新定位到客户端的socket描述符中,
// 只要子程序printf,就能把printf得数据传送到客户端。
pid_t pid;
char head[SEND_BUF];
// 发送http头
memset(head, 0, sizeof(head));
sprintf(head, "HTTP/1.1 200 OK\r\n");
sprintf(head, "%sServer: Tiny Web Server\r\n", head);
send(fd, head, strlen(head), MSG_NOSIGNAL);
if((pid=fork()) == 0) {
// 子进程
// 设置环境变量
setenv("QUERY_STRING", args, 1);
// 重定向标准输出, 默认输出fd是1
dup2(fd, 1);
// 启动CGI
execl(filepath, "" , (char*)0);
} else {
// 等待子进程结束
waitpid(pid, 0, 0);
}
}
int serve_type(const char *filepath) {
const char str[] = "/cgi-bin/\0";
if(strncmp(filepath, str, strlen(str)) == 0) {
return SERVE_DYNAMIC;
}
return SERVE_STATIC;
}
void parse_url(char *filepath, char *args, char *url) {
char *file_start, *args_start;
file_start = index(url, '/');
args_start = index(url, '?');
if(args_start != 0) {
memcpy(filepath, file_start, args_start-url);
memcpy(args, args_start+1, strlen(args_start)-1);
} else if(file_start != 0) {
memcpy(filepath, file_start, strlen(file_start));
}
}
void process_command(void *tp_args) {
char data[RECV_BUF], request[16], filepath[256], \
new_abfilepath[256],args[256], url[512];
struct stat filestat;
int res, type;
int fd = *(int*)tp_args;
free(tp_args);
memset(data, 0, sizeof(data));
memset(request, 0, sizeof(request));
memset(filepath, 0, sizeof(filepath));
memset(url, 0, sizeof(url));
memset(args, 0, sizeof(args));
memset(new_abfilepath, 0, sizeof(new_abfilepath));
// 获取请求
res = recv(fd, data, sizeof(data), MSG_NOSIGNAL);
if(res == 0 || res == -1) {
// 删除连接
goto __end;
}
printf("%s\n", data);
// 解析请求
sscanf(data, "%s %s", request, url);
if(strcasecmp("GET", request) != 0) {
// 无法识别请求
serve_error(fd, request, "501", "Not implememted",
"Tiny does not implement this method");
goto __end;
}
// 解析url
parse_url(filepath, args, url);
// 如果文件位置为'/', 就把它设置为默认页面
if(strlen(filepath) == 1) {
// 默认页面
strcat(filepath, DEFAULT_PAGE);
}
get_absolute_path(new_abfilepath, g_root, filepath); // 设置文件的绝对路径
// 获取文件属性
res = stat(new_abfilepath, &filestat);
if(res == -1) {
// 找不到相关文件
serve_error(fd, filepath,"404", "Not found",
"Tiny couldn't find this file");
goto __end;
}
// 判断是静态请求还是动态请求,动态请求也就是我们常说的CGI程序
type = serve_type(filepath);
if(type == SERVE_STATIC) {
if(!(S_ISREG(filestat.st_mode)) || !(S_IRUSR & filestat.st_mode)) {
// 错误,不能读取这文件
serve_error(fd, filepath, "403", "Forbidden",
"Tiny couldn't read this file");
goto __end;
}
// 开始回复静态请求
serve_static(fd, new_abfilepath, filestat.st_size);
} else {
if(!(S_ISREG(filestat.st_mode)) || !(S_IXUSR & filestat.st_mode)) {
// 错误,不能运行这文件
serve_error(fd, filepath, "403", "Forbidden",
"Tiny couldn't run this cgi file");
goto __end;
}
// 开始回复动态请求
serve_dynamic(fd, new_abfilepath, args);
}
// 删除连接
__end:
close(fd);
}
void add_to_epoll(int efd, int fd) {
int res;
struct epoll_event epe;
epe.data.fd = fd;
epe.events = EPOLLIN | EPOLLET;
res = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &epe);
if(res == -1) {
perror("epoll_ctl");
}
}
void del_from_epoll(int efd, int fd) {
int res = epoll_ctl(efd, EPOLL_CTL_DEL, fd, 0);
if(res == -1) {
perror("epoll_ctl");
}
}
void set_nonblock(int fd) {
int tmp = 1;
if(ioctl(fd, FIONBIO, &tmp) == -1) {
perror("ioctl");
}
}
void set_root(char *rt, char *filepath) {
char *end = rindex(filepath, '/');
memcpy(rt, filepath, end-filepath);
strcat(rt, "\0");
}
int main(int, char *argv[]) {
int server,res, blreuse;
struct sockaddr_in addr;
bool blres;
// 设置根目录, 也就是该服务器程序的主目录
set_root(g_root, argv[0]);
// 创建服务器
server = socket(AF_INET, SOCK_STREAM,