lab4 webserver
This commit is contained in:
2
lab4/.gitignore
vendored
Normal file
2
lab4/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.o
|
||||||
|
webserver
|
||||||
12
lab4/Makefile
Normal file
12
lab4/Makefile
Normal file
@@ -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
|
||||||
49
lab4/daemon.c
Normal file
49
lab4/daemon.c
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/* Вспомогательные функции для демонизации вебсервера */
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
lab4/daemon.h
Normal file
6
lab4/daemon.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef DAEMON_H
|
||||||
|
#define DAEMON_H
|
||||||
|
|
||||||
|
void daemonize();
|
||||||
|
|
||||||
|
#endif
|
||||||
56
lab4/main.c
Normal file
56
lab4/main.c
Normal file
@@ -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 <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s <www_root> [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;
|
||||||
|
}
|
||||||
209
lab4/server.c
Normal file
209
lab4/server.c
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
Реализовать 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;
|
||||||
|
}
|
||||||
7
lab4/server.h
Normal file
7
lab4/server.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef SERVER_H
|
||||||
|
#define SERVER_H
|
||||||
|
|
||||||
|
int server(const char *www_root, int port);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
17
lab4/test-site/data.txt
Normal file
17
lab4/test-site/data.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Это тестовый текстовый файл.
|
||||||
|
|
||||||
|
Сервер должен корректно отдавать как текстовые, так и бинарные файлы.
|
||||||
|
|
||||||
|
Особенности реализации:
|
||||||
|
- Многопроцессная архитектура через fork()
|
||||||
|
- Демонизация процесса
|
||||||
|
- Поддержка HTTP/1.0 GET запросов
|
||||||
|
- Журналирование через syslog
|
||||||
|
- Обработка ошибок (404, 501)
|
||||||
|
|
||||||
|
Строки текста для проверки корректности передачи:
|
||||||
|
1234567890
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
абвгдежзийклмнопрстуфхцчшщъыьэюя
|
||||||
|
Special chars: !@#$%^&*()_+-=[]{}|;':",.<>?/
|
||||||
|
|
||||||
38
lab4/test-site/index.html
Normal file
38
lab4/test-site/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Тестовый веб-сервер</title>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h1>🚀 Веб-сервер работает!</h1>
|
||||||
|
<p>Это тестовая страница простого HTTP сервера на C с использованием fork().</p>
|
||||||
|
|
||||||
|
<h2>Особенности:</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Многопроцессная обработка клиентов</li>
|
||||||
|
<li>Работа в режиме демона</li>
|
||||||
|
<li>Поддержка HTTP GET</li>
|
||||||
|
<li>Статические файлы (HTML, текст, изображения)</li>
|
||||||
|
<li>Журналирование через syslog</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Тестовые ссылки:</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/test.html">Тестовая страница</a></li>
|
||||||
|
<li><a href="/logo.png">Изображение (PNG)</a></li>
|
||||||
|
<li><a href="/data.txt">Текстовый файл</a></li>
|
||||||
|
<li><a href="/style.css">CSS файл</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Изображение:</h2>
|
||||||
|
<img src="/logo.png" alt="Test Image">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
BIN
lab4/test-site/logo.png
Normal file
BIN
lab4/test-site/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
35
lab4/test-site/style.css
Normal file
35
lab4/test-site/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
39
lab4/test-site/test.html
Normal file
39
lab4/test-site/test.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Тестовая страница</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #ffd700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="box">
|
||||||
|
<h1>✨ Тестовая страница</h1>
|
||||||
|
<p>Если вы видите эту страницу, значит сервер корректно обрабатывает HTTP запросы!</p>
|
||||||
|
<p>Сервер работает на порту <strong>13131</strong>.</p>
|
||||||
|
<p><a href="/">← Вернуться на главную</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user