tutorial-09-http_file_server.cc
http_file_server
— это веб-сервер, который запускается с указанием порта и корневого пути (по умолчанию текущий каталог программы). Сервер можно также запустить как HTTPS-сервер, если указаны PEM-файлы сертификата и ключа.
При запуске сервера программа может принимать команды от пользователя через командную строку и доступна по адресу 127.0.0.1
. Программа демонстрирует использование операций асинхронного чтения с диска. В Linux используется низкоуровной интерфейс AIO для полного асинхронного чтения файлов.
Запуск сервера здесь отличается от эхо-сервера или прокси-HTTP только способом запуска SSL-сервера:
class WFServerBase
{
...
int start(unsigned short port, const char *cert_file, const char *key_file);
...
};
Метод start()
позволяет указывать PEM-файлы сертификата и ключа для запуска SSL-сервера. При определении сервера используется std::bind()
для привязки процесса к корневому пути:
void process(WFHttpTask *server_task, const char *root)
{
...
}
int main(int argc, char *argv[])
{
...
const char *root = (argc >= 3 ? argv[2] : ".");
auto&& proc = std::bind(process, std::placeholders::_1, root);
WFHttpServer server(proc);
// start server
...
}
Аналогично прокси-HTTP, мы не используем потоки для чтения файлов, а создаем асинхронные задачи чтения файла. После завершения чтения отправляем ответ:
void process(WFHttpTask *server_task, const char *root)
{
// генерация абсолютного пути
...
int fd = open(abs_path.c_str(), O_RDONLY);
if (fd >= 0)
{
size_t size = lseek(fd, 0, SEEK_END);
void *buf = malloc(size); /* Например, проверяем buf != NULL */
WFFileIOTask *pread_task;
pread_task = WFTaskFactory::create_pread_task(fd, buf, size, 0,
pread_callback);
/* Для реализации более сложного сервера используйте контекст серии вместо данных задачи */
pread_task->user_data = resp; /* передача указателя resp в задачу pread */
server_task->user_data = buf; /* для освобождения памяти в обратном вызове */
server_task->set_callback([](WFHttpTask *t){ free(t->user_data); });
series_of(server_task)->push_back(pread_task);
}
else
{
resp->set_status_code("404");
resp->append_output_body("<html>404 Not Found.</html>");
}
}
Вместо создания нового HTTP-клиентского запроса, мы используем фабрику для создания задачи чтения файла (pread
). В WFTaskFactory.h представлены соответствующие интерфейсы:
struct FileIOArgs
{
int fd;
void *buf;
size_t count;
off_t offset;
};
...
using WFFileIOTask = WFFileTask<struct FileIOArgs>;
using fio_callback_t = std::function<void (WFFileIOTask *)>;
...
class WFTaskFactory
{
public:
...
static WFFileIOTask *create_pread_task(int fd, void *buf, size_t count, off_t offset,
fio_callback_t callback);
static WFFileIOTask *create_pwrite_task(int fd, void *buf, size_t count, off_t offset,
fio_callback_t callback);
...
/* Интерфейс с использованием имени файла */
static WFFileIOTask *create_pread_task(const std::string& path, void *buf, size_t count, off_t offset,
fio_callback_t callback);
static WFFileIOTask *create_pwrite_task(const std::string& path, void *buf, size_t count, off_t offset,
fio_callback_t callback);
};
Как pread
, так и pwrite
возвращают объект типа WFFileIOTask
. Также существуют методы preadv
и pwritev
, которые возвращают WFFileVIOTask
, а также fsync
и fdsync
, которые возвращают WFFileSyncTask
.
Пример использует поле user_data
задачи для хранения глобальных данных сервера. Однако для больших приложений рекомендуется использовать контекст серии. Это можно увидеть в примере прокси.
using namespace protocol;
void pread_callback(WFFileIOTask *task)
{
FileIOArgs *args = task->get_args();
long ret = task->get_retval();
HttpResponse *resp = (HttpResponse *)task->user_data;
/* закрывайте fd только тогда, когда вы создали задачу с помощью **fd** интерфейса. */
close(args->fd);
if (ret < 0)
{
resp->set_status_code("503");
resp->append_output_body("<html>503 Internal Server Error.</html>");
}
else /* Используйте '_nocopy' осторожно. */
resp->append_output_body_nocopy(args->buf, ret);
}
Функция get_args()
получает входные параметры, которые являются структурой FileIOArgs
. Поле fd
равно -1, если задача была создана с использованием имени файла.
Функция get_retval()
возвращает значение операции. Когда ret < 0
, задача ошибочна. В противном случае ret
представляет размер прочитанных данных.
Поле buf
является управляемым программой, его можно передать в resp
с помощью append_output_body_nocopy()
. После завершения ответа, память будет освобождена с помощью следующего кода:
server_task->set_callback([](WFHttpTask *t){ free(t->user_data); });
После запуска сервера, пользователи могут вводить имена файлов для доступа к серверу. Когда ввод пуст (Ctrl+D), сервер закрывается и программа завершается.
Для реализации циклического приема ввода используется WFRepeaterTask
. Этот тип задачи представляет собой цикл, который повторяет выполнение задачи до тех пор, пока функция создания задачи не вернет null:
using repeated_create_t = std::function<SubTask *(WFRepeaterTask *)>;
using repeater_callback_t = std::function<void (WFRpeaterTask *)>;
class WFTaskFactory
{
WFRpeaterTask *create_repeater_task(repeated_create_t create, repeater_callback_t callback);
};
Функция create
создает задачу повторителя. Цикл внутри повторителя продолжается до тех пор, пока create
не вернет null.
В данном примере функция create
использует scanf
для получения ввода пользователя. Когда ввод пуст, create
возвращает null, что завершает цикл. Когда ввод не пуст (имя файла), создается задача HTTP для доступа к запущенному серверу.
{
auto&& create = [&scheme, port](WFRepeaterTask *)->SubTask *{
...
scanf("%1023s", buf);
if (*buf == '\0')
return NULL;
std::string url = scheme + "127.0.0.1:" + std::to_string(port) + "/" + buf;
WFHttpTask *task = WFTaskFactory::create_http_task(url, 0, 0,
[](WFHttpTask *task) {
...
});
return task;
};
WFFacilities::WaitGroup wg(1);
WFRepeaterTask *repeater;
repeater = WFTaskFactory::create_repeater_task(create, [&wg](WFRepeaterTask *) {
wg.done();
});
repeater->start();
wg.wait();
server.stop();
}
Когда create
возвращает null, вызывается обратный вызов repeater
, который закрывает сервер и завершает программу.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )