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_REQUIRED ON)
set(OPENSSL_LIBRARIES ssl crypto)
include(FetchContent)
FetchContent_Declare(Kylin
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 "DateTime.h"
#include "HttpSession.h"
#include "IoContext.h"
#include "ServiceLogic.h"
#include "ServiceManager.h"
#include "WeChatContext/CorporationContext.h"
#include "WebsocketSession.h"
SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_root)
: m_ioContext(ioContext), m_router{std::make_shared<boost::urls::router<Handler>>()},
m_docRoot(std::move(doc_root)), m_timer(ioContext) {
alarmTask();
Application::Application(const std::string &path)
: ApplicationSettings(path), m_router{std::make_shared<boost::urls::router<RequestHandler>>()} {
// clang-format off
m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
using namespace boost::beast;
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"));
return;
}
std::string path = ResponseUtility::pathCat(m_docRoot, target);
std::string path = ResponseUtility::pathCat(getDocumentRoot(), target);
if (target.back() == '/') path.append("index.html");
if (std::filesystem::is_directory(path)) path.append("/index.html");
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));
});
m_router->insert("/wechat/{session*}",
[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
ServiceLogic::onWechat(shared_from_this(), request,
[&session](auto &&response) { session.reply(std::move(response)); });
});
m_router->insert("/wechat/{session*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
ServiceLogic::onWechat(shared_from_this(), request,
[&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;
auto database = Amass::Singleton<Database>::instance();
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();
}
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;
reply["status"] = ret ? 0 : -1;
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));
});
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;
LOG(info) << "delete task: " << matches.at("id");
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;
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()};
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
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) {
LOG(info) << "webhook: " << request;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
});
LOG(info) << "webhook: " << request;
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) {
auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request);
session.reply(
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
});
auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request);
session.reply(
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::urls::matches_base &matches) const noexcept {
boost::asio::io_context &Application::ioContext() {
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);
}
std::string_view SharedState::galleryRoot() const noexcept {
return m_galleryRoot;
int Application::exec() {
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) {
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() {
void Application::alarmTask() {
int hour = 10;
int minute = 30;
auto alarmTime = DateTime::currentDateTime();
@ -174,8 +151,8 @@ void SharedState::alarmTask() {
if (std::chrono::system_clock::now() > alarmTime()) {
alarmTime = alarmTime.tomorrow();
}
m_timer.expires_at(alarmTime());
m_timer.async_wait([this](const boost::system::error_code &error) mutable {
m_timer->expires_at(alarmTime());
m_timer->async_wait([this](const boost::system::error_code &error) mutable {
if (error) {
LOG(error) << error.message();
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)
add_subdirectory(ChatRoom)
add_subdirectory(Database)
add_subdirectory(WebRTC)
add_executable(Server main.cpp
Application.h Application.cpp
HttpSession.h HttpSession.cpp
Listener.h Listener.cpp
ResponseUtility.h ResponseUtility.cpp
ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp
ServiceManager.h
SharedState.h SharedState.cpp
UdpServer.h UdpServer.cpp
WebsocketSession.h WebsocketSession.cpp
WeChatContext/CorporationContext.h WeChatContext/CorporationContext.cpp
WeChatContext/WeChatContext.h WeChatContext/WeChatContext.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>
WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state)
: m_ws(std::move(socket)), m_state(state) {}
WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket) : m_ws(std::move(socket)) {
}
WebSocketSession::~WebSocketSession() {
// Remove this session from the list of active sessions
m_state->leave(this);
auto chatRoom = Amass::Singleton<ChatRoom>::instance();
chatRoom->leave(this);
}
void WebSocketSession::onAccept(boost::beast::error_code ec) {
// Handle the error, if any
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();
}
// Add this session to the list of active sessions
m_state->join(this);
auto chatRoom = Amass::Singleton<ChatRoom>::instance();
chatRoom->join(this);
// Read a message
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) {
// 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;
}
LOG(info) << boost::beast::buffers_to_string(m_buffer.data());
// Send to all connections
m_state->send(boost::beast::buffers_to_string(m_buffer.data()));
auto message = boost::beast::buffers_to_string(m_buffer.data());
LOG(info) << message;
auto chatRoom = Amass::Singleton<ChatRoom>::instance();
chatRoom->send(message); // Send to all connections
// Clear the buffer
m_buffer.consume(m_buffer.size());
// Read another message
m_buffer.consume(m_buffer.size()); // Clear the buffer
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
#include "BoostLog.h"
#include "SharedState.h"
#include <boost/beast.hpp>
#include <cstdlib>
#include <memory>
#include <string>
#include <vector>
class SharedState;
/**
* @brief Represents an active WebSocket connection to the server
*/
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
public:
WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
WebSocketSession(boost::asio::ip::tcp::socket &&socket);
~WebSocketSession();
@ -27,7 +23,7 @@ public:
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");
@ -47,10 +43,11 @@ protected:
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::shared_ptr<SharedState> m_state;
std::vector<std::shared_ptr<std::string const>> m_queue;
std::string m_id;
};
#endif // WEBSOCKETSESSION_H

View File

@ -1,14 +1,12 @@
#include "HttpSession.h"
#include "WebsocketSession.h"
#include "Application.h"
#include <boost/config.hpp>
#include <boost/url/parse_path.hpp>
#include <boost/url/url_view.hpp>
#include <iostream>
#include <limits>
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<SharedState> &state)
: m_stream(std::move(socket)), m_state(state) {
// m_buffer.reserve(1000 * 1000 * 1000);
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket) : m_stream(std::move(socket)) {
}
void HttpSession::run() {
@ -62,22 +60,17 @@ void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
}
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());
if (!path) {
LOG(error) << request.target() << "failed, error: " << path.error().message();
errorReply(request, http::status::bad_request, "Illegal request-target");
return;
}
auto application = Amass::Singleton<Application>::instance();
boost::urls::matches matches;
auto handler = m_state->find(*path, matches);
auto handler = application->find(*path, matches);
if (handler) {
(*handler)(*this, request, matches);
} else {

View File

@ -1,7 +1,6 @@
#ifndef HTTPSESSION_H
#define HTTPSESSION_H
#include "SharedState.h"
#include "boost/beast.hpp"
#include <cstdlib>
#include <memory>
@ -18,7 +17,7 @@ class HttpSession : public std::enable_shared_from_this<HttpSession> {
public:
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>
void reply(Response &&response) {
using ResponseType = typename std::decay_t<decltype(response)>;
@ -35,7 +34,6 @@ public:
private:
boost::beast::tcp_stream m_stream;
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;
};

View File

@ -3,9 +3,8 @@
#include <boost/asio.hpp>
#include <iostream>
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_state(state) {
Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint)
: m_ioContext(ioc), m_acceptor(ioc) {
boost::beast::error_code ec;
// 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";
} 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();
}
startAccept();

View File

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

View File

@ -1,8 +1,8 @@
#ifndef SERVICELOGIC_H
#define SERVICELOGIC_H
#include "Application.h"
#include "ResponseUtility.h"
#include "SharedState.h"
#include "StringUtility.h"
#include <boost/beast/http/message.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 {
template <class Send>
static void onGetBlogList(SharedStatePtr state, 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);
static void onWechat(const Application::Pointer &app, StringRequest &&request, Send &&send);
// Returns a server error response
boost::beast::http::response<boost::beast::http::string_body>

View File

@ -15,7 +15,7 @@
namespace ServiceLogic {
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;
boost::urls::url url(request.target());
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 "Database.h"
#include "IoContext.h"
#include "Listener.h"
#include "ProxyListener.h"
#include "ServiceManager.h"
#include "SharedState.h"
#include "UdpServer.h"
#include "WeChatContext/CorporationContext.h"
#include "WeChatContext/WeChatContext.h"
@ -11,13 +12,11 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <filesystem>
#include "Database.h"
void initSettings();
int main(int argc, char const *argv[]) {
using namespace Amass;
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");
// clang-format off
@ -55,115 +54,53 @@ int main(int argc, char const *argv[]) {
std::filesystem::current_path(prefix, error);
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");
std::string server = "0.0.0.0";
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...";
if (!std::filesystem::exists(application->getDocumentRoot())) {
LOG(fatal) << "document root: " << application->getDocumentRoot() << " is not exists...";
std::exit(102);
}
BOOST_ASSERT_MSG(!application->getServer().empty(), "server.empty() == true");
auto fileRoot = ptree.get<std::string>("fileRoot");
if (fileRoot.empty()) {
LOG(fatal) << "please set fileRoot.";
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));
auto address = boost::asio::ip::make_address(application->getServer());
auto listener = std::make_shared<Listener>(application->ioContext(),
boost::asio::ip::tcp::endpoint{address, application->getPort()});
listener->startAccept();
auto state = listener->state();
state->setFileRoot(fileRoot);
auto wechatContext = Singleton<WeChatContext>::instance<Construct>(application->ioContext());
auto corpContext = Singleton<CorporationContext>::instance<Construct>(application->ioContext());
auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext());
auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext());
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << threads;
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency()
<< ",threads: " << application->getThreads();
LOG(info) << "working directory: " << prefix.generic_string();
LOG(info) << "server: " << server << ",port: " << *port;
LOG(info) << "document root: " << state->docRoot();
LOG(info) << "server: " << application->getServer() << ",port: " << application->getPort();
LOG(info) << "document root: " << application->getDocumentRoot();
// Capture SIGINT and SIGTERM to perform a clean shutdown
#ifndef WIN32
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM, SIGHUP);
boost::asio::signal_set signals(application->ioContext(), SIGINT, SIGTERM, SIGHUP);
#else
boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM);
boost::asio::signal_set signals(application->ioContext(), SIGINT, SIGTERM);
#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()
// to return immediately, eventually destroying the
// io_context and any remaining handlers in it.
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;
auto proxy = std::make_shared<ProxyListener>(*ioContext->ioContext(),
boost::asio::ip::tcp::endpoint{proxyAddress, proxyPort});
auto proxy = std::make_shared<ProxyListener>(application->ioContext(), tcp::endpoint{proxyAddress, proxyPort});
boost::system::error_code perror;
proxy->run(perror);
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);
return application->exec();
}