1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/sogou-workflow

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
tutorial-09-http_file_server.md 9.4 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 01.03.2025 10:20 552780a

Асинхронный HTTP сервер: http_file_server

Пример кода

tutorial-09-http_file_server.cc

Описание http_file_server

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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/sogou-workflow.git
git@api.gitlife.ru:oschina-mirror/sogou-workflow.git
oschina-mirror
sogou-workflow
sogou-workflow
master