Книга: Программирование для Linux. Профессиональный подход

Листинг 11.4. (server.c) Реализация HTTP-сервера

Листинг 11.4. (server.c) Реализация HTTP-сервера

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "server.h"
/* HTTP-ответ и заголовок, возвращаемые в случае
   успешной обработки запроса. */
static char* ok_response =
 "HTTP/1.0 100 OKn"
 "Content-type: text/htmln"
 "n";
/* HTTP-ответ, заголовок и тело страницы на случай
   непонятного запроса. */
static char* bad_request_response =
 "HTTP/1.0 400 Bad Reguestn"
 "Content-type: text/htmln"
 "n"
 "<html>n"
 " <body>n"
 "  <h1>Bad Request</h1>n"
 "  <p>This server did not understand your request.</p>n"
 " </body>n"
 "</html>n";
/* HTTP-ответ, заголовок и шаблон страницы на случай,
   когда запрашиваемый документ не найден. */
static char* not_found_response_template =
 "HTTP/1.0 404 Not Foundn"
 "Content-type: text/htmln"
 "n"
 "<html>n"
 " <body>n"
 "  <h1>Not Found</h1>n"
 "  <p>The requested URL %s was not found on this server.</p>n"
 " </body>n"
 "</html>n";
/* HTTP-ответ, заголовок к шаблон страницы на случай,
   когда запрашивается непонятный метод */
static char* bad_method_response_template =
 "HTTP/1.0 501 Method Not Implementedn"
 "Content-type: text/htmln"
 "n"
 "<html>n"
 " <body>n"
 "  <h1>Method Not Implemented</h1>n"
 "  <p>The method %s is not implemented by this server.</p>n"
 " </body>n"
 "</html>n";
/* Обработчик сигнала SIGCHLD, удаляющий завершившиеся
   дочерние процессы. */
static void clean_up_child_process(int signal_number) {
 int status;
 wait(&status);
}
/* Обработка HTTP-запроса "GET" к странице PAGE и
   запись результата в файл с дескриптором CONNECTION_FD. */
