From 895958d0585b7bac946fa3dce49499c54daee786 Mon Sep 17 00:00:00 2001 From: tish Date: Sun, 2 Nov 2025 13:37:19 +0100 Subject: [PATCH] lab4 webserver --- lab4/.gitignore | 2 + lab4/Makefile | 12 +++ lab4/daemon.c | 49 +++++++++ lab4/daemon.h | 6 ++ lab4/main.c | 56 ++++++++++ lab4/server.c | 209 ++++++++++++++++++++++++++++++++++++++ lab4/server.h | 7 ++ lab4/test-site/data.txt | 17 ++++ lab4/test-site/index.html | 38 +++++++ lab4/test-site/logo.png | Bin 0 -> 3831 bytes lab4/test-site/style.css | 35 +++++++ lab4/test-site/test.html | 39 +++++++ 12 files changed, 470 insertions(+) create mode 100644 lab4/.gitignore create mode 100644 lab4/Makefile create mode 100644 lab4/daemon.c create mode 100644 lab4/daemon.h create mode 100644 lab4/main.c create mode 100644 lab4/server.c create mode 100644 lab4/server.h create mode 100644 lab4/test-site/data.txt create mode 100644 lab4/test-site/index.html create mode 100644 lab4/test-site/logo.png create mode 100644 lab4/test-site/style.css create mode 100644 lab4/test-site/test.html diff --git a/lab4/.gitignore b/lab4/.gitignore new file mode 100644 index 0000000..60b1c48 --- /dev/null +++ b/lab4/.gitignore @@ -0,0 +1,2 @@ +*.o +webserver \ No newline at end of file diff --git a/lab4/Makefile b/lab4/Makefile new file mode 100644 index 0000000..d94037c --- /dev/null +++ b/lab4/Makefile @@ -0,0 +1,12 @@ +CC = gcc +CFLAGS = -Wall -Wextra -O2 +OBJ = main.o server.o daemon.o + +webserver: $(OBJ) + $(CC) $(CFLAGS) -o $@ $(OBJ) + +%.o: %.c + $(CC) $(CFLAGS) -c $< + +clean: + rm -f *.o webserver diff --git a/lab4/daemon.c b/lab4/daemon.c new file mode 100644 index 0000000..c55c09c --- /dev/null +++ b/lab4/daemon.c @@ -0,0 +1,49 @@ +/* Вспомогательные функции для демонизации вебсервера */ + +#include +#include +#include +#include +#include +#include +#include + +pid_t pid, sid; + +void daemonize() { + syslog(LOG_DEBUG, " --- STARTING DAEMON --- "); + + /* Закрываем стандартные файловые дескприторы */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + pid = fork(); + + if (pid < 0) { + syslog(LOG_CRIT, "Unable to fork process!"); + exit(EXIT_FAILURE); + } + + /* Если fork получился, то родительский процесс можно завершить */ + if (pid > 0) { + syslog(LOG_DEBUG, "Killed parent process"); + exit(EXIT_SUCCESS); + } + + umask(0); + + /* Sid для дочернего процесса */ + sid = setsid(); + + if (sid < 0) { + syslog(LOG_CRIT, "Unable to set session id"); + exit(EXIT_FAILURE); + } + + /* Изменяем текущий рабочий каталог */ + if ((chdir("/")) < 0) { + syslog(LOG_CRIT, "Unable to change working directory"); + exit(EXIT_FAILURE); + } +} diff --git a/lab4/daemon.h b/lab4/daemon.h new file mode 100644 index 0000000..55642ab --- /dev/null +++ b/lab4/daemon.h @@ -0,0 +1,6 @@ +#ifndef DAEMON_H +#define DAEMON_H + +void daemonize(); + +#endif \ No newline at end of file diff --git a/lab4/main.c b/lab4/main.c new file mode 100644 index 0000000..1892fed --- /dev/null +++ b/lab4/main.c @@ -0,0 +1,56 @@ +/* + * Простой многопроцессный HTTP сервер с использованием fork() + * + * Компиляция: make + * + * Запуск: ./webserver <путь_к_папке> [порт] + * Примеры: + * ./webserver ./test-site + * ./webserver ./test-site 8080 + * + * Остановка: pkill webserver + * + * Просмотр логов: + * sudo journalctl -f -t MY-WEB-SERVER + * sudo journalctl -f -t MY-WEB-SERVER --since "now" -p info + * + * Просмотр процессов: + * pstree -p $(pgrep -o webserver) + * ps aux | grep webserver + */ + +#include "server.h" +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: %s [port]\n", argv[0]); + fprintf(stderr, "Example: %s ./test-site 13131\n", argv[0]); + return 1; + } + + /* Преобразуем путь в абсолютный */ + char abs_path[PATH_MAX]; + if (realpath(argv[1], abs_path) == NULL) { + perror("Invalid path"); + return 1; + } + + int port = 13131; + + if (argc > 2) { + port = atoi(argv[2]); + if (port <= 0 || port > 65535) { + fprintf(stderr, "Invalid port: %s\n", argv[2]); + return 1; + } + } + + printf("Starting web server on port %d, serving %s...\n", port, abs_path); + + server(abs_path, port); + + return 0; +} diff --git a/lab4/server.c b/lab4/server.c new file mode 100644 index 0000000..dc2e596 --- /dev/null +++ b/lab4/server.c @@ -0,0 +1,209 @@ +/* +Реализовать Web-Server использующий многопроцессный +способ обработки клиентов через fork(). Веб-сервер должен +работать в виде демона и обеспечивать базовую поддержку +протокола HTTP, на уровне метода GET (по желанию методы +HEAD и POST). Проверку Web-Server’а необходимо +осуществлять на статических текстовых и двоичных данных, +таких как изображения. Предусмотреть контроль и +журналирование ошибок (либо в файл, либо через syslog). +Обеспечить одновременную работу сервера с множественным +числом клиентов. +*/ + +#include "daemon.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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" + "

