210 lines
5.9 KiB
C
210 lines
5.9 KiB
C
/*
|
||
Реализовать Web-Server использующий многопроцессный
|
||
способ обработки клиентов через fork(). Веб-сервер должен
|
||
работать в виде демона и обеспечивать базовую поддержку
|
||
протокола HTTP, на уровне метода GET (по желанию методы
|
||
HEAD и POST). Проверку Web-Server’а необходимо
|
||
осуществлять на статических текстовых и двоичных данных,
|
||
таких как изображения. Предусмотреть контроль и
|
||
журналирование ошибок (либо в файл, либо через syslog).
|
||
Обеспечить одновременную работу сервера с множественным
|
||
числом клиентов.
|
||
*/
|
||
|
||
#include "daemon.h"
|
||
#include <fcntl.h>
|
||
#include <netinet/in.h>
|
||
#include <signal.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/syslog.h>
|
||
#include <unistd.h>
|
||
|
||
static int stop = 0;
|
||
static char www_root[512];
|
||
|
||
void signal_handler(int sig) {
|
||
switch (sig) {
|
||
case SIGTERM:
|
||
case SIGINT:
|
||
syslog(LOG_INFO, "Terminate signal catched. Stopping daemon...");
|
||
stop = 1;
|
||
break;
|
||
}
|
||
}
|
||
|
||
int create_server_socket(int port) {
|
||
struct sockaddr_in addr;
|
||
|
||
int listener = socket(AF_INET, SOCK_STREAM, 0);
|
||
if (listener < 0) {
|
||
syslog(LOG_ERR, "Unable to create server socket");
|
||
exit(1);
|
||
}
|
||
|
||
int opt = 1;
|
||
if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
|
||
syslog(LOG_WARNING, "Unable to set SO_REUSEADDR");
|
||
}
|
||
|
||
addr.sin_family = AF_INET;
|
||
addr.sin_port = htons(port);
|
||
addr.sin_addr.s_addr = INADDR_ANY;
|
||
if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||
syslog(LOG_ERR, "Unable to bind socket to port %d", port);
|
||
exit(1);
|
||
}
|
||
|
||
return listener;
|
||
}
|
||
|
||
const char *get_content_type(const char *path) {
|
||
const char *ext = strrchr(path, '.');
|
||
if (!ext)
|
||
return "application/octet-stream";
|
||
if (strcmp(ext, ".html") == 0)
|
||
return "text/html; charset=utf-8";
|
||
if (strcmp(ext, ".txt") == 0)
|
||
return "text/plain; charset=utf-8";
|
||
if (strcmp(ext, ".png") == 0)
|
||
return "image/png";
|
||
if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0)
|
||
return "image/jpeg";
|
||
if (strcmp(ext, ".css") == 0)
|
||
return "text/css";
|
||
if (strcmp(ext, ".js") == 0)
|
||
return "application/javascript";
|
||
return "application/octet-stream";
|
||
}
|
||
|
||
void send_file(int client_sock, const char *filepath) {
|
||
int fd = open(filepath, O_RDONLY);
|
||
if (fd < 0) {
|
||
/* Файл не найден - отправляем 404 */
|
||
const char *response = "HTTP/1.0 404 Not Found\r\n"
|
||
"Content-Type: text/html\r\n\r\n"
|
||
"<h1>404 Not Found</h1>";
|
||
write(client_sock, response, strlen(response));
|
||
syslog(LOG_INFO, "File not found: %s", filepath);
|
||
return;
|
||
}
|
||
|
||
/* Получаем размер файла */
|
||
struct stat st;
|
||
fstat(fd, &st);
|
||
|
||
/* Отправляем заголовки HTTP */
|
||
char header[256];
|
||
snprintf(header, sizeof(header),
|
||
"HTTP/1.0 200 OK\r\n"
|
||
"Content-Type: %s\r\n"
|
||
"Content-Length: %ld\r\n\r\n",
|
||
get_content_type(filepath), st.st_size);
|
||
write(client_sock, header, strlen(header));
|
||
|
||
/* Отправляем содержимое файла */
|
||
char buf[4096];
|
||
int bytes;
|
||
while ((bytes = read(fd, buf, sizeof(buf))) > 0) {
|
||
write(client_sock, buf, bytes);
|
||
}
|
||
|
||
close(fd);
|
||
syslog(LOG_INFO, "Sent file: %s", filepath);
|
||
}
|
||
|
||
void handle_client(int client_sock) {
|
||
char buf[1024];
|
||
int bytes_read = read(client_sock, buf, sizeof(buf) - 1);
|
||
|
||
if (bytes_read <= 0) {
|
||
syslog(LOG_ERR, "Unable to read from client");
|
||
return;
|
||
}
|
||
|
||
buf[bytes_read] = '\0';
|
||
|
||
/* Парсим первую строку: GET /path HTTP/1.x */
|
||
char method[16], path[256], protocol[16];
|
||
if (sscanf(buf, "%s %s %s", method, path, protocol) != 3) {
|
||
syslog(LOG_ERR, "Invalid HTTP request");
|
||
return;
|
||
}
|
||
|
||
/* Поддерживаем только GET */
|
||
if (strcmp(method, "GET") != 0) {
|
||
const char *response = "HTTP/1.0 501 Not Implemented\r\n\r\n";
|
||
write(client_sock, response, strlen(response));
|
||
syslog(LOG_INFO, "Unsupported method: %s", method);
|
||
return;
|
||
}
|
||
|
||
/* Формируем путь к файлу */
|
||
char filepath[512];
|
||
snprintf(filepath, sizeof(filepath), "%s", www_root);
|
||
|
||
/* Если запрашивают /, отдаём index.html */
|
||
if (strcmp(path, "/") == 0) {
|
||
strcat(filepath, "/index.html");
|
||
} else {
|
||
strcat(filepath, path);
|
||
}
|
||
|
||
syslog(LOG_INFO, "Request: %s %s -> %s", method, path, filepath);
|
||
|
||
/* Отправляем файл */
|
||
send_file(client_sock, filepath);
|
||
}
|
||
|
||
int server(const char *root, int port) {
|
||
snprintf(www_root, sizeof(www_root), "%s", root);
|
||
|
||
openlog("MY-WEB-SERVER", 0, LOG_DAEMON);
|
||
|
||
daemonize();
|
||
|
||
signal(SIGTERM, signal_handler);
|
||
signal(SIGINT, signal_handler);
|
||
|
||
/* Автоматическая очистка завершенных процессов */
|
||
signal(SIGCHLD, SIG_IGN);
|
||
|
||
int client_sock;
|
||
int server_socket = create_server_socket(port);
|
||
listen(server_socket, 5);
|
||
|
||
syslog(LOG_INFO, "Server started on port %d, serving %s", port, www_root);
|
||
|
||
while (!stop) {
|
||
client_sock = accept(server_socket, NULL, NULL);
|
||
if (client_sock < 0) {
|
||
if (stop) {
|
||
break;
|
||
}
|
||
syslog(LOG_ERR, "Unable to accept client connection");
|
||
continue;
|
||
}
|
||
|
||
switch (fork()) {
|
||
case -1:
|
||
syslog(LOG_ERR, "Unable to fork");
|
||
close(client_sock);
|
||
continue;
|
||
case 0:
|
||
close(server_socket);
|
||
handle_client(client_sock);
|
||
close(client_sock);
|
||
exit(0);
|
||
default:
|
||
close(client_sock);
|
||
}
|
||
}
|
||
|
||
close(server_socket);
|
||
syslog(LOG_INFO, "Server stopped");
|
||
closelog();
|
||
return 0;
|
||
}
|