Модуль веб-сервера
Основной рабочий процесс stappler_webserver_httpd
основан на модуле, загружаемом в веб-сервер Apache HTTPD. Модуль загружается с помощью конфигурации сервера:
LoadModule stappler_web_module <путь к модулю>/libstappler_web_module.so
Пример examples/web
использует базовый шаблон конфигурации. См. документацию Apache HTTPD для подробностей. Пример предполагает, что вебсервер собран с модулями в статическом режиме --enable-mods-static=most
.
StartServers 2
DirectoryIndex index.html
LogLevel warn
LogFormat "%h %l %u %t \"%r\" %>s %b" common
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5
SSLProtocol all -SSLv2 -SSLv3
SSLPassPhraseDialog builtin
SSLSessionCache "shmcb:/home/httpd/server/logs/ssl_scache(512000)"
SSLSessionCacheTimeout 300
# Загрузка основного модуля
LoadModule stappler_web_module $(abspath $(BUILD_SHARED_LIBRARY))
# Минимальное и максимальное число потоков для асинхронных задач (меньше - удобно для отладки)
StapplerRootThreadsCount 1 2
ServerRoot "$(LOCAL_DIR)"
ErrorLog "logs/error_log"
CustomLog "logs/access_log" common
# Путь к скомпилированным компонентам
StapplerSourceRoot "$(LOCAL_OUTDIR)/host"
Include $(LOCAL_DIR)/conf/httpd.conf
Реальная конфигурация зависит от нужд конечного приложения. Возможно, для установки будет использоваться средство apxs
из поставки веб-сервера.
Модуль веб-сервера добавляет следующие параметры конфигурации:
# Путь, по которому система будет искать библиотеки компонентов, задаётся на верхнем уровне
StapplerSourceRoot <Путь к директории>
# Загружает компонент из модуля (для сервера)
StapplerComponent "<Имя функции загрузки>" [параметр=значение]
# Загружает компонент из динамической библиотеки (для сервера)
StapplerComponent "<Имя файла>:<Имя функции загрузки>:<Имя компонента>" [параметр=значение]
# Загружает компонент WebAssembly. Имя функции передаётся в формает WIT (например: stappler:wasm/app#make-component)
StapplerWasmComponent "<путь к файлу>" "<функция для вызова>"
# Параметры сеанса в cookies
StapplerSession name=<Имя переменной> key=<Хэш-ключ> maxage=<срок в секундах> secure=<флаг защиты>
# Параметры подключения к БД. Переменные передаются напрямую в загрузку драйвера (см. модуль БД SDK)
StapplerDbParams driver=<имя драйвера> <параметр>=<значение>
# Уникальный ключ сервера, используется для целей криптографической защиты
# Мы используем для генерации `base64 < /dev/urandom | tr -d 'O0Il1+/' | head -c 44; printf '\n'`
StapplerHostSecret NX68nXCaeH6srdevZN5PCcgwgeeAp73ppdLKvNmyKcac
Модуль может использовать как собственное подключение к БД, так и подключение, созданное mod_dbd
.
Компоненты
Для работы создаются компоненты веб-сервера web::HostComponent
.
Пример компонента:
class TestComponent : public HostComponent {
public:
TestComponent(const Host &serv, const HostComponentInfo &info);
virtual ~TestComponent() { }
virtual void handleChildInit(const Host &) override;
virtual void initTransaction(db::Transaction &) override;
protected:
Scheme _objects = Scheme("objects");
};
TestComponent::TestComponent(const Host &serv, const HostComponentInfo &info)
: HostComponent(serv, info) {
// Экспортирует схему данных
exportValues(_objects);
using namespace db;
_objects.define(Vector<Field>{
Field::Text("text", MinLength(3)),
Field::Extra("data", Vector<Field>{
Field::Array("strings", Field::Text("")),
}),
Field::File("image", MaxFileSize(1_MiB)),
Field::Text("alias", Transform::Alias),
Field::Integer("mtime", Flags::AutoMTime | Flags::Indexed),
Field::Integer("index", Flags::Indexed),
},
AccessRole::Admin(AccessRoleId::Authorized));
}
void TestComponent::handleChildInit(const Host &serv) {
// Добавляет автоматический ресурс для схемы данных
serv.addResourceHandler("/objects/", _objects);
// Добавляет команду для консоли администратора
addOutputCommand("test", [&] (StringView str, const Callback<void(const Value &)> &cb) -> bool {
if (auto t = db::Transaction::acquireIfExists()) {
cb(Value("test"));
}
return true;
}, " - test");
}
void TestComponent::initTransaction(db::Transaction &t) {
// Функция создания новой транзакции к БД
t.setRole(db::AccessRoleId::Authorized);
}
// Экспортируемая точка входа
SP_EXTERN_C HostComponent * CreateTestComponent(const Host &serv, const HostComponentInfo &info) {
return new TestComponent(serv, info);
}
Компоннент экспортирует схемы данных для хоста и определяет карту доступа к ресурсам и обработчикам запросов.
Обработчики запросов
Пример простого обработчика запроса:
class TestRequestHandler : public RequestHandler {
public:
virtual bool isRequestPermitted(Request & rctx) override {
return true;
}
virtual Status onTranslateName(Request &rctx) override {
return HTTP_NOT_FOUND;
}
};
// Для добавления в handleChildInit компонента
serv.addHandler("/test", RequestHandler::Make<TestRequestHandler>());
Тип RequestHandler
предоставляет основные точки перегрузки:
onRequestRecieved
- получение нового запроса. Обязательно нужно вызвать родительскую функци ине игнорировать её результатonPostReadRequest
- обработка прочитанного заголовка запросаonTranslateName
- обработка доступа по пути запросаonQuickHandler
- попытка быстрой обработки запросаonInsertFilter
- добавление фильтраonHandler
- окончательная обработка запросаonFilterInit
- начало работы фильтраonFilterUpdate
- обновление фильтраonFilterComplete
- завершение работы фильтра
В качестве основного типа возврата используется Status
(аналогично Apache HTTPD). Статусы HTTP_*
означают завершение обработки запроса с соотвествующим кодом. Статус OK
означает, что запрос обработан на текущей фазе. Статус DDNE
означает, что обработка запроса закончена, и необходимо вернуть окончательный ответ, код ответа вычисляется и контекста. DECLINED
- обработчик отказывается от обработки.
Для обработки запроса с содержимым нужно добавить фильтр на фазе onInsertFilter
. Если фильтр добавлен, запрос должен продолжать обработку после onHandler
. Встроенный фильтр (web::InputFilter
, SPWebInputFilter.h
) распознает форматы JSON, CBOR, передачу файлов и стандартные форматы форм веб-сайта.
Тип DataHandler
- упрощённый интерфейс для работы REST интерфейсов. Он позволяет обрабатывать GET и POST запросы, перегружая функцию processDataHandler
.
Тип RequestHandlerMap
позволяет декларативно выстраивать сложную карту запросов с параметрами, например:
class TestHandlerMap : public RequestHandlerMap {
public:
TestHandlerMap() {
addHandler("Variant1", RequestMethod::Get, "/:id/:page", Handler::Make<TestHandlerMapVariant1>())
.addQueryFields({
db::Field::Integer("intValue", db::Flags::Required),
db::Field::Integer("mtime", db::Flags::AutoMTime),
});
addHandler("Variant1Post", RequestMethod::Post, "/:id/:page", Handler::Make<TestHandlerMapVariant1>())
.addQueryFields({
db::Field::Integer("intValue", db::Flags::Required),
db::Field::Integer("mtime", db::Flags::AutoMTime),
}).addInputFields({
db::Field::Text("text", db::Flags::Required),
db::Field::File("file", db::MaxFileSize(2_MiB)),
db::Field::Data("data"),
}).setInputConfig(db::InputConfig{
db::InputConfig::Require::Body | db::InputConfig::Require::Data | db::InputConfig::Require::Files | db::InputConfig::Require::FilesAsData,
2_MiB,
2_MiB,
2_MiB,
});
addHandler("Variant2Post", RequestMethod::Post, "/urlencoded", Handler::Make<TestHandlerMapVariant2>())
.addInputFields({
db::Field::Text("text", db::Flags::Required),
db::Field::Data("dict"),
db::Field::Data("arr"),
db::Field::Data("array space"),
}).setInputConfig(db::InputConfig{
db::InputConfig::Require::Data,
2_MiB,
2_MiB,
2_MiB,
});
addHandler("Variant3Post", RequestMethod::Post, "/files", Handler::Make<TestHandlerMapVariant3>())
.setInputConfig(db::InputConfig{
db::InputConfig::Require::Files | db::InputConfig::Require::Body,
2_MiB,
2_MiB,
2_MiB,
});
}
};
Вебсокеты
Для работы с вебсокетами необходимо определить класс, наследуемый от WebsocketManager
, который будет создавать наследников WebsocketHandler
.
В классе WebsocketManager
необходимо перегрузить функцию onAccept
для принятия соединения.
В классе WebsocketHandler
перегружается функция handleFrame
для обработки входящих данных.