404 Not Found

"; + 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; +} diff --git a/lab4/server.h b/lab4/server.h new file mode 100644 index 0000000..5a85881 --- /dev/null +++ b/lab4/server.h @@ -0,0 +1,7 @@ +#ifndef SERVER_H +#define SERVER_H + +int server(const char *www_root, int port); + +#endif + diff --git a/lab4/test-site/data.txt b/lab4/test-site/data.txt new file mode 100644 index 0000000..922f8a5 --- /dev/null +++ b/lab4/test-site/data.txt @@ -0,0 +1,17 @@ +Это тестовый текстовый файл. + +Сервер должен корректно отдавать как текстовые, так и бинарные файлы. + +Особенности реализации: +- Многопроцессная архитектура через fork() +- Демонизация процесса +- Поддержка HTTP/1.0 GET запросов +- Журналирование через syslog +- Обработка ошибок (404, 501) + +Строки текста для проверки корректности передачи: +1234567890 +ABCDEFGHIJKLMNOPQRSTUVWXYZ +абвгдежзийклмнопрстуфхцчшщъыьэюя +Special chars: !@#$%^&*()_+-=[]{}|;':",.<>?/ + diff --git a/lab4/test-site/index.html b/lab4/test-site/index.html new file mode 100644 index 0000000..2555018 --- /dev/null +++ b/lab4/test-site/index.html @@ -0,0 +1,38 @@ + + + + + + + Тестовый веб-сервер + + + + +
+

🚀 Веб-сервер работает!

+

Это тестовая страница простого HTTP сервера на C с использованием fork().

+ +

Особенности:

+
    +
  • Многопроцессная обработка клиентов
  • +
  • Работа в режиме демона
  • +
  • Поддержка HTTP GET
  • +
  • Статические файлы (HTML, текст, изображения)
  • +
  • Журналирование через syslog
  • +
+ +

Тестовые ссылки:

+ + +

Изображение:

+ Test Image +
+ + + \ No newline at end of file diff --git a/lab4/test-site/logo.png b/lab4/test-site/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4deba33158459e240347983f3ddf6f91cc548505 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/lab4/test-site/style.css b/lab4/test-site/style.css new file mode 100644 index 0000000..04142d9 --- /dev/null +++ b/lab4/test-site/style.css @@ -0,0 +1,35 @@ +body { + font-family: Arial, sans-serif; + max-width: 800px; + margin: 50px auto; + padding: 20px; + background-color: #f5f5f5; +} + +h1 { + color: #333; +} + +.content { + background: white; + padding: 30px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +a { + color: #1a73e8; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +ul { + line-height: 1.8; +} + +img { + margin: 20px 0; +} \ No newline at end of file diff --git a/lab4/test-site/test.html b/lab4/test-site/test.html new file mode 100644 index 0000000..d5bb0bd --- /dev/null +++ b/lab4/test-site/test.html @@ -0,0 +1,39 @@ + + + + + + Тестовая страница + + + + +
+

✨ Тестовая страница

+

Если вы видите эту страницу, значит сервер корректно обрабатывает HTTP запросы!

+

Сервер работает на порту 13131.

+

← Вернуться на главную

+
+ + + \ No newline at end of file