завел https
This commit is contained in:
parent
d6851052b4
commit
2fef65d9d9
@ -17,6 +17,8 @@ endif()
|
||||
|
||||
add_compile_options(-Wall -Wextra -Wsign-conversion)
|
||||
|
||||
add_subdirectory(dependencies/control_system-dvbs-2)
|
||||
|
||||
add_executable(terminal-web-server
|
||||
src/server/mime_types.hpp
|
||||
src/server/mime_types.cpp
|
||||
@ -26,8 +28,6 @@ add_executable(terminal-web-server
|
||||
src/server/request_parser.cpp
|
||||
src/server/server.cpp
|
||||
src/server/header.hpp
|
||||
src/server/connection_manager.cpp
|
||||
src/server/connection_manager.hpp
|
||||
src/server/reply.hpp
|
||||
src/server/reply.cpp
|
||||
src/server/connection.cpp
|
||||
@ -38,18 +38,7 @@ add_executable(terminal-web-server
|
||||
)
|
||||
|
||||
find_package(Boost 1.53.0 COMPONENTS system thread filesystem url log log_setup REQUIRED)
|
||||
target_link_libraries(terminal-web-server ${Boost_LIBRARIES})
|
||||
target_include_directories(terminal-web-server PRIVATE ${Boost_INCLUDE_DIR})
|
||||
|
||||
#find_package(OpenSSL)
|
||||
#if(OPENSSL_FOUND)
|
||||
# target_compile_definitions(simple-web-server INTERFACE HAVE_OPENSSL)
|
||||
# target_link_libraries(simple-web-server INTERFACE ${OPENSSL_LIBRARIES})
|
||||
# target_include_directories(simple-web-server INTERFACE ${OPENSSL_INCLUDE_DIR})
|
||||
#
|
||||
# add_executable(https_examples https_examples.cpp)
|
||||
# target_link_libraries(https_examples simple-web-server)
|
||||
# target_link_libraries(https_examples ${Boost_LIBRARIES})
|
||||
# target_include_directories(https_examples PRIVATE ${Boost_INCLUDE_DIR})
|
||||
#endif()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(terminal-web-server ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES})
|
||||
target_include_directories(terminal-web-server PRIVATE ${Boost_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
|
||||
|
||||
|
16
README.md
16
README.md
@ -10,3 +10,19 @@
|
||||
sudo apt-get install libboost-all-dev
|
||||
```
|
||||
|
||||
Для библиотеки api нужно еще такое:
|
||||
|
||||
```shell
|
||||
sudo apt install cereal libcereal-dev
|
||||
```
|
||||
|
||||
# SSL
|
||||
|
||||
В коде установлена версия `TSL 1.2`.
|
||||
|
||||
Генерировать сертификаты в тестовых целях можно так:
|
||||
|
||||
```shell
|
||||
openssl dhparam -out dh.pem 2048
|
||||
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3600 -out cert.pem -subj "/C=ru/ST=ru/L=Moscow/O=NTC RSS/CN=terminal"
|
||||
```
|
||||
|
110
src/main.cpp
110
src/main.cpp
@ -9,6 +9,33 @@
|
||||
#include <boost/log/utility/setup/console.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/formatter_parser.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
|
||||
|
||||
|
||||
static std::vector<char> loadFile(const std::string& path) {
|
||||
std::ifstream is(path, std::ios::in | std::ios::binary);
|
||||
if (!is) {
|
||||
throw std::runtime_error("File not found");
|
||||
}
|
||||
|
||||
std::vector<char> content;
|
||||
for (;;) {
|
||||
char buf[512];
|
||||
auto len = is.read(buf, sizeof(buf)).gcount();
|
||||
if (len <= 0) {
|
||||
break;
|
||||
}
|
||||
content.insert(content.end(), buf, buf + len);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
namespace mime_types = http::server::mime_types;
|
||||
@ -43,19 +70,36 @@ void init_logging() {
|
||||
log::add_common_attributes();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
// Check command line arguments.
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: http_server <address> <port>\n";
|
||||
std::cerr << " For IPv4, try:\n";
|
||||
std::cerr << " receiver 0.0.0.0 80\n";
|
||||
std::cerr << " For IPv6, try:\n";
|
||||
std::cerr << " receiver 0::0 80\n";
|
||||
return 1;
|
||||
static void initResources(http::server::Server& s) {
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/login.html", mime_types::text_html));
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));
|
||||
|
||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
|
||||
if (req.method != "GET") {
|
||||
http::server::stock_reply(http::server::bad_request, rep);
|
||||
}
|
||||
|
||||
rep.status = http::server::ok;
|
||||
rep.headers.clear();
|
||||
rep.headers.push_back({.name = "Content-Type", .value = to_string(mime_types::json)});
|
||||
const char* json = R"({"key":"value"})";
|
||||
rep.content.insert(rep.content.end(), json, json + strlen(json));
|
||||
}));
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
try {
|
||||
prctl(PR_SET_NAME, "main", 0, 0, 0);
|
||||
// Check command line arguments.
|
||||
if (argc != 4 && argc != 5) {
|
||||
std::cerr << "Usage: http_server <ssl|nossl> <address> <port> [static files directory]\n";
|
||||
std::cerr << " For IPv4, try:\n";
|
||||
std::cerr << " receiver nossl 0.0.0.0 80\n";
|
||||
std::cerr << " For IPv6, try:\n";
|
||||
std::cerr << " receiver nossl 0::0 80\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
init_logging();
|
||||
boost::log::core::get()->add_thread_attribute("Scope", boost::log::attributes::named_scope());
|
||||
@ -67,26 +111,40 @@ int main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
// Initialise the server.
|
||||
http::server::server s(argv[1], argv[2]);
|
||||
std::unique_ptr<http::server::Server> s;
|
||||
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/", "static/login.html", mime_types::text_html));
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/favicon.ico", "static/favicon.png", mime_types::image_png));
|
||||
s.resources.emplace_back(std::make_unique<http::resource::StaticFileResource>("/js/vue.js", "static/js/vue.js", mime_types::javascript));
|
||||
if (strcmp(argv[1], "nossl") == 0) {
|
||||
s = std::make_unique<http::server::Server>(argv[2], argv[3]);
|
||||
initResources(*s);
|
||||
s->run();
|
||||
|
||||
s.resources.emplace_back(std::make_unique<http::resource::GenericResource>("/api/statistics", [](const auto& req, auto& rep) {
|
||||
if (req.method != "GET") {
|
||||
http::server::stock_reply(http::server::bad_request, rep);
|
||||
}
|
||||
} else if (strcmp(argv[1], "ssl") == 0) {
|
||||
const auto cert = loadFile("cert.pem");
|
||||
const auto key = loadFile("key.pem");
|
||||
const auto dh = loadFile("dh.pem");
|
||||
|
||||
rep.status = http::server::ok;
|
||||
rep.headers.clear();
|
||||
rep.headers.push_back({.name = "Content-Type", .value = to_string(mime_types::json)});
|
||||
const char* json = R"({"key":"value"})";
|
||||
rep.content.insert(rep.content.end(), json, json + strlen(json));
|
||||
}));
|
||||
auto ctx = std::make_shared<ssl::context>(ssl::context::tlsv12);
|
||||
|
||||
// Run the server until stopped.
|
||||
s.run();
|
||||
ctx->set_password_callback(
|
||||
[](std::size_t, ssl::context_base::password_purpose) {
|
||||
return "test";
|
||||
});
|
||||
|
||||
ctx->set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
|
||||
|
||||
ctx->use_certificate_chain(boost::asio::buffer(cert));
|
||||
|
||||
ctx->use_private_key(boost::asio::buffer(key), ssl::context::file_format::pem);
|
||||
|
||||
ctx->use_tmp_dh(boost::asio::buffer(dh));
|
||||
|
||||
s = std::make_unique<http::server::Server>(argv[2], argv[3], ctx);
|
||||
initResources(*s);
|
||||
s->run();
|
||||
} else {
|
||||
std::cerr << "Unsupported ssl mode: " << argv[1] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} catch (std::exception &e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what() << std::endl;
|
||||
return -1;
|
||||
|
@ -1,38 +1,28 @@
|
||||
//
|
||||
// connection.cpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "connection.hpp"
|
||||
#include <utility>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
|
||||
#include "connection_manager.hpp"
|
||||
#include "server.hpp"
|
||||
|
||||
|
||||
namespace http::server {
|
||||
connection::connection(boost::asio::ip::tcp::socket socket,
|
||||
connection_manager &manager, request_handler handler)
|
||||
: socket_(std::move(socket)),
|
||||
connection_manager_(manager),
|
||||
request_handler_(std::move(handler)), request_(), reply_() {
|
||||
const char* SERVER_HEADER_VALUE = "TerminalWebServer v0.1";
|
||||
|
||||
Connection::Connection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler)
|
||||
: socket_(std::move(socket)), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
|
||||
}
|
||||
|
||||
void connection::start() {
|
||||
void Connection::start() {
|
||||
do_read();
|
||||
}
|
||||
|
||||
void connection::stop() {
|
||||
void Connection::stop() {
|
||||
socket_.close();
|
||||
}
|
||||
|
||||
void connection::do_read() {
|
||||
Connection::~Connection() = default;
|
||||
|
||||
void Connection::do_read() {
|
||||
request_parser_.reset();
|
||||
request_.headers.clear();
|
||||
request_.method.clear();
|
||||
@ -60,8 +50,8 @@ namespace http::server {
|
||||
});
|
||||
}
|
||||
|
||||
void connection::do_write() {
|
||||
reply_.headers.push_back({.name = "Server", .value = "TerminalWebServer v0.1"});
|
||||
void Connection::do_write() {
|
||||
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
|
||||
if (!reply_.content.empty()) {
|
||||
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
|
||||
}
|
||||
@ -74,11 +64,83 @@ namespace http::server {
|
||||
auto self(shared_from_this());
|
||||
async_write(socket_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
|
||||
if (!ec) {
|
||||
// keep alive connection
|
||||
// keep alive Connection
|
||||
do_read();
|
||||
} else {
|
||||
connection_manager_.stop(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace http::server
|
||||
|
||||
SslConnection::SslConnection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler, const std::shared_ptr<boost::asio::ssl::context>& ctx):
|
||||
stream_(std::move(socket), *ctx), connection_manager_(manager), request_handler_(std::move(handler)), request_(), reply_() {
|
||||
}
|
||||
|
||||
void SslConnection::start() {
|
||||
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
|
||||
|
||||
// Perform the SSL handshake
|
||||
stream_.async_handshake(boost::asio::ssl::stream_base::server, boost::beast::bind_front_handler([this](auto ec) {
|
||||
boost::ignore_unused(ec);
|
||||
doRead();
|
||||
}));
|
||||
}
|
||||
|
||||
void SslConnection::stop() {
|
||||
}
|
||||
|
||||
SslConnection::~SslConnection() = default;
|
||||
|
||||
void SslConnection::doRead() {
|
||||
get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
|
||||
|
||||
request_parser_.reset();
|
||||
request_.headers.clear();
|
||||
request_.method.clear();
|
||||
request_.uri.clear();
|
||||
|
||||
auto self(shared_from_this());
|
||||
stream_.async_read_some(boost::asio::buffer(buffer_), [this, self](boost::system::error_code ec, std::size_t bytes_transferred) {
|
||||
if (!ec) {
|
||||
request_parser::result_type result;
|
||||
std::tie(result, std::ignore) = request_parser_.parse(
|
||||
request_, buffer_.data(), buffer_.data() + bytes_transferred);
|
||||
|
||||
if (result == request_parser::good) {
|
||||
request_handler_(request_, reply_);
|
||||
doWrite();
|
||||
} else if (result == request_parser::bad) {
|
||||
stock_reply(bad_request, reply_);
|
||||
doWrite();
|
||||
} else {
|
||||
doRead();
|
||||
}
|
||||
} else {
|
||||
connection_manager_.stop(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SslConnection::doWrite() {
|
||||
reply_.headers.push_back({.name = "Server", .value = SERVER_HEADER_VALUE});
|
||||
if (!reply_.content.empty()) {
|
||||
reply_.headers.push_back({.name = "Content-Length", .value = std::to_string(reply_.content.size())});
|
||||
}
|
||||
if (request_.http_version_major == 1) {
|
||||
reply_.headers.push_back({.name = "Connection", .value = "keep-alive"});
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "HTTP query " << reply_.status << " " << request_.method << " " << request_.uri;
|
||||
|
||||
auto self(shared_from_this());
|
||||
async_write(stream_, reply_.to_buffers(), [this, self](boost::system::error_code ec, std::size_t) {
|
||||
if (!ec) {
|
||||
// keep alive Connection
|
||||
doRead();
|
||||
} else {
|
||||
connection_manager_.stop(shared_from_this());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace http::Server
|
||||
|
@ -1,17 +1,10 @@
|
||||
//
|
||||
// connection.hpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_CONNECTION_HPP
|
||||
#define HTTP_CONNECTION_HPP
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include "reply.hpp"
|
||||
@ -22,24 +15,36 @@
|
||||
namespace http::server {
|
||||
using request_handler = std::function<void(const request &req, reply &rep)>;
|
||||
|
||||
class connection_manager;
|
||||
/// Represents a single connection from a client.
|
||||
class connection
|
||||
: public std::enable_shared_from_this<connection> {
|
||||
class ConnectionManager;
|
||||
/// Represents a single Connection from a client. Base class
|
||||
class ConnectionBase {
|
||||
public:
|
||||
connection(const connection &) = delete;
|
||||
ConnectionBase(const ConnectionBase &) = delete;
|
||||
ConnectionBase &operator=(const ConnectionBase &) = delete;
|
||||
ConnectionBase() = default;
|
||||
|
||||
connection &operator=(const connection &) = delete;
|
||||
/// Start the first asynchronous operation for the Connection.
|
||||
virtual void start() = 0;
|
||||
|
||||
/// Construct a connection with the given socket.
|
||||
explicit connection(boost::asio::ip::tcp::socket socket, connection_manager &manager, request_handler handler);
|
||||
/// Stop all asynchronous operations associated with the Connection.
|
||||
virtual void stop() = 0;
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void start();
|
||||
virtual ~ConnectionBase() = default;
|
||||
};
|
||||
|
||||
/// Stop all asynchronous operations associated with the connection.
|
||||
void stop();
|
||||
class Connection final : public ConnectionBase, public std::enable_shared_from_this<Connection> {
|
||||
public:
|
||||
|
||||
/// Construct a Connection with the given socket.
|
||||
explicit Connection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler);
|
||||
|
||||
/// Start the first asynchronous operation for the Connection.
|
||||
void start() override;
|
||||
|
||||
/// Stop all asynchronous operations associated with the Connection.
|
||||
void stop() override;
|
||||
|
||||
~Connection() override;
|
||||
private:
|
||||
/// Perform an asynchronous read operation.
|
||||
void do_read();
|
||||
@ -47,17 +52,17 @@ namespace http::server {
|
||||
/// Perform an asynchronous write operation.
|
||||
void do_write();
|
||||
|
||||
/// Socket for the connection.
|
||||
/// Socket for the Connection.
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
|
||||
/// The manager for this connection.
|
||||
connection_manager &connection_manager_;
|
||||
/// The manager for this Connection.
|
||||
ConnectionManager &connection_manager_;
|
||||
|
||||
/// The handler used to process the incoming request.
|
||||
request_handler request_handler_;
|
||||
|
||||
/// Buffer for incoming data.
|
||||
std::array<char, 8192> buffer_{};
|
||||
std::array<char, 2048> buffer_{};
|
||||
|
||||
/// The incoming request.
|
||||
request request_;
|
||||
@ -69,8 +74,50 @@ namespace http::server {
|
||||
reply reply_;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<connection> connection_ptr;
|
||||
} // namespace http::server
|
||||
class SslConnection final : public ConnectionBase, public std::enable_shared_from_this<SslConnection> {
|
||||
public:
|
||||
|
||||
/// Construct a Connection with the given socket.
|
||||
explicit SslConnection(boost::asio::ip::tcp::socket socket, ConnectionManager &manager, request_handler handler, const std::shared_ptr<boost::asio::ssl::context>& ctx);
|
||||
|
||||
/// Start the first asynchronous operation for the Connection.
|
||||
void start() override;
|
||||
|
||||
/// Stop all asynchronous operations associated with the Connection.
|
||||
void stop() override;
|
||||
|
||||
~SslConnection() override;
|
||||
private:
|
||||
/// Perform an asynchronous read operation.
|
||||
void doRead();
|
||||
|
||||
/// Perform an asynchronous write operation.
|
||||
void doWrite();
|
||||
|
||||
/// Socket for the Connection.
|
||||
boost::asio::ssl::stream<boost::beast::tcp_stream> stream_;
|
||||
|
||||
/// The manager for this Connection.
|
||||
ConnectionManager &connection_manager_;
|
||||
|
||||
/// The handler used to process the incoming request.
|
||||
request_handler request_handler_;
|
||||
|
||||
/// Buffer for incoming data.
|
||||
std::array<char, 2048> buffer_{};
|
||||
|
||||
/// The incoming request.
|
||||
request request_;
|
||||
|
||||
/// The parser for the incoming request.
|
||||
request_parser request_parser_;
|
||||
|
||||
/// The reply to be sent back to the client.
|
||||
reply reply_;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<ConnectionBase> connection_ptr;
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_CONNECTION_HPP
|
||||
|
@ -1,32 +0,0 @@
|
||||
//
|
||||
// connection_manager.cpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "connection_manager.hpp"
|
||||
|
||||
|
||||
namespace http::server {
|
||||
connection_manager::connection_manager() = default;
|
||||
|
||||
void connection_manager::start(connection_ptr c) {
|
||||
connections_.insert(c);
|
||||
c->start();
|
||||
}
|
||||
|
||||
void connection_manager::stop(connection_ptr c) {
|
||||
connections_.erase(c);
|
||||
c->stop();
|
||||
}
|
||||
|
||||
void connection_manager::stop_all() {
|
||||
for (auto& c: connections_)
|
||||
c->stop();
|
||||
connections_.clear();
|
||||
}
|
||||
} // namespace http::server
|
@ -1,46 +0,0 @@
|
||||
//
|
||||
// connection_manager.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_CONNECTION_MANAGER_HPP
|
||||
#define HTTP_CONNECTION_MANAGER_HPP
|
||||
|
||||
#include <set>
|
||||
#include "connection.hpp"
|
||||
|
||||
|
||||
namespace http::server {
|
||||
/// Manages open connections so that they may be cleanly stopped when the server
|
||||
/// needs to shut down.
|
||||
class connection_manager {
|
||||
public:
|
||||
connection_manager(const connection_manager &) = delete;
|
||||
|
||||
connection_manager &operator=(const connection_manager &) = delete;
|
||||
|
||||
/// Construct a connection manager.
|
||||
connection_manager();
|
||||
|
||||
/// Add the specified connection to the manager and start it.
|
||||
void start(connection_ptr c);
|
||||
|
||||
/// Stop the specified connection.
|
||||
void stop(connection_ptr c);
|
||||
|
||||
/// Stop all connections.
|
||||
void stop_all();
|
||||
|
||||
private:
|
||||
/// The managed connections.
|
||||
std::set<connection_ptr> connections_;
|
||||
};
|
||||
} // namespace http::server
|
||||
|
||||
|
||||
#endif // HTTP_CONNECTION_MANAGER_HPP
|
@ -19,7 +19,7 @@ namespace http::server {
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
} // namespace http::server
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_HEADER_HPP
|
||||
|
@ -34,7 +34,7 @@ namespace http::server::mime_types {
|
||||
|
||||
/// Convert a file extension into a MIME type.
|
||||
std::string extension_to_type(const std::string &extension);
|
||||
} // namespace http::server::mime_types
|
||||
} // namespace http::Server::mime_types
|
||||
|
||||
|
||||
#endif // HTTP_MIME_TYPES_HPP
|
||||
|
@ -57,7 +57,7 @@ namespace http::server {
|
||||
/// Get a stock reply.
|
||||
void stock_reply(status_type status, reply& rep);
|
||||
|
||||
} // namespace http::server
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_REPLY_HPP
|
||||
|
@ -26,7 +26,7 @@ namespace http::server {
|
||||
int http_version_minor;
|
||||
std::vector<header> headers;
|
||||
};
|
||||
} // namespace http::server
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_REQUEST_HPP
|
||||
|
@ -86,7 +86,7 @@ namespace http::server {
|
||||
expecting_newline_3
|
||||
} state_;
|
||||
};
|
||||
} // namespace http::server
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_REQUEST_PARSER_HPP
|
||||
|
@ -1,10 +1,29 @@
|
||||
#include "server.hpp"
|
||||
#include <utility>
|
||||
#include <boost/beast/core/basic_stream.hpp>
|
||||
#include <boost/url/url_view.hpp>
|
||||
|
||||
|
||||
namespace http::server {
|
||||
server::server(const std::string &address, const std::string &port)
|
||||
ConnectionManager::ConnectionManager() = default;
|
||||
|
||||
void ConnectionManager::start(const connection_ptr& c) {
|
||||
connections_.insert(c);
|
||||
c->start();
|
||||
}
|
||||
|
||||
void ConnectionManager::stop(const connection_ptr& c) {
|
||||
connections_.erase(c);
|
||||
c->stop();
|
||||
}
|
||||
|
||||
void ConnectionManager::stop_all() {
|
||||
for (auto& c: connections_)
|
||||
c->stop();
|
||||
connections_.clear();
|
||||
}
|
||||
|
||||
Server::Server(const std::string &address, const std::string &port)
|
||||
: io_context_(1), signals_(io_context_), acceptor_(io_context_) {
|
||||
// Register to handle the signals that indicate when the server should exit.
|
||||
// It is safe to register for the same signal multiple times in a program,
|
||||
@ -15,7 +34,7 @@ namespace http::server {
|
||||
signals_.add(SIGQUIT);
|
||||
#endif // defined(SIGQUIT)
|
||||
|
||||
do_await_stop();
|
||||
doAwaitStop();
|
||||
|
||||
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
|
||||
boost::asio::ip::tcp::resolver resolver(io_context_);
|
||||
@ -26,10 +45,34 @@ namespace http::server {
|
||||
acceptor_.bind(endpoint);
|
||||
acceptor_.listen(128);
|
||||
|
||||
do_accept();
|
||||
doAccept();
|
||||
}
|
||||
|
||||
void server::run() {
|
||||
Server::Server(const std::string &address, const std::string &port, std::shared_ptr<boost::asio::ssl::context> ctx):
|
||||
ssl_ctx(std::move(ctx)), io_context_(1), signals_(io_context_), acceptor_(io_context_) {
|
||||
// Register to handle the signals that indicate when the server should exit.
|
||||
// It is safe to register for the same signal multiple times in a program,
|
||||
// provided all registration for the specified signal is made through Asio.
|
||||
signals_.add(SIGINT);
|
||||
signals_.add(SIGTERM);
|
||||
#if defined(SIGQUIT)
|
||||
signals_.add(SIGQUIT);
|
||||
#endif // defined(SIGQUIT)
|
||||
|
||||
doAwaitStop();
|
||||
|
||||
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
|
||||
boost::asio::ip::tcp::resolver resolver(io_context_);
|
||||
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();
|
||||
acceptor_.open(endpoint.protocol());
|
||||
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
|
||||
acceptor_.bind(endpoint);
|
||||
acceptor_.listen(128);
|
||||
|
||||
doAccept();
|
||||
}
|
||||
|
||||
void Server::run() {
|
||||
// The io_context::run() call will block until all asynchronous operations
|
||||
// have finished. While the server is running, there is always at least one
|
||||
// asynchronous operation outstanding: the asynchronous accept call waiting
|
||||
@ -37,7 +80,7 @@ namespace http::server {
|
||||
io_context_.run();
|
||||
}
|
||||
|
||||
void server::do_accept() {
|
||||
void Server::doAccept() {
|
||||
acceptor_.async_accept(
|
||||
[this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket) {
|
||||
// Check whether the server was stopped by a signal before this
|
||||
@ -47,15 +90,18 @@ namespace http::server {
|
||||
}
|
||||
|
||||
if (!ec) {
|
||||
connection_manager_.start(std::make_shared<connection>(
|
||||
std::move(socket), connection_manager_, [this](const auto& req, auto& rep) { this->handle_request(req, rep); }));
|
||||
if (ssl_ctx == nullptr) {
|
||||
connection_manager_.start(std::make_shared<Connection>(std::move(socket), connection_manager_, [this](const auto& req, auto& rep) { this->requestHandler(req, rep); }));
|
||||
} else {
|
||||
connection_manager_.start(std::make_shared<SslConnection>(std::move(socket), connection_manager_, [this](const auto& req, auto& rep) { this->requestHandler(req, rep); }, ssl_ctx));
|
||||
}
|
||||
}
|
||||
|
||||
do_accept();
|
||||
doAccept();
|
||||
});
|
||||
}
|
||||
|
||||
void server::do_await_stop() {
|
||||
void Server::doAwaitStop() {
|
||||
signals_.async_wait(
|
||||
[this](boost::system::error_code /*ec*/, int /*signo*/) {
|
||||
// The server is stopped by cancelling all outstanding asynchronous
|
||||
@ -66,8 +112,7 @@ namespace http::server {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void server::handle_request(const request &req, reply &rep) {
|
||||
void Server::requestHandler(const request &req, reply &rep) {
|
||||
boost::urls::url_view url(req.uri);
|
||||
|
||||
const auto path = url.path();
|
||||
@ -92,4 +137,5 @@ namespace http::server {
|
||||
|
||||
stock_reply(not_found, rep);
|
||||
}
|
||||
} // namespace http::server
|
||||
|
||||
} // namespace http::Server
|
||||
|
@ -1,22 +1,51 @@
|
||||
#ifndef HTTP_SERVER_HPP
|
||||
#define HTTP_SERVER_HPP
|
||||
|
||||
#include <set>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <string>
|
||||
#include "connection_manager.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "resource.h"
|
||||
|
||||
|
||||
namespace http::server {
|
||||
/// The top-level class of the HTTP server.
|
||||
class server {
|
||||
/// Manages open connections so that they may be cleanly stopped when the server
|
||||
/// needs to shut down.
|
||||
class ConnectionManager {
|
||||
public:
|
||||
server(const server &) = delete;
|
||||
ConnectionManager(const ConnectionManager &) = delete;
|
||||
|
||||
server &operator=(const server &) = delete;
|
||||
ConnectionManager &operator=(const ConnectionManager &) = delete;
|
||||
|
||||
/// Construct a Connection manager.
|
||||
ConnectionManager();
|
||||
|
||||
/// Add the specified Connection to the manager and start it.
|
||||
void start(const connection_ptr& c);
|
||||
|
||||
/// Stop the specified Connection.
|
||||
void stop(const connection_ptr& c);
|
||||
|
||||
/// Stop all connections.
|
||||
void stop_all();
|
||||
|
||||
private:
|
||||
/// The managed connections.
|
||||
std::set<connection_ptr> connections_;
|
||||
};
|
||||
|
||||
|
||||
/// The top-level class of the HTTP server.
|
||||
class Server {
|
||||
public:
|
||||
Server(const Server &) = delete;
|
||||
|
||||
Server &operator=(const Server &) = delete;
|
||||
|
||||
/// Construct the server to listen on the specified TCP address and port
|
||||
explicit server(const std::string &address, const std::string &port);
|
||||
explicit Server(const std::string &address, const std::string &port);
|
||||
explicit Server(const std::string &address, const std::string &port, std::shared_ptr<boost::asio::ssl::context> ctx);
|
||||
|
||||
std::vector<std::unique_ptr<resource::BasicResource>> resources;
|
||||
|
||||
@ -24,11 +53,13 @@ namespace http::server {
|
||||
void run();
|
||||
|
||||
private:
|
||||
std::shared_ptr<boost::asio::ssl::context> ssl_ctx;
|
||||
|
||||
/// Perform an asynchronous accept operation.
|
||||
void do_accept();
|
||||
void doAccept();
|
||||
|
||||
/// Wait for a request to stop the server.
|
||||
void do_await_stop();
|
||||
void doAwaitStop();
|
||||
|
||||
/// The io_context used to perform asynchronous operations.
|
||||
boost::asio::io_context io_context_;
|
||||
@ -39,13 +70,13 @@ namespace http::server {
|
||||
/// Acceptor used to listen for incoming connections.
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
/// The connection manager which owns all live connections.
|
||||
connection_manager connection_manager_;
|
||||
/// The Connection manager which owns all live connections.
|
||||
ConnectionManager connection_manager_;
|
||||
|
||||
/// Handle a request and produce a reply.
|
||||
void handle_request(const request &req, reply &rep);
|
||||
void requestHandler(const request &req, reply &rep);
|
||||
};
|
||||
} // namespace http::server
|
||||
} // namespace http::Server
|
||||
|
||||
|
||||
#endif // HTTP_SERVER_HPP
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
export default {
|
||||
el: '#status-header',
|
||||
data: {
|
||||
message: 'Hello Vue!',
|
||||
now: new Date()
|
||||
},
|
||||
methods: {
|
||||
updateDate() {
|
||||
this.now = new Date();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setInterval(() => {
|
||||
this.updateDate();
|
||||
}, 1000);
|
||||
},
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
return { count }
|
||||
},
|
||||
template: `<div>Счётчик: {{ count }}</div>`
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user