Update server source,

This commit is contained in:
amass 2024-01-24 23:19:53 +08:00
parent 02ccd80cd9
commit cd77b8d1e1
23 changed files with 575 additions and 437 deletions

View File

@ -5,6 +5,8 @@ project(Older)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(OPENSSL_LIBRARIES ssl crypto)
include(FetchContent) include(FetchContent)
FetchContent_Declare(Kylin FetchContent_Declare(Kylin
GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git

View File

@ -1,17 +1,16 @@
#include "SharedState.h" #include "Application.h"
#include "Database.h" #include "Database.h"
#include "DateTime.h" #include "DateTime.h"
#include "HttpSession.h" #include "HttpSession.h"
#include "IoContext.h"
#include "ServiceLogic.h" #include "ServiceLogic.h"
#include "ServiceManager.h" #include "ServiceManager.h"
#include "WeChatContext/CorporationContext.h" #include "WeChatContext/CorporationContext.h"
#include "WebsocketSession.h"
SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_root) Application::Application(const std::string &path)
: m_ioContext(ioContext), m_router{std::make_shared<boost::urls::router<Handler>>()}, : ApplicationSettings(path), m_router{std::make_shared<boost::urls::router<RequestHandler>>()} {
m_docRoot(std::move(doc_root)), m_timer(ioContext) {
alarmTask();
// clang-format off
m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast; using namespace boost::beast;
boost::urls::url_view view(request.target()); boost::urls::url_view view(request.target());
@ -21,7 +20,7 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
session.reply(ServiceLogic::badRequest(request, "Illegal request-target")); session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
return; return;
} }
std::string path = ResponseUtility::pathCat(m_docRoot, target); std::string path = ResponseUtility::pathCat(getDocumentRoot(), target);
if (target.back() == '/') path.append("index.html"); if (target.back() == '/') path.append("index.html");
if (std::filesystem::is_directory(path)) path.append("/index.html"); if (std::filesystem::is_directory(path)) path.append("/index.html");
boost::beast::error_code ec; boost::beast::error_code ec;
@ -47,13 +46,12 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
session.reply(std::move(res)); session.reply(std::move(res));
}); });
m_router->insert("/wechat/{session*}", m_router->insert("/wechat/{session*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { ServiceLogic::onWechat(shared_from_this(), request,
ServiceLogic::onWechat(shared_from_this(), request, [&session](auto &&response) { session.reply(std::move(response)); });
[&session](auto &&response) { session.reply(std::move(response)); }); });
});
m_router->insert("/api/v1/tasklist",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { m_router->insert("/api/v1/tasklist", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast; using namespace boost::beast;
auto database = Amass::Singleton<Database>::instance(); auto database = Amass::Singleton<Database>::instance();
auto tasks = database->tasks(); auto tasks = database->tasks();
@ -79,7 +77,7 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
content = root.at("content").as_string(); content = root.at("content").as_string();
} }
bool ret = database->addTask(root.at("createTime").as_int64(), content, bool ret = database->addTask(root.at("createTime").as_int64(), content,
std::string(root.at("comment").as_string()), root.at("parentId").as_int64()); std::string(root.at("comment").as_string()), root.at("parentId").as_int64());
boost::json::object reply; boost::json::object reply;
reply["status"] = ret ? 0 : -1; reply["status"] = ret ? 0 : -1;
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()}; http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
@ -91,14 +89,14 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
session.reply(std::move(s)); session.reply(std::move(s));
}); });
m_router->insert("/api/v1/task/delete/{id}", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { m_router->insert("/api/v1/task/delete/{id}", [this](HttpSession &session, const Request &request,const boost::urls::matches &matches) {
using namespace boost::beast; using namespace boost::beast;
LOG(info) << "delete task: " << matches.at("id"); LOG(info) << "delete task: " << matches.at("id");
auto database = Amass::Singleton<Database>::instance(); auto database = Amass::Singleton<Database>::instance();
auto status = database->removeTask(std::stoi(matches.at("id")) ); auto status = database->removeTask(std::stoi(matches.at("id")));
boost::json::object reply; boost::json::object reply;
reply["status"] = status? 0 : -1; reply["status"] = status ? 0 : -1;
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()}; http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
s.set(http::field::server, BOOST_BEAST_VERSION_STRING); s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
s.set(http::field::content_type, "application/json;charset=UTF-8"); s.set(http::field::content_type, "application/json;charset=UTF-8");
@ -109,63 +107,42 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
}); });
m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
LOG(info) << "webhook: " << request; LOG(info) << "webhook: " << request;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html")); session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
}); });
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
auto corp = Amass::Singleton<CorporationContext>::instance(); auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request); corp->notify(request);
session.reply( session.reply(
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html")); ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
}); });
// clang-format on
m_ioContext = Amass::Singleton<IoContext>::instance<Amass::Construct>(getThreads());
m_timer = std::make_shared<boost::asio::system_timer>(*m_ioContext->ioContext());
alarmTask();
} }
const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path, boost::asio::io_context &Application::ioContext() {
boost::urls::matches_base &matches) const noexcept { return *m_ioContext->ioContext();
}
const Application::RequestHandler *Application::find(boost::urls::segments_encoded_view path,
boost::urls::matches_base &matches) const noexcept {
return m_router->find(path, matches); return m_router->find(path, matches);
} }
std::string_view SharedState::galleryRoot() const noexcept { int Application::exec() {
return m_galleryRoot; LOG(info) << "application start successful ...";
startCheckInterval(*m_ioContext->ioContext(), 2);
m_ioContext->run<IoContext::Mode::Synchronous>();
LOG(info) << "application exit successful ...";
return m_status;
} }
void SharedState::setFileRoot(const std::string_view &root) { void Application::alarmTask() {
if (m_fileRoot == root) return;
m_fileRoot = root;
}
void SharedState::join(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(mutex_);
sessions_.insert(session);
}
void SharedState::leave(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(mutex_);
sessions_.erase(session);
}
void SharedState::send(std::string message) {
// Put the message in a shared pointer so we can re-use it for each client
auto const ss = std::make_shared<std::string const>(std::move(message));
// Make a local list of all the weak pointers representing
// the sessions, so we can do the actual sending without
// holding the mutex:
std::vector<std::weak_ptr<WebSocketSession>> v;
{
std::lock_guard<std::mutex> lock(mutex_);
v.reserve(sessions_.size());
for (auto p : sessions_) v.emplace_back(p->weak_from_this());
}
// For each session in our local list, try to acquire a strong
// pointer. If successful, then send the message on that session.
for (auto const &wp : v)
if (auto sp = wp.lock()) sp->send(ss);
}
void SharedState::alarmTask() {
int hour = 10; int hour = 10;
int minute = 30; int minute = 30;
auto alarmTime = DateTime::currentDateTime(); auto alarmTime = DateTime::currentDateTime();
@ -174,8 +151,8 @@ void SharedState::alarmTask() {
if (std::chrono::system_clock::now() > alarmTime()) { if (std::chrono::system_clock::now() > alarmTime()) {
alarmTime = alarmTime.tomorrow(); alarmTime = alarmTime.tomorrow();
} }
m_timer.expires_at(alarmTime()); m_timer->expires_at(alarmTime());
m_timer.async_wait([this](const boost::system::error_code &error) mutable { m_timer->async_wait([this](const boost::system::error_code &error) mutable {
if (error) { if (error) {
LOG(error) << error.message(); LOG(error) << error.message();
return; return;

45
Server/Application.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef __SETTINGS_H__
#define __SETTINGS_H__
#include "ApplicationSettings.h"
#include "Singleton.h"
#include "router.hpp"
#include <boost/asio/system_timer.hpp>
#include <boost/beast/http/string_body.hpp>
#include <thread>
class HttpSession;
class ChatRoom;
class IoContext;
class Application : public ApplicationSettings<Application>, public std::enable_shared_from_this<Application> {
public:
using Pointer = std::shared_ptr<Application>;
using Request = boost::beast::http::request<boost::beast::http::string_body>;
using RequestHandler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
BUILD_SETTING(std::string, Server, "0.0.0.0");
BUILD_SETTING(uint16_t, Port, 8081);
BUILD_SETTING(uint32_t, Threads, std::thread::hardware_concurrency());
BUILD_SETTING(std::string, DocumentRoot, ".");
INITIALIZE_FIELDS(Server, Port, Threads, DocumentRoot);
Application(const std::string &path);
boost::asio::io_context &ioContext();
int exec();
const RequestHandler *find(boost::urls::segments_encoded_view path,
boost::urls::matches_base &matches) const noexcept;
protected:
void alarmTask();
private:
int m_status = 0;
std::shared_ptr<IoContext> m_ioContext;
std::shared_ptr<boost::urls::router<RequestHandler>> m_router;
std::shared_ptr<boost::asio::system_timer> m_timer;
std::shared_ptr<ChatRoom> m_charRoom;
};
#endif // __SETTINGS_H__

View File

@ -1,16 +1,17 @@
find_package(Boost COMPONENTS program_options json REQUIRED) find_package(Boost COMPONENTS program_options json REQUIRED)
add_subdirectory(ChatRoom)
add_subdirectory(Database) add_subdirectory(Database)
add_subdirectory(WebRTC)
add_executable(Server main.cpp add_executable(Server main.cpp
Application.h Application.cpp
HttpSession.h HttpSession.cpp HttpSession.h HttpSession.cpp
Listener.h Listener.cpp Listener.h Listener.cpp
ResponseUtility.h ResponseUtility.cpp ResponseUtility.h ResponseUtility.cpp
ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp
ServiceManager.h ServiceManager.h
SharedState.h SharedState.cpp
UdpServer.h UdpServer.cpp UdpServer.h UdpServer.cpp
WebsocketSession.h WebsocketSession.cpp
WeChatContext/CorporationContext.h WeChatContext/CorporationContext.cpp WeChatContext/CorporationContext.h WeChatContext/CorporationContext.cpp
WeChatContext/WeChatContext.h WeChatContext/WeChatContext.cpp WeChatContext/WeChatContext.h WeChatContext/WeChatContext.cpp
WeChatContext/WeChatSession.h WeChatContext/WeChatSession.cpp WeChatContext/WeChatSession.h WeChatContext/WeChatSession.cpp

View File

@ -0,0 +1,10 @@
add_library(ChatRoom
ChatRoom.h ChatRoom.cpp
WebSocketChatSession.h WebSocketChatSession.cpp
)
target_link_libraries(ChatRoom
PUBLIC Universal
)

View File

@ -0,0 +1,31 @@
#include "ChatRoom.h"
#include "WebSocketChatSession.h"
void ChatRoom::join(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(m_mutex);
m_sessions.insert(session);
}
void ChatRoom::leave(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(m_mutex);
m_sessions.erase(session);
}
void ChatRoom::send(std::string message) {
auto const ss = std::make_shared<std::string const>(std::move(message));
// Make a local list of all the weak pointers representing
// the sessions, so we can do the actual sending without
// holding the mutex:
std::vector<std::weak_ptr<WebSocketSession>> v;
{
std::lock_guard<std::mutex> lock(m_mutex);
v.reserve(m_sessions.size());
for (auto p : m_sessions) v.emplace_back(p->weak_from_this());
}
// For each session in our local list, try to acquire a strong
// pointer. If successful, then send the message on that session.
for (auto const &wp : v)
if (auto sp = wp.lock()) sp->send(ss);
}

View File

@ -0,0 +1,23 @@
#ifndef __CHATROOM_H__
#define __CHATROOM_H__
#include "Singleton.h"
#include <mutex>
#include <unordered_set>
class WebSocketSession;
class ChatRoom {
public:
void join(WebSocketSession *session);
void leave(WebSocketSession *session);
/**
* @brief Broadcast a message to all websocket client sessions
*/
void send(std::string message);
private:
std::mutex m_mutex;
std::unordered_set<WebSocketSession *> m_sessions;
};
#endif // __CHATROOM_H__

View File

@ -1,45 +1,50 @@
#include "WebsocketSession.h" #include "WebSocketChatSession.h"
#include "ChatRoom.h"
#include "StringUtility.h"
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <iostream> #include <iostream>
WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state) WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket) : m_ws(std::move(socket)) {
: m_ws(std::move(socket)), m_state(state) {} }
WebSocketSession::~WebSocketSession() { WebSocketSession::~WebSocketSession() {
// Remove this session from the list of active sessions auto chatRoom = Amass::Singleton<ChatRoom>::instance();
m_state->leave(this); chatRoom->leave(this);
} }
void WebSocketSession::onAccept(boost::beast::error_code ec) { void WebSocketSession::onAccept(boost::beast::error_code ec) {
// Handle the error, if any
if (ec) { if (ec) {
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return; if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "accept: " << ec.message() << "\n"; std::cerr << "accept: " << ec.message() << "\n";
return; return;
} }
LOG(info) << "accept websocket target: " << m_target;
if (m_target.find("/webrtc") == 0) {
auto splits = Amass::StringUtility::split(m_target, "/");
if (!splits.empty()) m_id = splits.back();
}
// Add this session to the list of active sessions auto chatRoom = Amass::Singleton<ChatRoom>::instance();
m_state->join(this); chatRoom->join(this);
// Read a message // Read a message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this())); m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this()));
} }
void WebSocketSession::on_read(boost::beast::error_code ec, std::size_t) { void WebSocketSession::on_read(boost::beast::error_code ec, std::size_t) {
// Handle the error, if any
if (ec) { if (ec) {
// Don't report these // Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return; if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
LOG(error) << "read: " << ec.message(); LOG(error) << "read: " << ec.message();
return; return;
} }
LOG(info) << boost::beast::buffers_to_string(m_buffer.data()); auto message = boost::beast::buffers_to_string(m_buffer.data());
// Send to all connections LOG(info) << message;
m_state->send(boost::beast::buffers_to_string(m_buffer.data())); auto chatRoom = Amass::Singleton<ChatRoom>::instance();
chatRoom->send(message); // Send to all connections
// Clear the buffer m_buffer.consume(m_buffer.size()); // Clear the buffer
m_buffer.consume(m_buffer.size());
// Read another message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this())); m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this()));
} }

View File

@ -2,22 +2,18 @@
#define WEBSOCKETSESSION_H #define WEBSOCKETSESSION_H
#include "BoostLog.h" #include "BoostLog.h"
#include "SharedState.h"
#include <boost/beast.hpp> #include <boost/beast.hpp>
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
class SharedState;
/** /**
* @brief Represents an active WebSocket connection to the server * @brief Represents an active WebSocket connection to the server
*/ */
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> { class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
public: public:
WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state); WebSocketSession(boost::asio::ip::tcp::socket &&socket);
~WebSocketSession(); ~WebSocketSession();
@ -27,7 +23,7 @@ public:
using namespace boost::beast::websocket; using namespace boost::beast::websocket;
// Set suggested timeout settings for the websocket // Set suggested timeout settings for the websocket
m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server)); m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server));
m_target = request.target();
// Set a decorator to change the Server of the handshake // Set a decorator to change the Server of the handshake
m_ws.set_option(stream_base::decorator([](response_type &response) { m_ws.set_option(stream_base::decorator([](response_type &response) {
response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi"); response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi");
@ -47,10 +43,11 @@ protected:
void onSend(std::shared_ptr<std::string const> const &ss); void onSend(std::shared_ptr<std::string const> const &ss);
private: private:
std::string m_target;
boost::beast::flat_buffer m_buffer; boost::beast::flat_buffer m_buffer;
boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws; boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws;
std::shared_ptr<SharedState> m_state;
std::vector<std::shared_ptr<std::string const>> m_queue; std::vector<std::shared_ptr<std::string const>> m_queue;
std::string m_id;
}; };
#endif // WEBSOCKETSESSION_H #endif // WEBSOCKETSESSION_H

View File

@ -1,14 +1,12 @@
#include "HttpSession.h" #include "HttpSession.h"
#include "WebsocketSession.h" #include "Application.h"
#include <boost/config.hpp> #include <boost/config.hpp>
#include <boost/url/parse_path.hpp> #include <boost/url/parse_path.hpp>
#include <boost/url/url_view.hpp> #include <boost/url/url_view.hpp>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<SharedState> &state) HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket) : m_stream(std::move(socket)) {
: m_stream(std::move(socket)), m_state(state) {
// m_buffer.reserve(1000 * 1000 * 1000);
} }
void HttpSession::run() { void HttpSession::run() {
@ -62,22 +60,17 @@ void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
} }
auto &request = m_parser->get(); auto &request = m_parser->get();
// See if it is a WebSocket Upgrade
if (websocket::is_upgrade(request)) {
// Create a websocket session, transferring ownership
// of both the socket and the HTTP request.
auto session = std::make_shared<WebSocketSession>(m_stream.release_socket(), m_state);
session->run(m_parser->release());
return;
}
auto path = boost::urls::parse_path(request.target()); auto path = boost::urls::parse_path(request.target());
if (!path) { if (!path) {
LOG(error) << request.target() << "failed, error: " << path.error().message(); LOG(error) << request.target() << "failed, error: " << path.error().message();
errorReply(request, http::status::bad_request, "Illegal request-target"); errorReply(request, http::status::bad_request, "Illegal request-target");
return; return;
} }
auto application = Amass::Singleton<Application>::instance();
boost::urls::matches matches; boost::urls::matches matches;
auto handler = m_state->find(*path, matches); auto handler = application->find(*path, matches);
if (handler) { if (handler) {
(*handler)(*this, request, matches); (*handler)(*this, request, matches);
} else { } else {

View File

@ -1,7 +1,6 @@
#ifndef HTTPSESSION_H #ifndef HTTPSESSION_H
#define HTTPSESSION_H #define HTTPSESSION_H
#include "SharedState.h"
#include "boost/beast.hpp" #include "boost/beast.hpp"
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
@ -18,7 +17,7 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
public: public:
using Request = boost::beast::http::request<boost::beast::http::string_body>; using Request = boost::beast::http::request<boost::beast::http::string_body>;
HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state); HttpSession(boost::asio::ip::tcp::socket &&socket);
template <typename Response> template <typename Response>
void reply(Response &&response) { void reply(Response &&response) {
using ResponseType = typename std::decay_t<decltype(response)>; using ResponseType = typename std::decay_t<decltype(response)>;
@ -35,7 +34,6 @@ public:
private: private:
boost::beast::tcp_stream m_stream; boost::beast::tcp_stream m_stream;
boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint32_t>::max()}; boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint32_t>::max()};
SharedStatePtr m_state;
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser; std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
}; };

View File

@ -3,9 +3,8 @@
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <iostream> #include <iostream>
Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint, Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint)
std::shared_ptr<SharedState> const &state) : m_ioContext(ioc), m_acceptor(ioc) {
: m_ioContext(ioc), m_acceptor(ioc), m_state(state) {
boost::beast::error_code ec; boost::beast::error_code ec;
// Open the acceptor // Open the acceptor
@ -57,7 +56,7 @@ void Listener::onAccept(boost::beast::error_code ec, std::shared_ptr<boost::asio
std::cerr << "accept: " << ec.message() << "\n"; std::cerr << "accept: " << ec.message() << "\n";
} else { // Launch a new session for this connection } else { // Launch a new session for this connection
auto session = std::make_shared<HttpSession>(std::move(*socket), m_state); auto session = std::make_shared<HttpSession>(std::move(*socket));
session->run(); session->run();
} }
startAccept(); startAccept();

View File

@ -6,19 +6,12 @@
#include <memory> #include <memory>
#include <string> #include <string>
class SharedState;
// Accepts incoming connections and launches the sessions
class Listener : public std::enable_shared_from_this<Listener> { class Listener : public std::enable_shared_from_this<Listener> {
public: public:
Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint, Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint);
std::shared_ptr<SharedState> const &state);
// Start accepting incoming connections // Start accepting incoming connections
void startAccept(); void startAccept();
inline std::shared_ptr<SharedState> state() const {
return m_state;
}
protected: protected:
void fail(boost::beast::error_code ec, char const *what); void fail(boost::beast::error_code ec, char const *what);
@ -27,7 +20,6 @@ protected:
private: private:
boost::asio::io_context &m_ioContext; boost::asio::io_context &m_ioContext;
boost::asio::ip::tcp::acceptor m_acceptor; boost::asio::ip::tcp::acceptor m_acceptor;
std::shared_ptr<SharedState> m_state;
}; };
#endif // LISTENER_H #endif // LISTENER_H

View File

@ -1,8 +1,8 @@
#ifndef SERVICELOGIC_H #ifndef SERVICELOGIC_H
#define SERVICELOGIC_H #define SERVICELOGIC_H
#include "Application.h"
#include "ResponseUtility.h" #include "ResponseUtility.h"
#include "SharedState.h"
#include "StringUtility.h" #include "StringUtility.h"
#include <boost/beast/http/message.hpp> #include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp> #include <boost/beast/http/string_body.hpp>
@ -17,16 +17,7 @@ using StringRequest = boost::beast::http::request<boost::beast::http::string_bod
namespace ServiceLogic { namespace ServiceLogic {
template <class Send> template <class Send>
static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send); static void onWechat(const Application::Pointer &app, StringRequest &&request, Send &&send);
template <class Send>
static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix);
template <class Send>
static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send);
template <class Send>
static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&send);
// Returns a server error response // Returns a server error response
boost::beast::http::response<boost::beast::http::string_body> boost::beast::http::response<boost::beast::http::string_body>

View File

@ -15,7 +15,7 @@
namespace ServiceLogic { namespace ServiceLogic {
template <class Send> template <class Send>
static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) { static void onWechat(const Application::Pointer &app, const StringRequest &request, Send &&send) {
using namespace boost::beast; using namespace boost::beast;
boost::urls::url url(request.target()); boost::urls::url url(request.target());
auto context = Amass::Singleton<WeChatContext>::instance(); auto context = Amass::Singleton<WeChatContext>::instance();

View File

@ -1,65 +0,0 @@
#ifndef SHAREDSTATE_H
#define SHAREDSTATE_H
#include "router.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/asio/system_timer.hpp>
#include <boost/beast/http/string_body.hpp>
#include <boost/smart_ptr.hpp>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_set>
class HttpSession;
class WebSocketSession;
// Represents the shared server state
class SharedState : public std::enable_shared_from_this<SharedState> {
// This mutex synchronizes all access to sessions_
std::mutex mutex_;
// Keep a list of all the connected clients
std::unordered_set<WebSocketSession *> sessions_;
public:
using Request = boost::beast::http::request<boost::beast::http::string_body>;
using Handler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
SharedState(boost::asio::io_context &ioContext, std::string doc_root);
const Handler *find(boost::urls::segments_encoded_view path, boost::urls::matches_base &matches) const noexcept;
inline std::string_view docRoot() const noexcept {
return m_docRoot;
}
std::string_view galleryRoot() const noexcept;
inline std::string_view fileRoot() const {
return m_fileRoot;
}
void setFileRoot(const std::string_view &root);
void join(WebSocketSession *session);
void leave(WebSocketSession *session);
/**
* @brief Broadcast a message to all websocket client sessions
*/
void send(std::string message);
protected:
void alarmTask();
private:
boost::asio::io_context &m_ioContext;
std::shared_ptr<boost::urls::router<Handler>> m_router;
std::string m_docRoot;
std::string m_galleryRoot = "/root/photos";
std::string m_fileRoot;
boost::asio::system_timer m_timer;
};
using SharedStatePtr = std::shared_ptr<SharedState>;
#endif // SHAREDSTATE_H

View File

@ -0,0 +1,8 @@
add_library(WebRTC
WebSocketSignalSession.h WebSocketSignalSession.cpp
)
target_link_libraries(WebRTC
PUBLIC Universal
)

View File

@ -0,0 +1,19 @@
#include "SignalServer.h"
void SignalServer::join(const std::string &id, WebSocketSignalSession *client) {
m_clients.insert({id, client});
}
void SignalServer::leave(const std::string &id) {
if (m_clients.contains(id)) {
m_clients.erase(id);
}
}
WebSocketSignalSession *SignalServer::client(const std::string &id) {
WebSocketSignalSession *ret;
if (m_clients.contains(id)) {
ret = m_clients.at(id);
}
return ret;
}

View File

@ -0,0 +1,20 @@
#ifndef __SIGNALSERVER_H__
#define __SIGNALSERVER_H__
#include "Singleton.h"
#include <string>
#include <unordered_map>
class WebSocketSignalSession;
class SignalServer {
public:
void join(const std::string &id, WebSocketSignalSession *client);
void leave(const std::string &id);
WebSocketSignalSession *client(const std::string &id);
private:
std::unordered_map<std::string, WebSocketSignalSession *> m_clients;
};
#endif // __SIGNALSERVER_H__

View File

@ -0,0 +1,102 @@
#include "WebSocketSignalSession.h"
#include "SignalServer.h"
#include "StringUtility.h"
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
WebSocketSignalSession::WebSocketSignalSession(boost::asio::ip::tcp::socket &&socket) : m_ws(std::move(socket)) {
}
WebSocketSignalSession::~WebSocketSignalSession() {
auto server = Amass::Singleton<SignalServer>::instance();
server->leave(m_id);
}
void WebSocketSignalSession::onAccept(boost::beast::error_code ec) {
if (ec) {
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "accept: " << ec.message() << "\n";
return;
}
LOG(info) << "accept websocket target: " << m_target;
if (m_target.find("/webrtc") == 0) {
auto splits = Amass::StringUtility::split(m_target, "/");
if (!splits.empty()) m_id = splits.back();
}
auto server = Amass::Singleton<SignalServer>::instance();
server->join(m_id, this);
// Read a message
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSignalSession::on_read, shared_from_this()));
}
void WebSocketSignalSession::on_read(boost::beast::error_code ec, std::size_t) {
// Handle the error, if any
if (ec) {
// Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
LOG(error) << "read: " << ec.message();
return;
}
auto message = boost::beast::buffers_to_string(m_buffer.data());
LOG(info) << message;
auto rootObject = boost::json::parse(message);
auto &root = rootObject.as_object();
if (root.contains("id")) {
if (!root.at("id").is_string()) {
LOG(warning) << "wrong format.";
m_ws.close(boost::beast::websocket::close_code::normal);
return;
}
auto server = Amass::Singleton<SignalServer>::instance();
auto destinationId = std::string(root["id"].as_string());
auto destination = server->client(destinationId);
if (destination == nullptr) {
LOG(info) << "client " << destinationId << " not found.";
} else {
root["id"] = m_id;
auto reply = std::make_shared<std::string>(boost::json::serialize(root));
destination->send(reply);
}
}
m_buffer.consume(m_buffer.size()); // Clear the buffer
m_ws.async_read(m_buffer, boost::beast::bind_front_handler(&WebSocketSignalSession::on_read, shared_from_this()));
}
void WebSocketSignalSession::send(std::shared_ptr<std::string const> const &ss) {
// Post our work to the strand, this ensures
// that the members of `this` will not be
// accessed concurrently.
m_ws.text();
boost::asio::post(m_ws.get_executor(),
boost::beast::bind_front_handler(&WebSocketSignalSession::onSend, shared_from_this(), ss));
}
void WebSocketSignalSession::onSend(std::shared_ptr<std::string const> const &ss) {
// Always add to queue
m_queue.push_back(ss);
// Are we already writing?
if (m_queue.size() > 1) return;
// We are not currently writing, so send this immediately
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSignalSession::on_write, shared_from_this()));
}
void WebSocketSignalSession::on_write(boost::beast::error_code ec, std::size_t) {
// Handle the error, if any
if (ec) {
// Don't report these
if (ec == boost::asio::error::operation_aborted || ec == boost::beast::websocket::error::closed) return;
std::cerr << "write: " << ec.message() << "\n";
return;
}
// Remove the string from the queue
m_queue.erase(m_queue.begin());
if (!m_queue.empty())
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
boost::beast::bind_front_handler(&WebSocketSignalSession::on_write, shared_from_this()));
}

