Update server source,
This commit is contained in:
parent
02ccd80cd9
commit
cd77b8d1e1
@ -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
|
||||||
|
@ -1,214 +1,191 @@
|
|||||||
#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 "ServiceLogic.h"
|
#include "IoContext.h"
|
||||||
#include "ServiceManager.h"
|
#include "ServiceLogic.h"
|
||||||
#include "WeChatContext/CorporationContext.h"
|
#include "ServiceManager.h"
|
||||||
#include "WebsocketSession.h"
|
#include "WeChatContext/CorporationContext.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());
|
auto target = view.path();
|
||||||
auto target = view.path();
|
LOG(info) << target;
|
||||||
LOG(info) << target;
|
if (target.find("..") != boost::beast::string_view::npos) {
|
||||||
if (target.find("..") != boost::beast::string_view::npos) {
|
session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
|
||||||
session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
std::string path = ResponseUtility::pathCat(getDocumentRoot(), target);
|
||||||
std::string path = ResponseUtility::pathCat(m_docRoot, 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;
|
http::file_body::value_type body;
|
||||||
http::file_body::value_type body;
|
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
|
||||||
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
|
if (ec == boost::beast::errc::no_such_file_or_directory) {
|
||||||
if (ec == boost::beast::errc::no_such_file_or_directory) {
|
std::ostringstream oss;
|
||||||
std::ostringstream oss;
|
oss << "The resource '" << target << "' was not found.";
|
||||||
oss << "The resource '" << target << "' was not found.";
|
LOG(error) << oss.str();
|
||||||
LOG(error) << oss.str();
|
session.errorReply(request, http::status::not_found, oss.str());
|
||||||
session.errorReply(request, http::status::not_found, oss.str());
|
return;
|
||||||
return;
|
} else if (ec) {
|
||||||
} else if (ec) {
|
session.reply(ServiceLogic::serverError(request, ec.message()));
|
||||||
session.reply(ServiceLogic::serverError(request, ec.message()));
|
return;
|
||||||
return;
|
}
|
||||||
}
|
auto const size = body.size();
|
||||||
auto const size = body.size();
|
http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
|
||||||
http::response<http::file_body> res{std::piecewise_construct, std::make_tuple(std::move(body)),
|
std::make_tuple(http::status::ok, request.version())};
|
||||||
std::make_tuple(http::status::ok, request.version())};
|
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
res.set(http::field::content_type, ResponseUtility::mimeType(path));
|
||||||
res.set(http::field::content_type, ResponseUtility::mimeType(path));
|
res.content_length(size);
|
||||||
res.content_length(size);
|
res.keep_alive(request.keep_alive());
|
||||||
res.keep_alive(request.keep_alive());
|
session.reply(std::move(res));
|
||||||
session.reply(std::move(res));
|
});
|
||||||
});
|
|
||||||
|
m_router->insert("/wechat/{session*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
||||||
m_router->insert("/wechat/{session*}",
|
ServiceLogic::onWechat(shared_from_this(), request,
|
||||||
[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
[&session](auto &&response) { session.reply(std::move(response)); });
|
||||||
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) {
|
||||||
|
using namespace boost::beast;
|
||||||
m_router->insert("/api/v1/tasklist",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
auto database = Amass::Singleton<Database>::instance();
|
||||||
using namespace boost::beast;
|
auto tasks = database->tasks();
|
||||||
auto database = Amass::Singleton<Database>::instance();
|
|
||||||
auto tasks = database->tasks();
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
||||||
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
||||||
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
s.keep_alive(request.keep_alive());
|
||||||
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
s.body() = boost::json::serialize(tasks);
|
||||||
s.keep_alive(request.keep_alive());
|
s.prepare_payload();
|
||||||
s.body() = boost::json::serialize(tasks);
|
session.reply(std::move(s));
|
||||||
s.prepare_payload();
|
});
|
||||||
session.reply(std::move(s));
|
|
||||||
});
|
m_router->insert("/api/v1/task/add", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
||||||
|
using namespace boost::beast;
|
||||||
m_router->insert("/api/v1/task/add", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
LOG(info) << "add task: " << request.body();
|
||||||
using namespace boost::beast;
|
auto database = Amass::Singleton<Database>::instance();
|
||||||
LOG(info) << "add task: " << request.body();
|
auto rootJson = boost::json::parse(request.body());
|
||||||
auto database = Amass::Singleton<Database>::instance();
|
auto &root = rootJson.as_object();
|
||||||
auto rootJson = boost::json::parse(request.body());
|
|
||||||
auto &root = rootJson.as_object();
|
std::string content;
|
||||||
|
if (root.contains("content")) {
|
||||||
std::string content;
|
content = root.at("content").as_string();
|
||||||
if (root.contains("content")) {
|
}
|
||||||
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());
|
||||||
bool ret = database->addTask(root.at("createTime").as_int64(), content,
|
boost::json::object reply;
|
||||||
std::string(root.at("comment").as_string()), root.at("parentId").as_int64());
|
reply["status"] = ret ? 0 : -1;
|
||||||
boost::json::object reply;
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
||||||
reply["status"] = ret ? 0 : -1;
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
||||||
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
s.keep_alive(request.keep_alive());
|
||||||
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
s.body() = boost::json::serialize(reply);
|
||||||
s.keep_alive(request.keep_alive());
|
s.prepare_payload();
|
||||||
s.body() = boost::json::serialize(reply);
|
session.reply(std::move(s));
|
||||||
s.prepare_payload();
|
});
|
||||||
session.reply(std::move(s));
|
|
||||||
});
|
m_router->insert("/api/v1/task/delete/{id}", [this](HttpSession &session, const Request &request,const boost::urls::matches &matches) {
|
||||||
|
using namespace boost::beast;
|
||||||
m_router->insert("/api/v1/task/delete/{id}", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
LOG(info) << "delete task: " << matches.at("id");
|
||||||
using namespace boost::beast;
|
auto database = Amass::Singleton<Database>::instance();
|
||||||
LOG(info) << "delete task: " << matches.at("id");
|
auto status = database->removeTask(std::stoi(matches.at("id")));
|
||||||
auto database = Amass::Singleton<Database>::instance();
|
|
||||||
auto status = database->removeTask(std::stoi(matches.at("id")) );
|
boost::json::object reply;
|
||||||
|
reply["status"] = status ? 0 : -1;
|
||||||
boost::json::object reply;
|
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
||||||
reply["status"] = status? 0 : -1;
|
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
||||||
http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
|
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
||||||
s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
|
s.keep_alive(request.keep_alive());
|
||||||
s.set(http::field::content_type, "application/json;charset=UTF-8");
|
s.body() = boost::json::serialize(reply);
|
||||||
s.keep_alive(request.keep_alive());
|
s.prepare_payload();
|
||||||
s.body() = boost::json::serialize(reply);
|
session.reply(std::move(s));
|
||||||
s.prepare_payload();
|
});
|
||||||
session.reply(std::move(s));
|
|
||||||
});
|
m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
||||||
|
LOG(info) << "webhook: " << request;
|
||||||
m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
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();
|
||||||
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
|
corp->notify(request);
|
||||||
auto corp = Amass::Singleton<CorporationContext>::instance();
|
session.reply(
|
||||||
corp->notify(request);
|
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
|
||||||
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());
|
||||||
const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path,
|
|
||||||
boost::urls::matches_base &matches) const noexcept {
|
alarmTask();
|
||||||
return m_router->find(path, matches);
|
}
|
||||||
}
|
|
||||||
|
boost::asio::io_context &Application::ioContext() {
|
||||||
std::string_view SharedState::galleryRoot() const noexcept {
|
return *m_ioContext->ioContext();
|
||||||
return m_galleryRoot;
|
}
|
||||||
}
|
|
||||||
|
const Application::RequestHandler *Application::find(boost::urls::segments_encoded_view path,
|
||||||
void SharedState::setFileRoot(const std::string_view &root) {
|
boost::urls::matches_base &matches) const noexcept {
|
||||||
if (m_fileRoot == root) return;
|
return m_router->find(path, matches);
|
||||||
m_fileRoot = root;
|
}
|
||||||
}
|
|
||||||
|
int Application::exec() {
|
||||||
void SharedState::join(WebSocketSession *session) {
|
LOG(info) << "application start successful ...";
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
startCheckInterval(*m_ioContext->ioContext(), 2);
|
||||||
sessions_.insert(session);
|
m_ioContext->run<IoContext::Mode::Synchronous>();
|
||||||
}
|
LOG(info) << "application exit successful ...";
|
||||||
|
return m_status;
|
||||||
void SharedState::leave(WebSocketSession *session) {
|
}
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
sessions_.erase(session);
|
void Application::alarmTask() {
|
||||||
}
|
int hour = 10;
|
||||||
|
int minute = 30;
|
||||||
void SharedState::send(std::string message) {
|
auto alarmTime = DateTime::currentDateTime();
|
||||||
// Put the message in a shared pointer so we can re-use it for each client
|
alarmTime.setHour(hour);
|
||||||
auto const ss = std::make_shared<std::string const>(std::move(message));
|
alarmTime.setMinute(minute);
|
||||||
|
if (std::chrono::system_clock::now() > alarmTime()) {
|
||||||
// Make a local list of all the weak pointers representing
|
alarmTime = alarmTime.tomorrow();
|
||||||
// the sessions, so we can do the actual sending without
|
}
|
||||||
// holding the mutex:
|
m_timer->expires_at(alarmTime());
|
||||||
std::vector<std::weak_ptr<WebSocketSession>> v;
|
m_timer->async_wait([this](const boost::system::error_code &error) mutable {
|
||||||
{
|
if (error) {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
LOG(error) << error.message();
|
||||||
v.reserve(sessions_.size());
|
return;
|
||||||
for (auto p : sessions_) v.emplace_back(p->weak_from_this());
|
}
|
||||||
}
|
auto database = Amass::Singleton<Database>::instance();
|
||||||
|
auto tasks = database->tasks();
|
||||||
// For each session in our local list, try to acquire a strong
|
bool founded = false;
|
||||||
// pointer. If successful, then send the message on that session.
|
std::string content;
|
||||||
for (auto const &wp : v)
|
for (auto &task : tasks) {
|
||||||
if (auto sp = wp.lock()) sp->send(ss);
|
if (founded) break;
|
||||||
}
|
for (auto &child : task.children) {
|
||||||
|
if (!child.finished) {
|
||||||
void SharedState::alarmTask() {
|
content = child.content;
|
||||||
int hour = 10;
|
founded = true;
|
||||||
int minute = 30;
|
break;
|
||||||
auto alarmTime = DateTime::currentDateTime();
|
}
|
||||||
alarmTime.setHour(hour);
|
}
|
||||||
alarmTime.setMinute(minute);
|
if (!founded && !task.finished) {
|
||||||
if (std::chrono::system_clock::now() > alarmTime()) {
|
content = task.content;
|
||||||
alarmTime = alarmTime.tomorrow();
|
founded = true;
|
||||||
}
|
}
|
||||||
m_timer.expires_at(alarmTime());
|
}
|
||||||
m_timer.async_wait([this](const boost::system::error_code &error) mutable {
|
if (founded) {
|
||||||
if (error) {
|
std::ostringstream oss;
|
||||||
LOG(error) << error.message();
|
oss << "待完成事项:" << std::endl;
|
||||||
return;
|
oss << "==========" << std::endl;
|
||||||
}
|
oss << content << std::endl;
|
||||||
auto database = Amass::Singleton<Database>::instance();
|
oss << "==========" << std::endl;
|
||||||
auto tasks = database->tasks();
|
oss << "每天都要过得充实开心哦~";
|
||||||
bool founded = false;
|
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||||
std::string content;
|
if (manager) manager->sendMessage(NotifyServerChan, oss.str());
|
||||||
for (auto &task : tasks) {
|
}
|
||||||
if (founded) break;
|
|
||||||
for (auto &child : task.children) {
|
alarmTask();
|
||||||
if (!child.finished) {
|
});
|
||||||
content = child.content;
|
}
|
||||||
founded = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!founded && !task.finished) {
|
|
||||||
content = task.content;
|
|
||||||
founded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (founded) {
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << "待完成事项:" << std::endl;
|
|
||||||
oss << "==========" << std::endl;
|
|
||||||
oss << content << std::endl;
|
|
||||||
oss << "==========" << std::endl;
|
|
||||||
oss << "每天都要过得充实开心哦~";
|
|
||||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
|
||||||
if (manager) manager->sendMessage(NotifyServerChan, oss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
alarmTask();
|
|
||||||
});
|
|
||||||
}
|
|
45
Server/Application.h
Normal file
45
Server/Application.h
Normal 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__
|
@ -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
|
||||||
|
10
Server/ChatRoom/CMakeLists.txt
Normal file
10
Server/ChatRoom/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
|
||||||
|
add_library(ChatRoom
|
||||||
|
ChatRoom.h ChatRoom.cpp
|
||||||
|
WebSocketChatSession.h WebSocketChatSession.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ChatRoom
|
||||||
|
PUBLIC Universal
|
||||||
|
)
|
31
Server/ChatRoom/ChatRoom.cpp
Normal file
31
Server/ChatRoom/ChatRoom.cpp
Normal 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);
|
||||||
|
}
|
23
Server/ChatRoom/ChatRoom.h
Normal file
23
Server/ChatRoom/ChatRoom.h
Normal 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__
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
|
8
Server/WebRTC/CMakeLists.txt
Normal file
8
Server/WebRTC/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
add_library(WebRTC
|
||||||
|
WebSocketSignalSession.h WebSocketSignalSession.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(WebRTC
|
||||||
|
PUBLIC Universal
|
||||||
|
)
|
19
Server/WebRTC/SignalServer.cpp
Normal file
19
Server/WebRTC/SignalServer.cpp
Normal 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;
|
||||||
|
}
|
20
Server/WebRTC/SignalServer.h
Normal file
20
Server/WebRTC/SignalServer.h
Normal 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__
|
102
Server/WebRTC/WebSocketSignalSession.cpp
Normal file
102
Server/WebRTC/WebSocketSignalSession.cpp
Normal 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()));
|
||||||
|
}
|
53
Server/WebRTC/WebSocketSignalSession.h
Normal file
53
Server/WebRTC/WebSocketSignalSession.h
Normal 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__
|
121
Server/main.cpp
121
Server/main.cpp
@ -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);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user