请为我修改为标准的C语言注释、代码中大括号上下要对齐//
// webserver.c — Minimal multithreaded HTTP static file server (Linux)
// Build: gcc webserver.c -o webserver -lpthread
// Run: ./webserver
// Browse: http://localhost:8080/
//
// Serves files under the WEB_ROOT directory. Defaults to "Index.html".
// Supports keep-alive on GET, basic content-type mapping, and zero-copy sendfile.
//
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define PORT 8080
#define BUFFER_SIZE 8192
#define WEB_ROOT "web/"
// -------------------------------
// Request structure & parsing
// -------------------------------
typedef struct {
char method[16];
char path[1024];
} Request;
static Request parse_request(const char *buffer) {
Request req;
memset(&req, 0, sizeof(req));
strcpy(req.method, ""); // default empty
strcpy(req.path, "/"); // default root
// Very simple parse: METHOD PATH HTTP/1.1
// e.g., "GET /index.html HTTP/1.1"
sscanf(buffer, "%15s %1023s", req.method, req.path);
return req;
}
// -------------------------------
// Utilities
// -------------------------------
static const char *guess_content_type(const char *path) {
// crude extension check
const char *ext = strrchr(path, '.');
if (!ext) return "text/html";
if (!strcmp(ext, ".html") || !strcmp(ext, ".htm")) return "text/html; charset=utf-8";
if (!strcmp(ext, ".css")) return "text/css; charset=utf-8";
if (!strcmp(ext, ".js")) return "application/javascript; charset=utf-8";
if (!strcmp(ext, ".json")) return "application/json; charset=utf-8";
if (!strcmp(ext, ".png")) return "image/png";
if (!strcmp(ext, ".jpg") || !strcmp(ext, ".jpeg")) return "image/jpeg";
if (!strcmp(ext, ".gif")) return "image/gif";
if (!strcmp(ext, ".svg")) return "image/svg+xml";
if (!strcmp(ext, ".ico")) return "image/x-icon";
if (!strcmp(ext, ".txt")) return "text/plain; charset=utf-8";
return "application/octet-stream";
}
static bool contains_dotdot(const char *p) {
return strstr(p, "..") != NULL;
}
// -------------------------------
// Client handler
// -------------------------------
static void *handle_client(void *arg) {
int client_fd = *((int *)arg);
free(arg);
int keep_alive = 1; // default keep-alive unless "Connection: close"
char buffer[BUFFER_SIZE];
while (keep_alive) {
memset(buffer, 0, sizeof(buffer));
ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytes_read <= 0) break; // closed or error
// Basic keep-alive check
if (strstr(buffer, "Connection: close")) {
keep_alive = 0;
}
Request req = parse_request(buffer);
// Only support GET/HEAD for static content; POST placeholder
if (!strcmp(req.method, "GET") || !strcmp(req.method, "HEAD")) {
char full_path[2048];
struct stat st;
// Directory traversal guard
if (contains_dotdot(req.path)) {
const char *bad = "HTTP/1.1 400 Bad Request\r\n"
"Content-Type: text/plain; charset=utf-8\r\n"
"Connection: close\r\n\r\n"
"Bad Request";
write(client_fd, bad, strlen(bad));
break;
}
if (!strcmp(req.path, "/")) {
snprintf(full_path, sizeof(full_path), "%sIndex.html", WEB_ROOT);
} else {
// strip leading '/' for local file path
const char *sub = req.path[0] == '/' ? req.path + 1 : req.path;
snprintf(full_path, sizeof(full_path), "%s%s", WEB_ROOT, sub);
}
if (stat(full_path, &st) < 0 || S_ISDIR(st.st_mode)) {
const char *not_found =
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Connection: keep-alive\r\n\r\n"
"<!doctype html><html><body><h1>404 Not Found</h1></body></html>";
write(client_fd, not_found, strlen(not_found));
continue;
} else {
const char *content_type = guess_content_type(full_path);
// Headers
char headers[512];
int n = snprintf(headers, sizeof(headers),
"HTTP/1.1 200 OK\r\n"
"Connection: keep-alive\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n\r\n",
content_type, (long)st.st_size);
write(client_fd, headers, (size_t)n);
if (!strcmp(req.method, "HEAD")) {
continue; // header only
}
int file_fd = open(full_path, O_RDONLY);
if (file_fd != -1) {
off_t offset = 0;
size_t remaining = (size_t)st.st_size;
while (remaining > 0) {
ssize_t sent = sendfile(client_fd, file_fd, &offset, remaining);
if (sent <= 0) break;
remaining -= (size_t)sent;
}
close(file_fd);
}
}
} else if (!strcmp(req.method, "POST")) {
// Placeholder: echo 204 No Content (not implemented)
char* contentLengthstr = strstr(buffer,"Content-Length");
if(!contentLengthstr)
{
printf("No content-length key\n");
}
else
{
int contentLength;
sscanf(contentLengthstr+14,":%d",&contentLength);
char*payload = strstr(buffer, "\r\n\r\n");
for(int i=0;i<contentLength;i++)
printf("%c",*(payload+4+i));
printf("\n");
}
const char *resp =
"HTTP/1.1 204 No Content\r\n"
"Connection: keep-alive\r\n\r\n";
write(client_fd, resp, strlen(resp));
} else {
const char *method_not_allowed =
"HTTP/1.1 405 Method Not Allowed\r\n"
"Allow: GET, HEAD, POST\r\n"
"Connection: keep-alive\r\n\r\n";
write(client_fd, method_not_allowed, strlen(method_not_allowed));
}
// Optional: break if the client sent only one request without keep-alive
// (Most modern browsers default to keep-alive; we keep the loop.)
}
close(client_fd);
return NULL;
}
// -------------------------------
// Main entry
// -------------------------------
int main(void) {
int server_fd;
struct sockaddr_in address;
int opt = 1;
// Create socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// SO_REUSEADDR
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
close(server_fd);
exit(EXIT_FAILURE);
}
// Bind
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// Listen
if (listen(server_fd, 10) < 0) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("WebServer running on port %d\n", PORT);
printf("Access at: http://localhost:%d/\n", PORT);
// Accept loop
while (1) {
int *client_fd = (int *)malloc(sizeof(int));
if (!client_fd) {
perror("malloc");
continue;
}
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
*client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (*client_fd < 0) {
perror("accept");
free(client_fd);
continue;
}
// Client info (optional)
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN);
// printf("New connection from: %s:%d\n", client_ip, ntohs(client_addr.sin_port));
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, handle_client, client_fd) != 0) {
perror("pthread_create");
close(*client_fd);
free(client_fd);
continue;
}
pthread_detach(thread_id); // resources auto-released when thread exits
}
// Not reached
close(server_fd);
return 0;
}