View File

@ -0,0 +1,53 @@
#ifndef __WEBSOCKETSIGNALSESSION_H__
#define __WEBSOCKETSIGNALSESSION_H__
#include "BoostLog.h"
#include <boost/beast.hpp>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
/**
* @brief Represents an active WebSocket connection to the server
*/
class WebSocketSignalSession : public std::enable_shared_from_this<WebSocketSignalSession> {
public:
WebSocketSignalSession(boost::asio::ip::tcp::socket &&socket);
~WebSocketSignalSession();
template <class Body, class Allocator>
void run(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> request) {
using namespace boost::beast::http;
using namespace boost::beast::websocket;
// Set suggested timeout settings for the websocket
m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server));
m_target = request.target();
// Set a decorator to change the Server of the handshake
m_ws.set_option(stream_base::decorator([](response_type &response) {
response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi");
}));
// LOG(info) << request.base().target(); //get path
// Accept the websocket handshake
m_ws.async_accept(request, [self{shared_from_this()}](boost::beast::error_code ec) { self->onAccept(ec); });
}
// Send a message
void send(std::shared_ptr<std::string const> const &ss);
protected:
void onAccept(boost::beast::error_code ec);
void on_read(boost::beast::error_code ec, std::size_t bytes_transferred);
void on_write(boost::beast::error_code ec, std::size_t bytes_transferred);
void onSend(std::shared_ptr<std::string const> const &ss);
private:
std::string m_target;
boost::beast::flat_buffer m_buffer;
boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws;
std::vector<std::shared_ptr<std::string const>> m_queue;
std::string m_id;
};
#endif // __WEBSOCKETSIGNALSESSION_H__

