#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: jdbhttpd/0.2.0\r\n"
// 函数声明
void* accept_request(void *);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(unsigned short *);
void unimplemented(int);
void forbidden(int);
void serve_directory(int, const char *);
void url_decode(char *, const char *);
const char *get_mime_type(const char *);
void log_request(const char *, const char *, int);
void set_socket_timeout(int, int)
/**********************************************************************/
/* 处理客户端请求 */
/**********************************************************************/
void* accept_request(void *pclient)
{
int client = *(int*)pclient;
char buf[65536];
int numchars;
char method[255];
char url[255];
char path[1024]; // 增加路径长度
char decoded_url[1024]; // 存储解码后的URL
size_t i, j;
struct stat st;
int cgi = 0;
char *query_string = NULL;
// 获取请求的第一行
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
// 只支持GET和POST方法
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return NULL;
}
// POST请求启用CGI
if (strcasecmp(method, "POST") == 0)
cgi = 1;
// 读取URL
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
// URL解码
url_decode(decoded_url, url);
// 记录请求日志
log_request(method, decoded_url, client);
// 处理GET请求
if (strcasecmp(method, "GET") == 0)
{
query_string = decoded_url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
cgi = 1;
*query_string = '\0';
query_string++;
}
}
// 构建文件路径(使用当前目录)
sprintf(path, "web%s", decoded_url);
// 防止路径遍历攻击
if (strstr(path, "..")) {
forbidden(client);
close(client);
return NULL;
}
// 处理目录请求
if (path[strlen(path) - 1] == '/')
strcat(path, "Index.html");
// 检查文件/目录是否存在
if (stat(path, &st) == -1) {
// 检查是否存在.html扩展名文件
char alt_path[1024];
sprintf(alt_path, "%s.html", path);
if (stat(alt_path, &st) == 0) {
strcpy(path, alt_path);
} else {
while ((numchars > 0) && strcmp("\n", buf))
numchars = get_line(client, buf, sizeof(buf));
not_found(client);
close(client);
return NULL;
}
}
// 如果是目录
if ((st.st_mode & S_IFMT) == S_IFDIR) {
// 检查目录中是否有index.html
char index_path[1024];
sprintf(index_path, "%s/Index.html", path);
if (stat(index_path, &st) == 0) {
strcpy(path, index_path);
} else {
// 显示目录列表
serve_directory(client, path);
close(client);
return NULL;
}
}
// 检查执行权限
if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))
cgi = 1;
// 处理静态文件或CGI
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string);
close(client);
return NULL;
}
/**********************************************************************/
/* URL解码 */
/**********************************************************************/
void url_decode(char *dest, const char *src) {
char *p = dest;
while (*src) {
if (*src == '%') {
if (src[1] && src[2]) {
char hex[3] = {src[1], src[2], '\0'};
*p++ = (char)strtol(hex, NULL, 16);
src += 3;
} else {
*p++ = *src++;
}
} else if (*src == '+') {
*p++ = ' ';
src++;
} else {
*p++ = *src++;
}
}
*p = '\0';
}
/**********************************************************************/
/* 获取MIME类型 */
/**********************************************************************/
const char *get_mime_type(const char *filename) {
const char *dot = strrchr(filename, '.');
if (!dot) return "text/plain";
if (strcasecmp(dot, ".html") == 0 || strcasecmp(dot, ".htm") == 0)
return "text/html";
if (strcasecmp(dot, ".css") == 0)
return "text/css";
if (strcasecmp(dot, ".js") == 0)
return "application/javascript";
if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcasecmp(dot, ".png") == 0)
return "image/png";
if (strcasecmp(dot, ".gif") == 0)
return "image/gif";
if (strcasecmp(dot, ".json") == 0)
return "application/json";
if (strcasecmp(dot, ".ico") == 0)
return "image/x-icon";
return "text/plain";
}
/**********************************************************************/
/* 记录请求日志 */
/**********************************************************************/
void log_request(const char *method, const char *url, int client) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[64];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
struct sockaddr_in addr;
socklen_t addr_len = sizeof(addr);
getpeername(client, (struct sockaddr*)&addr, &addr_len);
char *ip = inet_ntoa(addr.sin_addr);
printf("[%s] %s %s %s\n", timestamp, ip, method, url);
}
/**********************************************************************/
/* 处理目录列表 */
/**********************************************************************/
void serve_directory(int client, const char *path) {
char buf[4096];
// 发送HTTP头
sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
// 发送HTML头部
sprintf(buf, "<html><head><title>Index of %s</title></head>", path);
send(client, buf, strlen(buf), 0);
sprintf(buf, "<body><h1>Index of %s</h1><ul>", path);
send(client, buf, strlen(buf), 0);
// 打开目录
DIR *dir = opendir(path);
if (dir) {
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
// 跳过隐藏文件
if (ent->d_name[0] == '.') continue;
char full_path[1024];
sprintf(full_path, "%s/%s", path, ent->d_name);
struct stat st;
stat(full_path, &st);
char size_buf[32];
if (S_ISDIR(st.st_mode)) {
strcpy(size_buf, "[DIR]");
} else {
if (st.st_size < 1024) {
sprintf(size_buf, "%ld B", st.st_size);
} else if (st.st_size < 1024 * 1024) {
sprintf(size_buf, "%.1f KB", st.st_size / 1024.0);
} else {
sprintf(size_buf, "%.1f MB", st.st_size / (1024.0 * 1024));
}
}
sprintf(buf, "<li><a href=\"%s\">%s</a> - %s</li>",
ent->d_name, ent->d_name, size_buf);
send(client, buf, strlen(buf), 0);
}
closedir(dir);
}
// 发送HTML尾部
sprintf(buf, "</ul></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 400 Bad Request */
/**********************************************************************/
void bad_request(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>400 Bad Request</h1></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 403 Forbidden */
/**********************************************************************/
void forbidden(int client) {
char buf[1024];
sprintf(buf, "HTTP/1.0 403 Forbidden\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>403 Forbidden</h1><p>Access to this resource is denied.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 发送文件内容 */
/**********************************************************************/
void cat(int client, FILE *resource)
{
char buf[65536];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}
/**********************************************************************/
/* 500 Internal Server Error */
/**********************************************************************/
void cannot_execute(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>500 Internal Server Error</h1><p>CGI execution failed.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 错误处理 */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit(1);
}
/**********************************************************************/
/* 执行CGI脚本 */
/**********************************************************************/
void execute_cgi(int client, const char *path, const char *method, const char *query_string)
{
char buf[65536];
int cgi_output[2];
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchars = 1;
int content_length = -1;
buf[0] = 'A'; buf[1] = '\0';
if (strcasecmp(method, "GET") == 0)
while ((numchars > 0) && strcmp("\n", buf))
numchars = get_line(client, buf, sizeof(buf));
else
{
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > 0) && strcmp("\n", buf))
{
buf[15] = '\0';
if (strcasecmp(buf, "Content-Length:") == 0)
content_length = atoi(&(buf[16]));
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -1) {
bad_request(client);
return;
}
}
sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
if (pipe(cgi_output) < 0) {
cannot_execute(client);
return;
}
if (pipe(cgi_input) < 0) {
cannot_execute(client);
return;
}
if ((pid = fork()) < 0 ) {
cannot_execute(client);
return;
}
if (pid == 0) /* 子进程: CGI脚本 */
{
char meth_env[255];
char query_env[255];
char length_env[255];
dup2(cgi_output[1], 1);
dup2(cgi_input[0], 0);
close(cgi_output[0]);
close(cgi_input[1]);
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == 0) {
sprintf(query_env, "QUERY_STRING=%s", query_string);
putenv(query_env);
}
else { /* POST */
sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
putenv(length_env);
}
execl(path, path, NULL);
exit(0);
} else { /* 父进程 */
close(cgi_output[1]);
close(cgi_input[0]);
if (strcasecmp(method, "POST") == 0)
for (i = 0; i < content_length; i++) {
recv(client, &c, 1, 0);
write(cgi_input[1], &c, 1);
}
while (read(cgi_output[0], &c, 1) > 0)
send(client, &c, 1, 0);
close(cgi_output[0]);
close(cgi_input[1]);
waitpid(pid, &status, 0);
}
}
/**********************************************************************/
/* 读取一行 */
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
/**********************************************************************/
/* 发送HTTP头 */
/**********************************************************************/
void headers(int client, const char *filename, const char *content_type)
{
char buf[1024];
(void)filename; // 未使用
strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: %s\r\n", content_type);
send(client, buf, strlen(buf), 0);
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 404 Not Found */
/**********************************************************************/
void not_found(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>404 Not Found</h1><p>The requested URL was not found on this server.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
/**********************************************************************/
/* 服务静态文件 */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
// 丢弃请求头
buf[0] = 'A'; buf[1] = '\0';
while ((numchars > 0) && strcmp("\n", buf))
numchars = get_line(client, buf, sizeof(buf));
resource = fopen(filename, "r");
if (resource == NULL)
not_found(client);
else
{
// 获取MIME类型
const char *content_type = get_mime_type(filename);
headers(client, filename, content_type);
cat(client, resource);
}
fclose(resource);
}
/**********************************************************************/
/* 启动服务器 */
/**********************************************************************/
int startup(unsigned short *port)
{
int httpd = 0;
struct sockaddr_in name;
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
if (*port == 0)
{
socklen_t namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
error_die("getsockname");
*port = ntohs(name.sin_port);
}
if (listen(httpd, 5) < 0)
error_die("listen");
return(httpd);
}
/**********************************************************************/
/* 501 Not Implemented */
/**********************************************************************/
void unimplemented(int client)
{
char buf[1024];
sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<html><body><h1>501 Not Implemented</h1><p>The requested method is not implemented.</p></body></html>\r\n");
send(client, buf, strlen(buf), 0);
}
// 设置套接字发送超时(在主循环accept后调用)
void set_socket_timeout(int sockfd, int timeout_sec) {
struct timeval tv;
tv.tv_sec = timeout_sec; // 超时秒数
tv.tv_usec = 0;
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
perror("setsockopt SO_SNDTIMEO failed");
}
}
/**********************************************************************/
/* 主函数 */
/**********************************************************************/
int main(void)
{
int server_sock = -1;
unsigned short port = 8080;
int client_sock = -1;
struct sockaddr_in client_name;
socklen_t client_name_len = sizeof(client_name);
pthread_t newthread;
server_sock = startup(&port);
printf("HTTP server running on port %d\n", port);
signal(SIGPIPE, SIG_IGN);
while (1)
{
client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);
if (client_sock == -1)
error_die("accept");
if (pthread_create(&newthread , NULL, accept_request, (void*)&client_sock) != 0)
perror("pthread_create");
set_socket_timeout(client_sock, 2);
}
close(server_sock);
return(0);
}
目前代码如上。
线程卡死的问题解决了,但是返回的页面无法正常显示图片
最新发布