static void handle_get(int connection_fd, const char* page) {
 struct server_module* module = NULL;
 /* Убеждаемся, что имя страницы начинается с косой черты и
    не содержит других символов косой черты, так как
    подкаталоги не поддерживаются. */
 if (*page == '/' && strchr(page + 1, '/') == NULL) {
  char module_file_name[64];
  /* Имя страницы правильно. Формируем имя модуля, добавляя
     расширение ".so" к имени страницы. */
  snprintf(module_file_name, sizeof(module_file_name),
   "%s.so", page + 1);
  /* Попытка открытия модуля. */
  module = module_open(module_file_name);
 }
 if (module == NULL) {
  /* Имя страницы неправильно сформировано или не удалось
     открыть модуль с указанным именем. В любом случае
     возвращается HTTP-ответ "404. Not Found". */
  char response[1024];
  /* Формирование ответного сообщения. */
  snprintf(response, sizeof(response),
   not_found_response_template, page);
  /* Отправка его клиенту. */
  write(connection_fd, response, strlen(response));
 } else {
  /* Запрашиваемый модуль успешно загружен. */
  /* Выдача HTTP-ответа, обозначающего успешную обработку
     запроса, и HTTP-заголовка для HTML-страницы. */
  write(connection_fd, ok_response, strlen(ok_response));
  /* Вызов модуля, генерирующего HTML-код страницы и
     записывающего этот код в указанный файл. */
  (*module->generate_function)(connection_fd);
  /* Работа с модулем окончена. */
  module_close(module);
 }
}
/* Обработка клиентского запроса на подключение. */
static void handle_connection(int connection_fd) {
 char buffer[256];
 ssize_t bytes_read;
 /* Получение данных от клиента. */
 bytes_read =
  read(connection_fd, buffer, sizeof(buffer) — 1);
 if (bytes_read > 0) {
  char method[sizeof(buffer)];
  char url[sizeof(buffer)];
  char protocol[sizeof(buffer)];
  /* Часть данных успешно прочитана. Завершаем буфер
     нулевым символом, чтобы его можно было использовать
     в строковых операциях. */
  buffer[bytes_read] = '';
  /* Первая строка, посылаемая клиентом, -- это HTTP-запрос.
     В запросе указаны метод, запрашиваемая страница и
     версия протокола. */
  sscanf(buffer, "%s %s %s", method, url, protocol);
  /* В заголовке, стоящем после запроса, может находиться
     любая информация. В данной реализации HTTP-сервера
     эта информация не учитывается. Тем не менее необходимо
     прочитать все данные, посылаемые клиентом. Данные читаются
     до тех пор, пока не встретится конец заголовка,
     обозначаемый пустой строкой. В HTTP пустой строке
     соответствуют символы CR/LF. */
  while (strstr(buffer, " rnrn") == NULL)
   bytes_read = read(connection_fd, buffer, sizeof(buffer));
  /* Проверка правильности последней операции чтения.
     Если она не завершилась успешно, произошел разрыв
     соединения, поэтому завершаем работу. */
  if (bytes_read == -1) {
   close(connection_fd);
   return;
  }
  /* Проверка поля версии. Сервер понимает протокол HTTP
     версий 1.0 и 1.1. */
  if (strcmp(protocol, "HTTP/1.0") &&
   strcmp(protocol, "HTTP/1.1")) {
   /* Протокол не поддерживается. */
   write(connection_fd, bad_request_response,
    sizeof(bad_request_response));
  } else if (strcmp (method, "GET")) {
   /* Сервер реализует только метод GET, а клиент указал
      другой метод. */
   char response[1024];
   snprintf(response, sizeof(response),
    bad_method_response_template, method);
   write(connection_fd, response, strlen(response));
  } else
   /* Корректный запрос. Обрабатываем его. */
   handle_get(connection_fd, url);
 } else if (bytes_read == 0)
  /* Клиент разорвал соединение, не успев отправить данные.
     Ничего не предпринимаем */
  ;
 else
  /* Операция чтения завершилась ошибкой. */
  system_error("read");
}
void server_run(struct in_addr local_address, uint16_t port) {
 struct sockaddr_in socket_address;
 int rval;
 struct sigaction sigchld_action;
 int server_socket;
 /* Устанавливаем обработчик сигнала SIGCHLD, который будет
    удалять завершившееся дочерние процессы. */
 memset(&sigchld_action, 0, sizeof(sigchld_action));
 sigchld_action.sa_handler = &clean_up_child_process;
 sigaction(SIGCHLD, &sigchld_action, NULL);
 /* Создание TCP-сокета */
 server_socket = socket(PF_INET, SOCK_STREAM, 0);
 if (server_socket == -1) system_error("socket");
 /* Создание адресной структуры, определяющей адрес
    для приема запросов. */
 memset(&socket_address, 0, sizeof(socket_address));
 socket_address.sin_family = AF_INET;
 socket_address.sin_port = port;
 socket_address.sin_addr = local_address;
 /* Привязка сокета к этому адресу. */
 rval =
  bind(server_socket, &socket_address,
  sizeof(socket_address));
 if (rval != 0)
  system_error("bind");
 /* Перевод сокета в режим приема запросов. */
 rval = listen(server_socket, 10);
 if (rval != 0)
  system_error("listen");
 if (verbose) {
  /* В режиме развернутых сообщений отображаем адрес и порт,
     с которыми работает сервер. */
  socklen_t address_length;
  /* Нахождение адреса сокета. */
  address_length = sizeof(socket_address);
  rval =
   getsockname(server_socket, &socket_address, &address_length);
  assert(rval == 0);
  /* Вывод сообщения. Номер порта должен быть преобразован
     из сетевого (обратного) порядка следования байтов
     в серверный (прямой). */
  printf("server listening on %s:%dn",
   inet_ntoa(socket_address.sin_addr),
   (int)ntohs(socket_address.sin_port));
  }
  /* Бесконечный цикл обработки запросов. */
  while (1) {
   struct sockaddr_in remote_address;
   socklen_t address_length;
   int connection;
   pid_t child_pid;
  /* Прием запроса. Эта функция блокируется до тех пор, пока
     не поступит запрос. */
  address_length = sizeof(remote_address);
  connection = accept(server_socket, &remote_address,
   &address_length);
  if (connection == -1) {
   /* Функция завершилась неудачно. */
   if (errno == EINTR)
    /* Функция была прервана сигналом. Повторная попытка. */
    continue;
   else
    /* Что-то случилось. */
    system_error("accept");
  }
  /* Соединение установлено. Вывод сообщения, если сервер
     работает в режиме развернутых сообщений. */
  if (verbose) {
   socklen_t address_length;
   /* Получение адреса клиента. */
   address_length = sizeof(socket_address);
   rval =
    getpeername(connection, &socket_address, &address_length);
   assert(rval == 0);
   /* Вывод сообщения. */
   printf("connection accepted from %sn",
   inet_ntoa(socket_address.sin_addr));
  }
  /* Создание дочернего процесса для обработки запроса. */
  child_pid = fork();
  if (child_pid == 0) {
   /* Это дочерний процесс. Потоки stdin и stdout ему не нужны,
      поэтому закрываем их. */
   close(STDIN_FILENO);
   close(STDOUT_FILENO);
   /* Дочерний процесс не должен работать с серверным сокетом,
      поэтому закрываем его дескриптор. */
   close(server_socket);
   /* Обработка запроса. */
   handle_connection(connection);
   /* Обработка завершена. Закрываем соединение и завершаем
      дочерний процесс. */
   close(connection);
   exit(0);
  } else if (child_pid > 0) {
   /* Это родительский процесс. Дескриптор клиентского сокета
      ему не нужен. Переход к приему следующего запроса. */
   close(connection);
  } else
   /* Вызов функции fork() завершился неудачей. */
   system_error("fork");
 }
}