View File

@ -1,9 +1,10 @@
#include "Application.h"
#include "BoostLog.h" #include "BoostLog.h"
#include "Database.h"
#include "IoContext.h" #include "IoContext.h"
#include "Listener.h" #include "Listener.h"
#include "ProxyListener.h" #include "ProxyListener.h"
#include "ServiceManager.h" #include "ServiceManager.h"
#include "SharedState.h"
#include "UdpServer.h" #include "UdpServer.h"
#include "WeChatContext/CorporationContext.h" #include "WeChatContext/CorporationContext.h"
#include "WeChatContext/WeChatContext.h" #include "WeChatContext/WeChatContext.h"
@ -11,13 +12,11 @@
#include <boost/property_tree/ini_parser.hpp> #include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp> #include <boost/property_tree/ptree.hpp>
#include <filesystem> #include <filesystem>
#include "Database.h"
void initSettings();
int main(int argc, char const *argv[]) { int main(int argc, char const *argv[]) {
using namespace Amass;
boost::log::initialize("logs/HttpServer"); boost::log::initialize("logs/HttpServer");
auto manager = Amass::Singleton<ServiceManager>::instance<Amass::Construct>(); auto manager = Singleton<ServiceManager>::instance<Construct>();
boost::program_options::options_description description("Allowed options"); boost::program_options::options_description description("Allowed options");
// clang-format off // clang-format off
@ -55,115 +54,53 @@ int main(int argc, char const *argv[]) {
std::filesystem::current_path(prefix, error); std::filesystem::current_path(prefix, error);
LOG_IF(fatal, error) << "cannot set current path,reason: " << error.message(); LOG_IF(fatal, error) << "cannot set current path,reason: " << error.message();
} }
initSettings();
auto database = Amass::Singleton<Database>::instance<Amass::Construct>(); auto application = Singleton<Application>::instance<Construct>("settings.ini");
auto database = Singleton<Database>::instance<Construct>();
database->open("database.sqlite"); database->open("database.sqlite");
std::string server = "0.0.0.0"; if (!std::filesystem::exists(application->getDocumentRoot())) {
LOG(fatal) << "document root: " << application->getDocumentRoot() << " is not exists...";
boost::property_tree::ptree ptree;
boost::property_tree::read_ini("settings.ini", ptree);
if (ptree.count("server") > 0) {
server = ptree.get<std::string>("server");
}
auto port = ptree.get_optional<uint16_t>("port");
auto docRoot = ptree.get<std::string>("docRoot");
if (docRoot.empty()) {
LOG(fatal) << "please set docRoot.";
std::exit(101);
} else if (!std::filesystem::exists(docRoot)) {
LOG(fatal) << "document root: " << docRoot << " is not exists...";
std::exit(102); std::exit(102);
} }
BOOST_ASSERT_MSG(!application->getServer().empty(), "server.empty() == true");
auto fileRoot = ptree.get<std::string>("fileRoot"); auto address = boost::asio::ip::make_address(application->getServer());
if (fileRoot.empty()) { auto listener = std::make_shared<Listener>(application->ioContext(),
LOG(fatal) << "please set fileRoot."; boost::asio::ip::tcp::endpoint{address, application->getPort()});
std::exit(103);
} else if (!std::filesystem::exists(fileRoot)) {
LOG(fatal) << "file root: " << fileRoot << " is not exists...";
std::exit(104);
}
if (!port) {
LOG(fatal) << "port is not a number.";
std::exit(255);
}
auto threads = ptree.get<unsigned int>("threads");
if (threads <= 0 || threads > std::thread::hardware_concurrency()) threads = std::thread::hardware_concurrency();
BOOST_ASSERT_MSG(!server.empty(), "server.empty() == true");
auto ioContext = Amass::Singleton<IoContext>::instance<Amass::Construct>(threads);
auto address = boost::asio::ip::make_address(server);
auto listener = std::make_shared<Listener>(*ioContext->ioContext(), boost::asio::ip::tcp::endpoint{address, *port},
std::make_shared<SharedState>(*ioContext->ioContext(), docRoot));
listener->startAccept(); listener->startAccept();
auto state = listener->state(); auto wechatContext = Singleton<WeChatContext>::instance<Construct>(application->ioContext());
state->setFileRoot(fileRoot); auto corpContext = Singleton<CorporationContext>::instance<Construct>(application->ioContext());
auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext()); LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency()
auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext()); << ",threads: " << application->getThreads();
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << threads;
LOG(info) << "working directory: " << prefix.generic_string(); LOG(info) << "working directory: " << prefix.generic_string();
LOG(info) << "server: " << server << ",port: " << *port; LOG(info) << "server: " << application->getServer() << ",port: " << application->getPort();
LOG(info) << "document root: " << state->docRoot(); LOG(info) << "document root: " << application->getDocumentRoot();
// Capture SIGINT and SIGTERM to perform a clean shutdown // Capture SIGINT and SIGTERM to perform a clean shutdown
#ifndef WIN32 #ifndef WIN32
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM, SIGHUP); boost::asio::signal_set signals(application->ioContext(), SIGINT, SIGTERM, SIGHUP);
#else #else
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM); boost::asio::signal_set signals(application->ioContext(), SIGINT, SIGTERM);
#endif #endif
signals.async_wait([&ioContext](boost::system::error_code const &, int signal) { signals.async_wait([&application](boost::system::error_code const &, int signal) {
// Stop the io_context. This will cause run() // Stop the io_context. This will cause run()
// to return immediately, eventually destroying the // to return immediately, eventually destroying the
// io_context and any remaining handlers in it. // io_context and any remaining handlers in it.
LOG(info) << "capture " << (signal == SIGINT ? "SIGINT" : "SIGTERM") << ",stop!"; LOG(info) << "capture " << (signal == SIGINT ? "SIGINT" : "SIGTERM") << ",stop!";
ioContext->ioContext()->stop(); application->ioContext().stop();
}); });
auto udpServer = std::make_shared<UdpServer>(*ioContext->ioContext()); auto udpServer = std::make_shared<UdpServer>(application->ioContext());
auto proxyAddress = boost::asio::ip::make_address(server); using namespace boost::asio::ip;
auto proxyAddress = make_address(application->getServer());
uint16_t proxyPort = 41091; uint16_t proxyPort = 41091;
auto proxy = std::make_shared<ProxyListener>(*ioContext->ioContext(), auto proxy = std::make_shared<ProxyListener>(application->ioContext(), tcp::endpoint{proxyAddress, proxyPort});
boost::asio::ip::tcp::endpoint{proxyAddress, proxyPort});
boost::system::error_code perror; boost::system::error_code perror;
proxy->run(perror); proxy->run(perror);
return application->exec();
LOG(info) << "server start successful ...";
ioContext->run<IoContext::Mode::Synchronous>();
LOG(info) << "server exit successful ...";
return EXIT_SUCCESS;
}
void initSettings() {
boost::property_tree::ptree ptree;
if (std::filesystem::exists(std::filesystem::path("settings.ini")))
boost::property_tree::read_ini("settings.ini", ptree);
if (ptree.find("server") == ptree.not_found()) {
ptree.put("server", "0.0.0.0");
}
if (ptree.find("port") == ptree.not_found()) {
ptree.put("port", 8083);
}
if (ptree.find("docRoot") == ptree.not_found()) {
ptree.put("docRoot", ".");
}
if (ptree.find("fileRoot") == ptree.not_found()) {
ptree.put("fileRoot", ".");
}
if (ptree.find("threads") == ptree.not_found()) {
ptree.put("threads", std::thread::hardware_concurrency());
}
boost::property_tree::write_ini("settings.ini", ptree);
} }