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