В файле server.c определены следующие функции.

? Функция server_run() является телом сервера. Она запускает сервер и начинает принимать запросы на подключение, не завершаясь до тех пор, пока не произойдет серьезная ошибка. Сервер создает потоковый TCP-сокет (см. раздел 5.5.3, "Серверы").

Первый аргумент функции server_run определяет локальный адрес, по которому принимаются запросы. У компьютера может быть несколько адресов, каждый из которых соответствует определённому сетевому интерфейсу.[37] Данный аргумент ограничивает работу сервера конкретным интерфейсом или разрешает принимать запросы отовсюду, если равен INADDR_ANY.

Второй аргумент функции server_run() — это номер порта сервера. Если порт уже используется или является привилегированным, работа сервера завершится. Когда номер порта задан равным нулю. ОС Linux автоматически выберет неиспользуемый порт.

Для обработки каждого клиентского запроса сервер создает дочерний процесс с помощью функции fork() (см. раздел 3.2.2. "Функции fork() и exec()"), в то время как родительский процесс продолжает принимать новые запросы. Дочерний процесс вызывает функцию handle_connection(), после чего закрывает соединение и завершается.

? Функция handle_connection() обрабатывает отдельный клиентский запрос, принимая в качестве аргумента дескриптор сокета. Функция читает данные из сокета и пытается интерпретировать их как HTTP-запрос на получение страницы.

Сервер обрабатывает только запросы протокола HTTP версий 1.0 и 1.1. Столкнувшись с иными протоколом или версией сервер возвращает HTTP-код 400 и сообщение bad_request_response. Сервер понимает только HTTP-метод GET. Если клиент запрашивает какой-то другой метод, сервер возвращает HTTP-код 501 и сообщение bad_method_response_template.

? Если клиент послал правильно сформированный запрос GET, функция handle_connection() вызывает функцию handle_get(), которая обрабатывает запрос. Эта функция пытается загрузить серверный модуль, имя которого генерируется на основании имени запрашиваемой страницы. Например, когда клиент запрашивает страницу с именем "information", делается попытка загрузить модуль information.so. Если модуль не может быть загружен, функция handle_get() возвращает HTTP-код 404 и сообщение not_found_response_template.

В случае обращения к верной странице функция handle_get() возвращает клиенту HTTP-код 200, указывающий на успешную обработку запроса, и вызывает функцию module_generate(), содержащуюся в модуле. Последняя генерирует HTML-код Web-страницы и посылает его клиенту.

? Функция server_run() регистрирует функцию clean_up_child_process() в качестве обработчика сигнала SIGCHLD. Обработчик просто очищает ресурсы завершившегося дочернего процесса (см. раздел 3.4.4. "Асинхронное удаление дочерних процессов").

Оглавление книги


Генерация: 1.218. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз