Compare commits
2 Commits
d9967b69e8
...
0b794fac40
Author | SHA1 | Date | |
---|---|---|---|
0b794fac40 | |||
82b433c447 |
@ -38,6 +38,8 @@ add_executable(terminal-web-server
|
|||||||
src/terminal_api_driver.cpp
|
src/terminal_api_driver.cpp
|
||||||
src/auth/resources.cpp
|
src/auth/resources.cpp
|
||||||
src/auth/resources.h
|
src/auth/resources.h
|
||||||
|
src/auth/jwt.cpp
|
||||||
|
src/auth/jwt.h
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(Boost 1.53.0 COMPONENTS system thread filesystem log log_setup REQUIRED)
|
find_package(Boost 1.53.0 COMPONENTS system thread filesystem log log_setup REQUIRED)
|
||||||
|
5
src/auth/jwt.cpp
Normal file
5
src/auth/jwt.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//
|
||||||
|
// Created by vlad on 04.11.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "jwt.h"
|
23
src/auth/jwt.h
Normal file
23
src/auth/jwt.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef JWT_H
|
||||||
|
#define JWT_H
|
||||||
|
#include <string>
|
||||||
|
#include "resources.h"
|
||||||
|
|
||||||
|
namespace http::auth::jwt {
|
||||||
|
class Jwt {
|
||||||
|
public:
|
||||||
|
static Jwt fromCookies(const std::string& cookie);
|
||||||
|
static Jwt fromString(const std::string& cookie);
|
||||||
|
static Jwt fromUser(const std::string& User);
|
||||||
|
|
||||||
|
bool isValid();
|
||||||
|
|
||||||
|
std::string getUsername();
|
||||||
|
|
||||||
|
std::string asCookie();
|
||||||
|
|
||||||
|
~Jwt();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //JWT_H
|
@ -3,3 +3,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
// http::auth::AuentificationRequiredResource::AuentificationRequiredResource(const std::string &path, AuthProvider& provider, resource::respGenerator generator): BasicResource(path), generator_(std::move(generator)) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void http::auth::AuentificationRequiredResource::handle(const server::Request &req, server::Reply &rep) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// http::auth::AuentificationRequiredResource::~AuentificationRequiredResource() = default;
|
||||||
|
@ -4,25 +4,86 @@
|
|||||||
|
|
||||||
namespace http::auth {
|
namespace http::auth {
|
||||||
/**
|
/**
|
||||||
* Класс пользовательских разрешений,
|
* Класс пользователя, содержит логин/хеш_пароля/настройки пользователя/права.
|
||||||
*/
|
* Хеш пароля представляется в виде строки
|
||||||
class UserPremision{};
|
*/
|
||||||
|
class User {
|
||||||
|
private:
|
||||||
|
uint32_t perms;
|
||||||
|
|
||||||
/**
|
public:
|
||||||
* Класс пользователя, содержит логин/хеш_пароля/настройки пользователя/права
|
const std::string username;
|
||||||
*/
|
std::string passwordHash;
|
||||||
class User{};
|
|
||||||
|
|
||||||
/**
|
User(const std::string& username, const std::string& passwordHash);
|
||||||
* Класс аутентификации. Управляет всеми сессиями, создает новые при логине, удаляет при логауте.
|
|
||||||
* @note Класс устанавливает заголовок 'Set-Cookie' в ответе, и этот заголовок должен дойти до пользователя!
|
|
||||||
*/
|
|
||||||
class AuthProvider {
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить пароль на соответствие хешу
|
||||||
|
* @param pass
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool checkPassword(const std::string& pass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установка пароля
|
||||||
|
* @param pass строка с исходным паролем
|
||||||
|
*/
|
||||||
|
void setPassword(const std::string& pass);
|
||||||
|
|
||||||
|
|
||||||
|
static constexpr uint32_t SUPERUSER = 0x0001;
|
||||||
|
static constexpr uint32_t WATCH_STATISTICS = 0x0002; // мониторинг модема
|
||||||
|
static constexpr uint32_t RESET_PACKET_STATISTICS = 0x0004; // сброс статистики пакетов
|
||||||
|
static constexpr uint32_t WATCH_SETTINGS = 0x0008; // просмотр настроек , если недоступно, то вкладки с настройками не будет
|
||||||
|
static constexpr uint32_t EDIT_SETTINGS = 0x0010; // редактирование настроек, установка параметров модулятора/демодулятора/dma/cinc
|
||||||
|
static constexpr uint32_t UPDATE_FIRMWARE = 0x0020; // обновление прошивки
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить, что у пользователя есть нужное право. Если это суперпользователь, то у него по умолчанию все права есть.
|
||||||
|
* @param p набор прав, из констант данного класса.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool checkPremisions(uint32_t p);
|
||||||
|
|
||||||
|
void setPremisions(uint32_t p);
|
||||||
|
void resetPremisions(uint32_t p);
|
||||||
|
|
||||||
|
~User();
|
||||||
};
|
};
|
||||||
|
|
||||||
class NeedAuentificationResource: public resource::BasicResource {};
|
/**
|
||||||
|
* Класс аутентификации. Управляет всеми сессиями, создает новые при логине, удаляет при логауте.
|
||||||
|
* @note Класс устанавливает заголовок 'Set-Cookie' в ответе, и этот заголовок должен дойти до пользователя!
|
||||||
|
*/
|
||||||
|
class AuthProvider {
|
||||||
|
public:
|
||||||
|
AuthProvider();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Авторизовать пользователя.
|
||||||
|
*
|
||||||
|
* @param rep
|
||||||
|
* @return true, в случае успешной авторизации
|
||||||
|
*/
|
||||||
|
bool doAuth(const std::string& username, const std::string& password, server::Reply& rep);
|
||||||
|
|
||||||
|
std::shared_ptr<User> getSession(server::Request& req);
|
||||||
|
|
||||||
|
~AuthProvider();
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuentificationRequiredResource final: public resource::BasicResource {
|
||||||
|
public:
|
||||||
|
explicit AuentificationRequiredResource(const std::string& path, std::shared_ptr<AuthProvider> provider, resource::respGenerator generator);
|
||||||
|
|
||||||
|
void handle(const server::Request &req, server::Reply &rep) override;
|
||||||
|
|
||||||
|
~AuentificationRequiredResource() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
resource::respGenerator generator_;
|
||||||
|
std::shared_ptr<AuthProvider> provider_;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //RESOURCES_H
|
#endif //RESOURCES_H
|
||||||
|
169
src/main.cpp
169
src/main.cpp
@ -16,6 +16,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include "terminal_api_driver.h"
|
#include "terminal_api_driver.h"
|
||||||
|
#include "auth/resources.h"
|
||||||
|
|
||||||
|
|
||||||
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
|
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
|
||||||
@ -72,70 +73,124 @@ void init_logging() {
|
|||||||
log::add_common_attributes();
|
log::add_common_attributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void initResources(http::server::Server& s, std::shared_ptr<api_driver::ApiDriver>& api) {
|
class ServerResources {
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/main.html", mime_types::text_html));
|
std::unique_ptr<http::resource::StaticFileFactory> sf;
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/login", "static/login.html", mime_types::text_html));
|
std::unique_ptr<api_driver::ApiDriver> api;
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
|
public:
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/images/krokodil_vzryvaetsya_hd.gif", "static/krokodil.gif", mime_types::image_gif));
|
static constexpr const char* INDEX_HTML = "static/main.html";
|
||||||
|
static constexpr const char* LOGIN_HTML = "static/login.html";
|
||||||
|
static constexpr const char* LOGIN_FAILED_HTML = "static/login-failed.html";
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/style.css", "static/style.css", mime_types::text_css));
|
// картинки, их даже можно кешировать
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/fields.css", "static/fields.css", mime_types::text_css));
|
static constexpr const char* FAVICON_ICO = "static/favicon.png";
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));
|
static constexpr const char* KROKODIL_GIF = "static/krokodil.gif";
|
||||||
|
static constexpr const char* VUE_JS = "static/js/vue.js"; // это тоже можно кешировать
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
|
// а эти стили нельзя кешировать в отладочной версии
|
||||||
if (req.method != "GET") {
|
static constexpr const char* STYLE_CSS = "static/style.css";
|
||||||
http::server::stockReply(http::server::bad_request, rep);
|
static constexpr const char* FIELDS_CSS = "static/fields.css";
|
||||||
}
|
|
||||||
|
|
||||||
rep.status = http::server::ok;
|
ServerResources(const ServerResources&) = delete;
|
||||||
rep.headers.clear();
|
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
|
||||||
const char* json = R"({"key":"value"})";
|
|
||||||
rep.content.insert(rep.content.end(), json, json + strlen(json));
|
|
||||||
}));
|
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/statistics", [api](const auto& req, auto& rep) {
|
ServerResources(): sf(std::make_unique<http::resource::StaticFileFactory>()), api(std::make_unique<api_driver::ApiDriver>()) {
|
||||||
if (req.method != "GET") {
|
sf->registerFile(INDEX_HTML, mime_types::text_html, false);
|
||||||
http::server::stockReply(http::server::bad_request, rep);
|
sf->registerFile(LOGIN_HTML, mime_types::text_html, false);
|
||||||
}
|
sf->registerFile(LOGIN_FAILED_HTML, mime_types::text_html, false);
|
||||||
|
|
||||||
rep.status = http::server::ok;
|
sf->registerFile(FAVICON_ICO, mime_types::image_png, true);
|
||||||
rep.headers.clear();
|
sf->registerFile(KROKODIL_GIF, mime_types::image_gif, true);
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
sf->registerFile(VUE_JS, mime_types::javascript, true);
|
||||||
std::string result = R"({"mainState":)";
|
|
||||||
result += api->loadTerminalState();
|
|
||||||
result += "}";
|
|
||||||
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
|
||||||
}));
|
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/resetPacketStatistics", [api](const auto& req, auto& rep) {
|
#if USE_DEBUG
|
||||||
if (req.method != "POST") {
|
constexpr bool allowCacheCss = false;
|
||||||
http::server::stockReply(http::server::bad_request, rep);
|
#else
|
||||||
}
|
constexpr bool allowCacheCss = true;
|
||||||
api->resetPacketStatistics();
|
#endif
|
||||||
|
|
||||||
rep.status = http::server::ok;
|
sf->registerFile(STYLE_CSS, mime_types::text_css, allowCacheCss);
|
||||||
rep.headers.clear();
|
sf->registerFile(FIELDS_CSS, mime_types::text_css, allowCacheCss);
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
}
|
||||||
const std::string result = R"({"status":"ok")";
|
|
||||||
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
|
||||||
}));
|
|
||||||
|
|
||||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/settings", [api](const auto& req, auto& rep) {
|
void registerResources(http::server::Server& s) {
|
||||||
if (req.method != "GET") {
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/", [this](const auto& req, auto& rep) {
|
||||||
http::server::stockReply(http::server::bad_request, rep);
|
boost::ignore_unused(req);
|
||||||
}
|
sf->serve(INDEX_HTML, rep);
|
||||||
|
}));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/login", [this](const auto& req, auto& rep) {
|
||||||
|
if (req.method == "GET") {
|
||||||
|
sf->serve(LOGIN_HTML, rep);
|
||||||
|
} else if (req.method == "POST") {
|
||||||
|
sf->serve(LOGIN_FAILED_HTML, rep);
|
||||||
|
} else {
|
||||||
|
http::server::stockReply(http::server::bad_request, rep);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/favicon.ico", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FAVICON_ICO, rep); }));
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/images/krokodil_vzryvaetsya_hd.gif", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(KROKODIL_GIF, rep); }));
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/style.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(STYLE_CSS, rep); }));
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/fields.css", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(FIELDS_CSS, rep); }));
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/js/vue.js", [this](const auto& req, auto& rep) { boost::ignore_unused(req); sf->serve(VUE_JS, rep); }));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
|
||||||
|
if (req.method != "GET") {
|
||||||
|
http::server::stockReply(http::server::bad_request, rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
rep.status = http::server::ok;
|
||||||
|
rep.headers.clear();
|
||||||
|
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
||||||
|
const char* json = R"({"key":"value"})";
|
||||||
|
rep.content.insert(rep.content.end(), json, json + strlen(json));
|
||||||
|
}));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/statistics", [this](const auto& req, auto& rep) {
|
||||||
|
if (req.method != "GET") {
|
||||||
|
http::server::stockReply(http::server::bad_request, rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
rep.status = http::server::ok;
|
||||||
|
rep.headers.clear();
|
||||||
|
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
||||||
|
std::string result = R"({"mainState":)";
|
||||||
|
result += api->loadTerminalState();
|
||||||
|
result += "}";
|
||||||
|
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
||||||
|
}));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/resetPacketStatistics", [this](const auto& req, auto& rep) {
|
||||||
|
if (req.method != "POST") {
|
||||||
|
http::server::stockReply(http::server::bad_request, rep);
|
||||||
|
}
|
||||||
|
api->resetPacketStatistics();
|
||||||
|
|
||||||
|
rep.status = http::server::ok;
|
||||||
|
rep.headers.clear();
|
||||||
|
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
||||||
|
const std::string result = R"({"status":"ok")";
|
||||||
|
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
||||||
|
}));
|
||||||
|
|
||||||
|
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/get/settings", [this](const auto& req, auto& rep) {
|
||||||
|
if (req.method != "GET") {
|
||||||
|
http::server::stockReply(http::server::bad_request, rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
rep.status = http::server::ok;
|
||||||
|
rep.headers.clear();
|
||||||
|
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
||||||
|
std::string result = R"({"settings":)";
|
||||||
|
result += api->loadSettings();
|
||||||
|
result += "}";
|
||||||
|
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
~ServerResources() = default;
|
||||||
|
};
|
||||||
|
|
||||||
rep.status = http::server::ok;
|
|
||||||
rep.headers.clear();
|
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
|
|
||||||
std::string result = R"({"settings":)";
|
|
||||||
result += api->loadSettings();
|
|
||||||
result += "}";
|
|
||||||
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
try {
|
try {
|
||||||
@ -159,14 +214,14 @@ int main(int argc, char *argv[]) {
|
|||||||
BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0];
|
BOOST_LOG_TRIVIAL(info) << "Starting RELEASE build" << argv[0];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto api = std::make_shared<api_driver::ApiDriver>();
|
ServerResources resources;
|
||||||
|
|
||||||
// Initialise the server.
|
// Initialise the server.
|
||||||
std::unique_ptr<http::server::Server> s;
|
std::unique_ptr<http::server::Server> s;
|
||||||
|
|
||||||
if (strcmp(argv[1], "nossl") == 0) {
|
if (strcmp(argv[1], "nossl") == 0) {
|
||||||
s = std::make_unique<http::server::Server>(argv[2], argv[3]);
|
s = std::make_unique<http::server::Server>(argv[2], argv[3]);
|
||||||
initResources(*s, api);
|
resources.registerResources(*s);
|
||||||
s->run();
|
s->run();
|
||||||
|
|
||||||
} else if (strcmp(argv[1], "ssl") == 0) {
|
} else if (strcmp(argv[1], "ssl") == 0) {
|
||||||
@ -187,7 +242,7 @@ int main(int argc, char *argv[]) {
|
|||||||
ctx->use_tmp_dh(boost::asio::buffer(dh));
|
ctx->use_tmp_dh(boost::asio::buffer(dh));
|
||||||
|
|
||||||
s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
|
s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
|
||||||
initResources(*s, api);
|
resources.registerResources(*s);
|
||||||
s->run();
|
s->run();
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
|
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
|
|
||||||
namespace http::server {
|
namespace http::server {
|
||||||
using request_handler = std::function<void(const Request &req, reply &rep)>;
|
using request_handler = std::function<void(const Request &req, Reply &rep)>;
|
||||||
|
|
||||||
class ConnectionManager;
|
class ConnectionManager;
|
||||||
/// Represents a single Connection from a client. Base class
|
/// Represents a single Connection from a client. Base class
|
||||||
@ -71,7 +71,7 @@ namespace http::server {
|
|||||||
RequestParser request_parser_;
|
RequestParser request_parser_;
|
||||||
|
|
||||||
/// The reply to be sent back to the client.
|
/// The reply to be sent back to the client.
|
||||||
reply reply_;
|
Reply reply_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> {
|
class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> {
|
||||||
@ -113,7 +113,7 @@ namespace http::server {
|
|||||||
RequestParser request_parser_;
|
RequestParser request_parser_;
|
||||||
|
|
||||||
/// The reply to be sent back to the client.
|
/// The reply to be sent back to the client.
|
||||||
reply reply_;
|
Reply reply_;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<ConnectionBase> connection_ptr;
|
typedef std::shared_ptr<ConnectionBase> connection_ptr;
|
||||||
|
@ -68,7 +68,7 @@ namespace http::server {
|
|||||||
const char crlf[] = {'\r', '\n'};
|
const char crlf[] = {'\r', '\n'};
|
||||||
} // namespace misc_strings
|
} // namespace misc_strings
|
||||||
|
|
||||||
std::vector<boost::asio::const_buffer> reply::to_buffers() const {
|
std::vector<boost::asio::const_buffer> Reply::to_buffers() const {
|
||||||
std::vector<boost::asio::const_buffer> buffers;
|
std::vector<boost::asio::const_buffer> buffers;
|
||||||
buffers.push_back(status_strings::to_buffer(status));
|
buffers.push_back(status_strings::to_buffer(status));
|
||||||
for (const auto & h : headers) {
|
for (const auto & h : headers) {
|
||||||
@ -85,77 +85,77 @@ namespace http::server {
|
|||||||
namespace stock_replies {
|
namespace stock_replies {
|
||||||
constexpr char ok[] = "";
|
constexpr char ok[] = "";
|
||||||
constexpr char created[] =
|
constexpr char created[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Created</title></head>"
|
"<head><title>Created</title></head>"
|
||||||
"<body><h1>201 Created</h1></body>"
|
"<body><h1>201 Created</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char accepted[] =
|
constexpr char accepted[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Accepted</title></head>"
|
"<head><title>Accepted</title></head>"
|
||||||
"<body><h1>202 Accepted</h1></body>"
|
"<body><h1>202 Accepted</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char no_content[] =
|
constexpr char no_content[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>No Content</title></head>"
|
"<head><title>No Content</title></head>"
|
||||||
"<body><h1>204 Content</h1></body>"
|
"<body><h1>204 Content</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char multiple_choices[] =
|
constexpr char multiple_choices[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Multiple Choices</title></head>"
|
"<head><title>Multiple Choices</title></head>"
|
||||||
"<body><h1>300 Multiple Choices</h1></body>"
|
"<body><h1>300 Multiple Choices</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char moved_permanently[] =
|
constexpr char moved_permanently[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Moved Permanently</title></head>"
|
"<head><title>Moved Permanently</title></head>"
|
||||||
"<body><h1>301 Moved Permanently</h1></body>"
|
"<body><h1>301 Moved Permanently</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char moved_temporarily[] =
|
constexpr char moved_temporarily[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Moved Temporarily</title></head>"
|
"<head><title>Moved Temporarily</title></head>"
|
||||||
"<body><h1>302 Moved Temporarily</h1></body>"
|
"<body><h1>302 Moved Temporarily</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char not_modified[] =
|
constexpr char not_modified[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Not Modified</title></head>"
|
"<head><title>Not Modified</title></head>"
|
||||||
"<body><h1>304 Not Modified</h1></body>"
|
"<body><h1>304 Not Modified</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char bad_request[] =
|
constexpr char bad_request[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Bad Request</title></head>"
|
"<head><title>Bad Request</title></head>"
|
||||||
"<body><h1>400 Bad Request</h1></body>"
|
"<body><h1>400 Bad Request</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char unauthorized[] =
|
constexpr char unauthorized[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Unauthorized</title></head>"
|
"<head><title>Unauthorized</title></head>"
|
||||||
"<body><h1>401 Unauthorized</h1></body>"
|
"<body><h1>401 Unauthorized</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char forbidden[] =
|
constexpr char forbidden[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Forbidden</title></head>"
|
"<head><title>Forbidden</title></head>"
|
||||||
"<body><h1>403 Forbidden</h1></body>"
|
"<body><h1>403 Forbidden</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char not_found[] =
|
constexpr char not_found[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Not Found</title></head>"
|
"<head><title>Not Found</title></head>"
|
||||||
"<body><h1>404 Not Found</h1></body>"
|
"<body><h1>404 Not Found</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char internal_server_error[] =
|
constexpr char internal_server_error[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Internal Server Error</title></head>"
|
"<head><title>Internal Server Error</title></head>"
|
||||||
"<body><h1>500 Internal Server Error</h1></body>"
|
"<body><h1>500 Internal Server Error</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char not_implemented[] =
|
constexpr char not_implemented[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Not Implemented</title></head>"
|
"<head><title>Not Implemented</title></head>"
|
||||||
"<body><h1>501 Not Implemented</h1></body>"
|
"<body><h1>501 Not Implemented</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char bad_gateway[] =
|
constexpr char bad_gateway[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Bad Gateway</title></head>"
|
"<head><title>Bad Gateway</title></head>"
|
||||||
"<body><h1>502 Bad Gateway</h1></body>"
|
"<body><h1>502 Bad Gateway</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
constexpr char service_unavailable[] =
|
constexpr char service_unavailable[] =
|
||||||
"<html>"
|
"<!DOCTYPE html>\n<html>"
|
||||||
"<head><title>Service Unavailable</title></head>"
|
"<head><title>Service Unavailable</title></head>"
|
||||||
"<body><h1>503 Service Unavailable</h1></body>"
|
"<body><h1>503 Service Unavailable</h1></body>"
|
||||||
"</html>";
|
"</html>";
|
||||||
@ -206,7 +206,7 @@ namespace http::server {
|
|||||||
}
|
}
|
||||||
} // namespace stock_replies
|
} // namespace stock_replies
|
||||||
|
|
||||||
void stockReply(status_type status, reply& rep) {
|
void stockReply(status_type status, Reply& rep) {
|
||||||
rep.status = status;
|
rep.status = status;
|
||||||
rep.headers.clear();
|
rep.headers.clear();
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::text_html)});
|
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::text_html)});
|
||||||
|
@ -28,7 +28,7 @@ namespace http::server {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A reply to be sent to a client.
|
/// A reply to be sent to a client.
|
||||||
struct reply {
|
struct Reply {
|
||||||
status_type status;
|
status_type status;
|
||||||
|
|
||||||
/// The headers to be included in the reply.
|
/// The headers to be included in the reply.
|
||||||
@ -44,7 +44,7 @@ namespace http::server {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Get a stock reply.
|
/// Get a stock reply.
|
||||||
void stockReply(status_type status, reply& rep);
|
void stockReply(status_type status, Reply& rep);
|
||||||
|
|
||||||
} // namespace http::Server
|
} // namespace http::Server
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../../dependencies/control_system/common/protocol_commands.h"
|
||||||
|
|
||||||
static void loadFile(const std::string& path, std::vector<char>& content) {
|
static void loadFile(const std::string& path, std::vector<char>& content) {
|
||||||
std::ifstream is(path, std::ios::in | std::ios::binary);
|
std::ifstream is(path, std::ios::in | std::ios::binary);
|
||||||
@ -19,44 +22,60 @@ static void loadFile(const std::string& path, std::vector<char>& content) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http::resource::StaticFileResource::StaticFileResource(const std::string &path, const std::string &filePath, server::mime_types::Mime type): type(type) {
|
http::resource::BasicResource::BasicResource(std::string path): path(std::move(path)) {}
|
||||||
this->path = path;
|
|
||||||
#ifdef USE_DEBUG
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Skip loading file " << filePath << " (http path: " << path << ")";
|
|
||||||
this->filePath = filePath;
|
|
||||||
#else
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Load file " << filePath << " (http path: " << path << ")";
|
|
||||||
loadFile(filePath, this->content);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void http::resource::StaticFileResource::handle(const server::Request &req, server::reply &rep) {
|
http::resource::StaticFileFactory::StaticFileDef::StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache): path(std::move(path)), type(type), allowCache(allowCache) {
|
||||||
if (req.method != "GET") {
|
#ifdef USE_DEBUG
|
||||||
stockReply(server::bad_request, rep);
|
if (allowCache) {
|
||||||
return;
|
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path;
|
||||||
|
loadFile(this->path, this->content);
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Skip loading static file " << this->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO сделать поддержку range
|
|
||||||
#ifdef USE_DEBUG
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Reload file " << filePath << " (http path: " << path << ")";
|
|
||||||
loadFile(this->filePath, rep.content);
|
|
||||||
#else
|
#else
|
||||||
rep.content.clear();
|
BOOST_LOG_TRIVIAL(info) << "Load static file " << this->path;
|
||||||
rep.content.insert(rep.content.end(), this->content.begin(), this->content.end());
|
loadFile(this->path, this->content);
|
||||||
#endif
|
#endif
|
||||||
rep.status = server::ok;
|
}
|
||||||
rep.headers.clear();
|
http::resource::StaticFileFactory::StaticFileDef::~StaticFileDef() = default;
|
||||||
// TODO сделать cache control
|
|
||||||
rep.headers.push_back({.name = "Content-Type", .value = toString(this->type)});
|
http::resource::StaticFileFactory::StaticFileFactory() = default;
|
||||||
|
|
||||||
|
void http::resource::StaticFileFactory::registerFile(const std::string &path, server::mime_types::Mime type, bool allowCache) {
|
||||||
|
this->files.emplace_back(path, type, allowCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
http::resource::StaticFileResource::~StaticFileResource() = default;
|
void http::resource::StaticFileFactory::serve(const std::string &path, server::Reply &rep) {
|
||||||
|
for (auto& f: this->files) {
|
||||||
http::resource::GenericResource::GenericResource(const std::string &path, const respGenerator &generator): generator_(generator) {
|
if (f.path == path) {
|
||||||
this->path = path;
|
#ifdef USE_DEBUG
|
||||||
|
if (f.allowCache) {
|
||||||
|
rep.content.clear();
|
||||||
|
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Reload file " << path << " (http path: " << path << ")";
|
||||||
|
loadFile(f.path, rep.content);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
rep.content.clear();
|
||||||
|
rep.content.insert(rep.content.end(), f.content.begin(), f.content.end());
|
||||||
|
#endif
|
||||||
|
rep.status = server::ok;
|
||||||
|
// rep.headers.clear();
|
||||||
|
rep.headers.push_back({.name = "Content-Type", .value = server::mime_types::toString(f.type)});
|
||||||
|
if (f.allowCache) {
|
||||||
|
rep.headers.push_back({.name = "Cache-Control", .value = "max-age=86400"}); // сутки, думаю хватит
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void http::resource::GenericResource::handle(const server::Request &req, server::reply &rep) {
|
http::resource::StaticFileFactory::~StaticFileFactory() = default;
|
||||||
|
|
||||||
|
http::resource::GenericResource::GenericResource(const std::string &path, respGenerator generator): BasicResource(path), generator_(std::move(generator)) {}
|
||||||
|
|
||||||
|
void http::resource::GenericResource::handle(const server::Request &req, server::Reply &rep) {
|
||||||
this->generator_(req, rep);
|
this->generator_(req, rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,34 +12,38 @@ namespace http::resource {
|
|||||||
*/
|
*/
|
||||||
class BasicResource {
|
class BasicResource {
|
||||||
public:
|
public:
|
||||||
|
BasicResource(std::string path);
|
||||||
std::string path;
|
std::string path;
|
||||||
|
|
||||||
virtual void handle(const server::Request &req, server::reply &rep) = 0;
|
virtual void handle(const server::Request &req, server::Reply &rep) = 0;
|
||||||
|
|
||||||
virtual ~BasicResource() = default;
|
virtual ~BasicResource() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
class StaticFileFactory {
|
||||||
* Класс ресурса статического файла
|
class StaticFileDef {
|
||||||
*/
|
public:
|
||||||
class StaticFileResource final : public BasicResource {
|
StaticFileDef(std::string path, server::mime_types::Mime type, bool allowCache = true);
|
||||||
private:
|
|
||||||
server::mime_types::Mime type;
|
|
||||||
#ifdef USE_DEBUG
|
|
||||||
std::string filePath;
|
|
||||||
#else
|
|
||||||
std::vector<char> content;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
std::string path;
|
||||||
|
server::mime_types::Mime type;
|
||||||
|
bool allowCache;
|
||||||
|
std::vector<char> content;
|
||||||
|
|
||||||
|
~StaticFileDef();
|
||||||
|
};
|
||||||
|
std::vector<StaticFileDef> files;
|
||||||
public:
|
public:
|
||||||
StaticFileResource(const std::string& path, const std::string& filePath, server::mime_types::Mime type);
|
StaticFileFactory();
|
||||||
|
|
||||||
void handle(const server::Request &req, server::reply &rep) override;
|
void registerFile(const std::string& path, server::mime_types::Mime type, bool allowCache = true);
|
||||||
|
|
||||||
~StaticFileResource() override;
|
void serve(const std::string& path, server::Reply& rep);
|
||||||
|
|
||||||
|
~StaticFileFactory();
|
||||||
};
|
};
|
||||||
|
|
||||||
using respGenerator = std::function<void(const server::Request &req, server::reply &rep)>;
|
using respGenerator = std::function<void(const server::Request &req, server::Reply &rep)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Класс ресурса для POST-запросов
|
* Класс ресурса для POST-запросов
|
||||||
@ -49,9 +53,9 @@ namespace http::resource {
|
|||||||
respGenerator generator_;
|
respGenerator generator_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GenericResource(const std::string& path, const respGenerator& generator);
|
GenericResource(const std::string& path, respGenerator generator);
|
||||||
|
|
||||||
void handle(const server::Request &req, server::reply &rep) override;
|
void handle(const server::Request &req, server::Reply &rep) override;
|
||||||
|
|
||||||
~GenericResource() override;
|
~GenericResource() override;
|
||||||
};
|
};
|
||||||
|
@ -111,7 +111,7 @@ namespace http::server {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server::requestHandler(const Request &req, reply &rep) {
|
void Server::requestHandler(const Request &req, Reply &rep) {
|
||||||
// Request path must be absolute and not contain "..".
|
// Request path must be absolute and not contain "..".
|
||||||
if (req.url->path.empty() || req.url->path[0] != '/' || req.url->path.find("..") != std::string::npos) {
|
if (req.url->path.empty() || req.url->path[0] != '/' || req.url->path.find("..") != std::string::npos) {
|
||||||
stockReply(bad_request, rep);
|
stockReply(bad_request, rep);
|
||||||
|
@ -74,7 +74,7 @@ namespace http::server {
|
|||||||
ConnectionManager connection_manager_;
|
ConnectionManager connection_manager_;
|
||||||
|
|
||||||
/// Handle a request and produce a reply.
|
/// Handle a request and produce a reply.
|
||||||
void requestHandler(const Request &req, reply &rep);
|
void requestHandler(const Request &req, Reply &rep);
|
||||||
};
|
};
|
||||||
} // namespace http::Server
|
} // namespace http::Server
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user