diff --git a/.gitignore b/.gitignore index 3e956f4..252a924 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ target_wrapper.* *.qmlproject.user.* # QtCreator CMake -CMakeLists.txt.user* \ No newline at end of file +CMakeLists.txt.user* +build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index dbef695..7d8ba49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,11 @@ cmake_minimum_required(VERSION 3.17) -project(Older) \ No newline at end of file +project(Older) + +include(FetchContent) +FetchContent_Declare(Kylin + GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git +) +FetchContent_MakeAvailable(Kylin) + +add_subdirectory(Server) \ No newline at end of file diff --git a/Server/AlarmClockServer.cpp b/Server/AlarmClockServer.cpp new file mode 100644 index 0000000..79b9645 --- /dev/null +++ b/Server/AlarmClockServer.cpp @@ -0,0 +1,92 @@ +#include "AlarmClockServer.h" +#include "ServiceManager.h" +#include +#include + +AlarmClockServer::AlarmClockServer(boost::asio::io_context &context) + : m_publisher(context, ZeroMQ::SocketType::Pub), m_timer(context) { + auto manager = Amass::Singleton::instance(); + manager->registerTopic(SetAlarmClock, [this](uint8_t hour, uint8_t minute) { setAlarmTime(hour, minute); }); + manager->registerTopic(TextToSpeech, [this](const std::string &text) { textToSpeech(text); }); + manager->registerTopic(CurrentDatetime, [this]() { currentDatatime(); }); + manager->registerTopic(PlayRandomMusic, [this]() { playRandomMusic(); }); + manager->registerTopic(StopPlayMusic, [this]() { stopPlayMusic(); }); +} + +void AlarmClockServer::startPublishHeartBeat() { + m_timer.expires_after(std::chrono::seconds(25)); + m_timer.async_wait([this](const boost::system::error_code &error) { + if (error) { + LOG(error) << error.message(); + return; + } + boost::json::object obj; + obj["type"] = HeartBeat; + ZeroMQ::Message message(boost::json::serialize(obj)); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + startPublishHeartBeat(); + }); +} + +void AlarmClockServer::listen(const std::string_view &host, const std::string_view &port) { + std::ostringstream oss; + oss << "tcp://" << host << ":" << port; + boost::system::error_code error; + auto address = oss.str(); + m_publisher.bind(address, error); + if (error) { + LOG(error) << error.message(); + return; + } + startPublishHeartBeat(); + LOG(info) << "AlarmClockServer bind address: " << address; +} + +void AlarmClockServer::setAlarmTime(uint8_t hour, uint8_t minute) { + boost::json::object obj; + obj["type"] = SetAlarmClock; + obj["hour"] = hour; + obj["minute"] = minute; + + auto body = boost::json::serialize(obj); + ZeroMQ::Message message(body); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + LOG(info) << "send to client: " << body << ", size: " << size; +} + +void AlarmClockServer::textToSpeech(const std::string_view &text) { + boost::json::object obj; + obj["type"] = TextToSpeech.topic; + obj["text"] = text.data(); + auto body = boost::json::serialize(obj); + ZeroMQ::Message message(body); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + LOG(info) << "text:[" << text << "].\nsend to client: " << body << ", size: " << size; +} + +void AlarmClockServer::currentDatatime() { + boost::json::object obj; + obj["type"] = CurrentDatetime; + auto body = boost::json::serialize(obj); + ZeroMQ::Message message(body); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + LOG(info) << "send to client: " << body << ", size: " << size; +} + +void AlarmClockServer::playRandomMusic() { + boost::json::object obj; + obj["type"] = PlayRandomMusic; + auto body = boost::json::serialize(obj); + ZeroMQ::Message message(body); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + LOG(info) << "send to client: " << body << ", size: " << size; +} + +void AlarmClockServer::stopPlayMusic() { + boost::json::object obj; + obj["type"] = StopPlayMusic.topic; + auto body = boost::json::serialize(obj); + ZeroMQ::Message message(body); + auto size = m_publisher.send(std::move(message), ZeroMQ::SendFlags::Dontwait); + LOG(info) << "send to client: " << body << ", size: " << size; +} diff --git a/Server/AlarmClockServer.h b/Server/AlarmClockServer.h new file mode 100644 index 0000000..a3b4ae9 --- /dev/null +++ b/Server/AlarmClockServer.h @@ -0,0 +1,28 @@ +#ifndef __ALARMCLOCKSERVER_H__ +#define __ALARMCLOCKSERVER_H__ + +#include "ZeroMQSocket.h" +#include +#include + +class AlarmClockServer { + friend class Amass::Singleton; + +public: + void listen(const std::string_view &host, const std::string_view &port); + void setAlarmTime(uint8_t hour, uint8_t minute); + void textToSpeech(const std::string_view &text); + void currentDatatime(); + +protected: + AlarmClockServer(boost::asio::io_context &context); + void startPublishHeartBeat(); + void playRandomMusic(); + void stopPlayMusic(); + +private: + ZeroMQ::Socket m_publisher; + boost::asio::steady_timer m_timer; +}; + +#endif // __ALARMCLOCKSERVER_H__ \ No newline at end of file diff --git a/Server/CMakeLists.txt b/Server/CMakeLists.txt new file mode 100644 index 0000000..d88f31e --- /dev/null +++ b/Server/CMakeLists.txt @@ -0,0 +1,27 @@ +find_package(Boost COMPONENTS program_options json REQUIRED) + +add_executable(Server main.cpp + AlarmClockServer.h AlarmClockServer.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 +) + +target_link_libraries(Server + PRIVATE Universal + PRIVATE HttpProxy + PRIVATE AsioZeroMQ + PRIVATE ${Boost_LIBRARIES} +) + +set_target_properties(Server PROPERTIES + OUTPUT_NAME HttpServer +) \ No newline at end of file diff --git a/Server/HttpSession.cpp b/Server/HttpSession.cpp new file mode 100644 index 0000000..fe52c2a --- /dev/null +++ b/Server/HttpSession.cpp @@ -0,0 +1,100 @@ +#include "HttpSession.h" +#include "WebsocketSession.h" +#include +#include +#include +#include +#include + +HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr &state) + : m_stream(std::move(socket)), m_state(state) { + // m_buffer.reserve(1000 * 1000 * 1000); +} + +void HttpSession::run() { + doRead(); +} + +void HttpSession::errorReply(const Request &request, boost::beast::http::status status, + boost::beast::string_view message) { + using namespace boost::beast; + // invalid route + http::response res{status, request.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(request.keep_alive()); + res.body() = message; + res.prepare_payload(); + + reply(std::move(res)); +} + +void HttpSession::doRead() { + // Construct a new parser for each message + m_parser.emplace(); + + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + m_parser->body_limit(std::numeric_limits::max()); + m_parser->header_limit(std::numeric_limits::max()); + m_buffer.clear(); + + // Set the timeout. + m_stream.expires_after(std::chrono::seconds(30)); + boost::beast::http::async_read( + m_stream, m_buffer, *m_parser, + [self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred) { + self->onRead(ec, bytes_transferred); + }); +} + +void HttpSession::onRead(boost::beast::error_code ec, std::size_t) { + // This means they closed the connection + if (ec == boost::beast::http::error::end_of_stream) { + m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); + return; + } + if (ec) { + if (ec == boost::asio::error::operation_aborted) return; + LOG(info) << ec << " : " << ec.message(); + return; + } + + auto &request = m_parser->get(); + // See if it is a WebSocket Upgrade + if (boost::beast::websocket::is_upgrade(request)) { + // Create a websocket session, transferring ownership + // of both the socket and the HTTP request. + auto session = std::make_shared(m_stream.release_socket(), m_state); + session->run(m_parser->release()); + return; + } + boost::urls::url_view view(request.target()); + auto path = boost::urls::parse_path(view.path()); + TemplateMatches matches; + auto handler = m_state->find(*path, matches); + if (handler) { + (*handler)(*this, request, matches); + } else { + auto message = "The resource '" + std::string(path->buffer()) + "' was not found."; + errorReply(request, boost::beast::http::status::not_found, message); + LOG(error) << message; + } +} + +void HttpSession::onWrite(boost::beast::error_code ec, std::size_t, bool close) { + if (ec) { + if (ec == boost::asio::error::operation_aborted) return; + std::cerr << "write: " << ec.message() << "\n"; + } + + if (close) { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); + return; + } + + // Read another request + doRead(); +} diff --git a/Server/HttpSession.h b/Server/HttpSession.h new file mode 100644 index 0000000..5804700 --- /dev/null +++ b/Server/HttpSession.h @@ -0,0 +1,42 @@ +#ifndef HTTPSESSION_H +#define HTTPSESSION_H + +#include "SharedState.h" +#include "boost/beast.hpp" +#include +#include +#include + +/** Represents an established HTTP connection + */ +class HttpSession : public std::enable_shared_from_this { + void doRead(); + void onRead(boost::beast::error_code ec, std::size_t); + void onWrite(boost::beast::error_code ec, std::size_t, bool close); + + // void sendResponse(boost::beast::http::response &&response); + +public: + using Request = boost::beast::http::request; + HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr const &state); + template + void reply(const Response &&response) { + using ResponseType = typename std::decay_t; + auto sp = std::make_shared(std::forward(response)); + boost::beast::http::async_write( + m_stream, *sp, [self = shared_from_this(), sp](boost::beast::error_code ec, std::size_t bytes) { + self->onWrite(ec, bytes, sp->need_eof()); + }); + } + void errorReply(const Request &request, boost::beast::http::status status, boost::beast::string_view message); + + void run(); + +private: + boost::beast::tcp_stream m_stream; + boost::beast::flat_buffer m_buffer{std::numeric_limits::max()}; + SharedStatePtr m_state; + std::optional> m_parser; +}; + +#endif // HTTPSESSION_H diff --git a/Server/Listener.cpp b/Server/Listener.cpp new file mode 100644 index 0000000..c2de3c0 --- /dev/null +++ b/Server/Listener.cpp @@ -0,0 +1,64 @@ +#include "Listener.h" +#include "HttpSession.h" +#include +#include + +Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint, + std::shared_ptr const &state) + : m_ioContext(ioc), m_acceptor(ioc), m_state(state) { + boost::beast::error_code ec; + + // Open the acceptor + m_acceptor.open(endpoint.protocol(), ec); + if (ec) { + fail(ec, "open"); + return; + } + + // Allow address reuse + m_acceptor.set_option(boost::asio::socket_base::reuse_address(true), ec); + if (ec) { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + m_acceptor.bind(endpoint, ec); + if (ec) { + fail(ec, "bind"); + return; + } + + // Start listening for connections + m_acceptor.listen(boost::asio::socket_base::max_listen_connections, ec); + if (ec) { + fail(ec, "listen"); + return; + } +} + +void Listener::startAccept() { + // The new connection gets its own strand + auto client = std::make_shared(boost::asio::make_strand(m_ioContext)); + m_acceptor.async_accept( + *client, [self{shared_from_this()}, client](const boost::system::error_code &ec) { self->onAccept(ec, client); }); +} + +void Listener::fail(boost::beast::error_code ec, char const *what) { + // Don't report on canceled operations + if (ec == boost::asio::error::operation_aborted) return; + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handle a connection +void Listener::onAccept(boost::beast::error_code ec, std::shared_ptr socket) { + if (ec) { + if (ec == boost::asio::error::operation_aborted) return; + std::cerr << "accept: " << ec.message() << "\n"; + + } else { // Launch a new session for this connection + auto session = std::make_shared(std::move(*socket), m_state); + session->run(); + } + startAccept(); +} diff --git a/Server/Listener.h b/Server/Listener.h new file mode 100644 index 0000000..6357222 --- /dev/null +++ b/Server/Listener.h @@ -0,0 +1,33 @@ +#ifndef LISTENER_H +#define LISTENER_H + +#include +#include +#include +#include + +class SharedState; + +// Accepts incoming connections and launches the sessions +class Listener : public std::enable_shared_from_this { +public: + Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint, + std::shared_ptr const &state); + + // Start accepting incoming connections + void startAccept(); + inline std::shared_ptr state() const { + return m_state; + } + +protected: + void fail(boost::beast::error_code ec, char const *what); + void onAccept(boost::beast::error_code ec, std::shared_ptr socket); + +private: + boost::asio::io_context &m_ioContext; + boost::asio::ip::tcp::acceptor m_acceptor; + std::shared_ptr m_state; +}; + +#endif // LISTENER_H diff --git a/Server/ResponseUtility.cpp b/Server/ResponseUtility.cpp new file mode 100644 index 0000000..dee717e --- /dev/null +++ b/Server/ResponseUtility.cpp @@ -0,0 +1,51 @@ +#include "ResponseUtility.h" +#include "boost/beast.hpp" + +namespace ResponseUtility { + +std::string_view mimeType(std::string_view path) { + using boost::beast::iequals; + auto const ext = [&path] { + auto const pos = path.rfind("."); + if (pos == std::string_view::npos) return std::string_view{}; + return path.substr(pos); + }(); + if (iequals(ext, ".pdf")) return "Application/pdf"; + if (iequals(ext, ".htm")) return "text/html"; + if (iequals(ext, ".html")) return "text/html"; + if (iequals(ext, ".php")) return "text/html"; + if (iequals(ext, ".css")) return "text/css"; + if (iequals(ext, ".txt")) return "text/plain"; + if (iequals(ext, ".js")) return "application/javascript"; + if (iequals(ext, ".json")) return "application/json"; + if (iequals(ext, ".xml")) return "application/xml"; + if (iequals(ext, ".swf")) return "application/x-shockwave-flash"; + if (iequals(ext, ".flv")) return "video/x-flv"; + if (iequals(ext, ".png")) return "image/png"; + if (iequals(ext, ".jpe")) return "image/jpeg"; + if (iequals(ext, ".jpeg")) return "image/jpeg"; + if (iequals(ext, ".jpg")) return "image/jpeg"; + if (iequals(ext, ".gif")) return "image/gif"; + if (iequals(ext, ".bmp")) return "image/bmp"; + if (iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; + if (iequals(ext, ".tiff")) return "image/tiff"; + if (iequals(ext, ".tif")) return "image/tiff"; + if (iequals(ext, ".svg")) return "image/svg+xml"; + if (iequals(ext, ".svgz")) return "image/svg+xml"; + return "application/text"; +} + +std::string pathCat(std::string_view base, std::string_view path) { + if (base.empty()) return std::string(path); + std::string result(base); + char constexpr path_separator = '/'; + if (result.back() == path_separator && path.front() == path_separator) { + result.resize(result.size() - 1); + } else if (result.back() != path_separator && path.front() != path_separator) { + result.append("/"); + } + result.append(path.data(), path.size()); + + return result; +} +} // namespace ResponseUtility diff --git a/Server/ResponseUtility.h b/Server/ResponseUtility.h new file mode 100644 index 0000000..c0ff08b --- /dev/null +++ b/Server/ResponseUtility.h @@ -0,0 +1,19 @@ +#ifndef RESPONSEUTILITY_H +#define RESPONSEUTILITY_H + +#include + +namespace ResponseUtility { +/** + * @brief Return a reasonable mime type based on the extension of a file. + */ +std::string_view mimeType(std::string_view path); + +/** + * @brief Append an HTTP rel-path to a local filesystem path.The returned path is normalized for the + * platform. + */ +std::string pathCat(std::string_view base, std::string_view path); +} // namespace ResponseUtility + +#endif // RESPONSEUTILITY_H diff --git a/Server/ServiceLogic.cpp b/Server/ServiceLogic.cpp new file mode 100644 index 0000000..ad3f5e5 --- /dev/null +++ b/Server/ServiceLogic.cpp @@ -0,0 +1,34 @@ +#include "ServiceLogic.h" +#include + +namespace ServiceLogic { + +boost::beast::http::response +notFound(const boost::beast::http::request &request) { + using namespace boost::beast; + http::response res{http::status::not_found, request.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(request.keep_alive()); + std::ostringstream oss; + oss << "The resource '" << request.target() << "' was not found."; + res.body() = oss.str(); + res.prepare_payload(); + return res; +} + +boost::beast::http::response +serverError(const boost::beast::http::request &request, + std::string_view errorMessage) { + using namespace boost::beast; + http::response res{http::status::internal_server_error, request.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(request.keep_alive()); + std::ostringstream oss; + oss << "An error occurred: '" << errorMessage << "'"; + res.body() = oss.str(); + res.prepare_payload(); + return res; +} +} // namespace ServiceLogic diff --git a/Server/ServiceLogic.h b/Server/ServiceLogic.h new file mode 100644 index 0000000..b7a9935 --- /dev/null +++ b/Server/ServiceLogic.h @@ -0,0 +1,59 @@ +#ifndef SERVICELOGIC_H +#define SERVICELOGIC_H + +#include "ResponseUtility.h" +#include "SharedState.h" +#include "StringUtility.h" +#include +#include +#include +#include +#include +#include +#include + +using StringRequest = boost::beast::http::request; + +namespace ServiceLogic { + +template +static void onDownload(SharedStatePtr /*state*/, StringRequest &&request, Send &&send); + +template +static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send); + +template +static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix); + +template +static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send); + +template +static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&send); + +// Returns a not found response +boost::beast::http::response +notFound(const boost::beast::http::request &request); + +// Returns a server error response +boost::beast::http::response +serverError(const boost::beast::http::request &request, std::string_view errorMessage); + +template +boost::beast::http::response make_200(const boost::beast::http::request &request, + typename ResponseBody::value_type body, + boost::beast::string_view content) { + boost::beast::http::response response{boost::beast::http::status::ok, request.version()}; + response.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); + response.set(boost::beast::http::field::content_type, content); + response.body() = body; + response.prepare_payload(); + response.keep_alive(request.keep_alive()); + + return response; +} +}; // namespace ServiceLogic + +#include "ServiceLogic.inl" + +#endif // SERVICELOGIC_H diff --git a/Server/ServiceLogic.inl b/Server/ServiceLogic.inl new file mode 100644 index 0000000..eedbd1b --- /dev/null +++ b/Server/ServiceLogic.inl @@ -0,0 +1,130 @@ +#ifndef SERVICELOGIC_INL +#define SERVICELOGIC_INL + +#include "BoostLog.h" +#include "ServiceLogic.h" +#include "WeChatContext/WeChatContext.h" +#include +#include +#include +#include +#include +#include +#include + +namespace ServiceLogic { + +template +static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send) { + using namespace boost::beast; + boost::urls::url url(request.target()); + if (url.path().size() < 12) return; + auto file = url.path().substr(12); + + auto root = state->notebookRoot(); + auto path = ResponseUtility::pathCat(root, file); + + boost::nowide::ifstream ifs(path, boost::nowide::ifstream::binary); + std::vector file_string(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); + boost::beast::error_code ec; + http::response> s; + s.set(http::field::server, BOOST_BEAST_VERSION_STRING); + s.set(http::field::content_type, ResponseUtility::mimeType(path)); + s.keep_alive(request.keep_alive()); + s.body() = std::move(file_string); + s.prepare_payload(); + return send(std::move(s)); +} + +template +static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix) { + using namespace boost::beast; + boost::urls::url url(request.target()); + auto file = url.path().substr(prefix.length()); + + auto root = state->notebookRoot(); + auto path = ResponseUtility::pathCat(root, file); + LOG(info) << "get file: " << path; + + if (!std::filesystem::exists(path)) { + return send(notFound(request)); + } + boost::nowide::ifstream ifs(path); + std::string file_string(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); + http::response s; + s.set(http::field::server, BOOST_BEAST_VERSION_STRING); + s.set(http::field::content_type, "text/markdown;charset=UTF-8"); + s.keep_alive(request.keep_alive()); + s.body() = file_string; + s.prepare_payload(); + return send(std::move(s)); +} + +template +void onDownload(SharedStatePtr state, StringRequest &&request, Send &&send) { + LOG(info) << "onDownload"; + using namespace boost::beast; + std::string_view file; + if (request.target() == "/download") { + auto requestData = boost::json::parse(request.body()); + file = requestData.as_object()["file"].as_string(); + } else { + file = request.target().substr(9); + } + auto path = ResponseUtility::pathCat(state->fileRoot(), file); + LOG(info) << "get file: " << path; + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if (ec == boost::system::errc::no_such_file_or_directory) { + return send(notFound(request)); + } else if (ec) { // Handle an unknown error + return send(serverError(request, ec.message())); + } + + // Cache the size since we need it after the move + auto const size = body.size(); + + // Respond to GET request + http::response res{std::piecewise_construct, std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, request.version())}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, ResponseUtility::mimeType(path)); + res.set(http::field::content_description, file); + res.set(http::field::accept_charset, "utf-8"); + res.content_length(size); + res.keep_alive(request.keep_alive()); + + return send(std::move(res)); +} + +template +static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) { + using namespace boost::beast; + boost::urls::url url(request.target()); + auto context = Amass::Singleton::instance(); + http::response response; + if (request.count("Content-Type") > 0 && request.at("Content-Type") == "text/xml") { + response.body() = context->reply(request.body()); + } else { + auto query = url.params(); + if (auto iterator = query.find("echostr"); iterator != query.end()) { + response.body() = (*iterator)->value; + } + } + boost::beast::error_code ec; + + response.set(http::field::server, BOOST_BEAST_VERSION_STRING); + response.set(http::field::content_type, "text/xml;charset=UTF-8"); + response.keep_alive(request.keep_alive()); + + response.prepare_payload(); + return send(std::move(response)); +} +} // namespace ServiceLogic + +#endif // SERVICELOGIC_INL diff --git a/Server/ServiceManager.h b/Server/ServiceManager.h new file mode 100644 index 0000000..5781e9f --- /dev/null +++ b/Server/ServiceManager.h @@ -0,0 +1,24 @@ +#ifndef __SERVICEMANAGER_H__ +#define __SERVICEMANAGER_H__ + +#include +#include + +constexpr auto HeartBeat = "HeartBeat"; +constexpr auto SetAlarmClock = "setAlarmClock"; +constexpr auto CurrentDatetime = "CurrentDatetime"; +constexpr auto PlayRandomMusic = "PlayRandomMusic"; + +constexpr auto NotifyServerChan = MessageManager::Message("NotifyServerChan"); +constexpr auto TextToSpeech = MessageManager::Message("TextToSpeech"); +constexpr auto StopPlayMusic = MessageManager::Message<>("StopPlayMusic"); + +using SetAlarmClockService = void(uint8_t, uint8_t); +using CurrentDatetimeService = void(); +using PlayRandomMusicService = void(); + +class ServiceManager : public MessageManager { + friend class Amass::Singleton; +}; + +#endif // __SERVICEMANAGER_H__ \ No newline at end of file diff --git a/Server/SharedState.cpp b/Server/SharedState.cpp new file mode 100644 index 0000000..a86a6f8 --- /dev/null +++ b/Server/SharedState.cpp @@ -0,0 +1,107 @@ +#include "SharedState.h" +#include "HttpSession.h" +#include "ServiceLogic.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>()}, m_docRoot(std::move(doc_root)) { + + m_router->insert("/", [](HttpSession &session, const Request &request, const TemplateMatches &matches) { + // Send content message to client and wait to receive next request + session.reply(ServiceLogic::make_200(request, "Main page\n", "text/html")); + }); + + m_router->insert("/wechat/{session*}", + [this](HttpSession &session, const Request &request, const TemplateMatches &matches) { + ServiceLogic::onWechat(shared_from_this(), request, + [&session](auto &&response) { session.reply(std::move(response)); }); + }); + + // m_router->all(R"(^/download/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { + // ServiceLogic::onDownload(shared_from_this(), std::move(request), + // [&context](auto &&response) { context.send(std::move(response)); }); + // }); + // m_router->all(R"(^/blog/list$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { + // ServiceLogic::onGetBlogList(shared_from_this(), std::move(request), + // [&context](auto &&response) { context.send(response); }); + // }); + + // m_router->all(R"(^/blog/image/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { + // ServiceLogic::onGetBlogImage(shared_from_this(), std::move(request), + // [&context](auto &&response) { context.send(std::move(response)); }); + // }); + // m_router->all(R"(^/blog/content.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) + // { + // ServiceLogic::onGetBlogContent( + // shared_from_this(), std::move(request), [&context](auto &&response) { context.send(std::move(response)); + // }, + // "/blog/content"); + // }); + + m_router->insert( + "/trigger-ci.hook", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) { + LOG(info) << "webhook: " << request; + session.reply(ServiceLogic::make_200(request, "Main page\n", "text/html")); + }); + + m_router->insert("/notify", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) { + auto corp = Amass::Singleton::instance(); + corp->notify(request); + session.reply( + ServiceLogic::make_200(request, "notify successed.\n", "text/html")); + }); +} + +const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path, + TemplateMatchStorageBase &matches) const noexcept { + return m_router->find(path, matches); +} + +std::string_view SharedState::galleryRoot() const noexcept { + return m_galleryRoot; +} + +void SharedState::setFileRoot(const std::string_view &root) { + if (m_fileRoot == root) return; + m_fileRoot = root; +} + +std::string_view SharedState::notebookRoot() const { + return m_notebookRoot; +} + +void SharedState::setNotebookRoot(const std::string_view &root) { + if (m_notebookRoot == root) return; + m_notebookRoot = root; +} + +void SharedState::join(WebSocketSession *session) { + std::lock_guard lock(mutex_); + sessions_.insert(session); +} + +void SharedState::leave(WebSocketSession *session) { + std::lock_guard 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::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> v; + { + std::lock_guard 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); +} diff --git a/Server/SharedState.h b/Server/SharedState.h new file mode 100644 index 0000000..1891d72 --- /dev/null +++ b/Server/SharedState.h @@ -0,0 +1,65 @@ +#ifndef SHAREDSTATE_H +#define SHAREDSTATE_H + +#include "TemplateMatchs.h" +#include "UrlRouter.h" +#include +#include +#include +#include +#include +#include +#include + +class HttpSession; +class WebSocketSession; + +// Represents the shared server state +class SharedState:public std::enable_shared_from_this { + + // This mutex synchronizes all access to sessions_ + std::mutex mutex_; + + // Keep a list of all the connected clients + std::unordered_set sessions_; + +public: + using Request = boost::beast::http::request; + using Handler = std::function; + SharedState(boost::asio::io_context &ioContext,std::string doc_root); + + const Handler *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &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); + + std::string_view notebookRoot() const; + void setNotebookRoot(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); + +private: + boost::asio::io_context &m_ioContext; + std::shared_ptr > m_router; + std::string m_docRoot; + std::string m_galleryRoot = "/root/photos"; + std::string m_fileRoot; + std::string m_notebookRoot; +}; + +using SharedStatePtr = std::shared_ptr; + +#endif // SHAREDSTATE_H diff --git a/Server/UdpServer.cpp b/Server/UdpServer.cpp new file mode 100644 index 0000000..2028b17 --- /dev/null +++ b/Server/UdpServer.cpp @@ -0,0 +1,78 @@ +#include "UdpServer.h" +#include "BoostLog.h" +#include "boost/endian.hpp" +#include +#include + +UdpServer::UdpServer(boost::asio::io_context &io_context) + : m_socket(io_context, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 8088)), m_reveiveBuffer(512), + m_timer(io_context) { + + // start recieve. + m_socket.async_receive_from( + boost::asio::buffer(m_reveiveBuffer), m_remotePoint, + std::bind(&UdpServer::reveiveHandler, this, std::placeholders::_1, std::placeholders::_2)); + + sendData(); + + boost::system::error_code error; + auto address = boost::asio::ip::address::from_string("127.0.0.1", error); + if (error) { + LOG(error) << error.message(); + } else { + m_remotePoint = boost::asio::ip::udp::endpoint(address, 1234); + } +} + +void UdpServer::reveiveHandler(const boost::system::error_code &error, std::size_t bytes_transferred) { + LOG(info) << "Received byte size: " << bytes_transferred + << ",remote ip address: " << m_remotePoint.address().to_string() << std::endl; + if (error) { + LOG(error) << error.message(); + return; + } + + handleReceivedBuffer(m_reveiveBuffer); + + // continue recieve next datagram. + m_socket.async_receive_from( + boost::asio::buffer(m_reveiveBuffer), m_remotePoint, + std::bind(&UdpServer::reveiveHandler, this, std::placeholders::_1, std::placeholders::_2)); +} + +void UdpServer::sendHandler(const boost::system::error_code &error, std::size_t bytes_transferred) { + if (error) { + LOG(error) << error.message(); + } +} + +void UdpServer::handleReceivedBuffer(std::vector &data) { +} + +void UdpServer::sendData() { + using namespace std::chrono_literals; + static uint32_t index = 0; + m_timer.expires_after(1ms); + m_timer.async_wait([this](const boost::system::error_code &) { + std::random_device rd; // 将用于获得随机数引擎的种子 + std::mt19937 gen(rd()); // 以 rd() 播种的标准 mersenne_twister_engine + std::uniform_int_distribution distribution(std::numeric_limits::min(), + std::numeric_limits::max()); + + std::array data; + data[0] = 0xAA; + data[1] = 0xFF; + data[2] = 0x55; + data[3] = 0x00; + auto bigEndian = reinterpret_cast(&data[4]); + *bigEndian++ = boost::endian::native_to_big(index++); + for (size_t i = 0; i < 100; i++) { + *bigEndian++ = boost::endian::native_to_big(distribution(gen)); + } + + m_socket.async_send_to(boost::asio::buffer(data), m_remotePoint, + std::bind(&UdpServer::sendHandler, this, std::placeholders::_1, std::placeholders::_2)); + + sendData(); + }); +} diff --git a/Server/UdpServer.h b/Server/UdpServer.h new file mode 100644 index 0000000..f36b155 --- /dev/null +++ b/Server/UdpServer.h @@ -0,0 +1,36 @@ +#ifndef UDPSERVER_H +#define UDPSERVER_H + +#include "boost/asio.hpp" + +class UdpServer { +public: + UdpServer(boost::asio::io_context &io_context); + +protected: + /** + * @brief reveiveHandler + * @param error Result of operation. + * @param bytes_transferred Number of bytes received. + */ + void reveiveHandler(const boost::system::error_code &error, std::size_t bytes_transferred); + + /** + * @brief sendHandler + * @param error Result of operation. + * @param bytes_transferred Number of bytes sent. + */ + void sendHandler(const boost::system::error_code &error, std::size_t bytes_transferred); + + void handleReceivedBuffer(std::vector &data); + + void sendData(); + +private: + boost::asio::ip::udp::socket m_socket; + boost::asio::ip::udp::endpoint m_remotePoint; //客户端端点 + std::vector m_reveiveBuffer; + boost::asio::steady_timer m_timer; +}; + +#endif // UDPSERVER_H diff --git a/Server/WeChatContext/CorporationContext.cpp b/Server/WeChatContext/CorporationContext.cpp new file mode 100644 index 0000000..3ca1392 --- /dev/null +++ b/Server/WeChatContext/CorporationContext.cpp @@ -0,0 +1,113 @@ +#include "CorporationContext.h" +#include "../ServiceManager.h" +#include "BoostLog.h" +#include "NetworkUtility.h" +#include +#include +#include +#include +#include +#include + +CorporationContext::CorporationContext(boost::asio::io_context &ioContext) + : m_ioContext(ioContext), m_timer(ioContext) { + boost::asio::defer([this]() { updateAccessToken(); }); + + auto manager = Amass::Singleton::instance(); + if (manager) + manager->registerTopic(NotifyServerChan, + [this](const std::string &text) { sendMessage(MessageType::Text, text); }); +} + +void CorporationContext::sendMessage(MessageType type, const std::string &message) { + boost::format target("/cgi-bin/message/send?access_token=%1%"); + target % m_accessToken; + + boost::json::object msg; + msg["content"] = message; + + boost::json::object request; + request["touser"] = "@all"; + request["agentid"] = agentid; + if (type == MessageType::Markdown) { + request["msgtype"] = "markdown"; + request["markdown"] = std::move(msg); + } else { + request["msgtype"] = "text"; + request["text"] = std::move(msg); + } + auto body = boost::json::serialize(request); + + boost::beast::error_code error; + auto response = Https::post(m_ioContext, host, port, target.str(), body, error); + if (error) { + LOG(error) << error.message(); + return; + } + LOG(info) << response; +} + +void CorporationContext::notify(const RequestType &request) { + boost::json::error_code error; + auto json = boost::json::parse(request.body(), error); + if (error) { + LOG(error) << "parse: [" << request.body() << "] failed, reason: " << error.message(); + return; + } + // LOG(debug) << "parse: [" << request.body() << "] succeed."; + auto &req = json.as_object(); + MessageType type = MessageType::Text; + if (req.contains("type")) { + if (req.at("type").as_string() == "markdown") { + type = MessageType::Markdown; + } + } + if (req.contains("msg")) { + std::string msg(req.at("msg").as_string()); + sendMessage(type, std::move(msg)); + } +} + +void CorporationContext::updateAccessToken() { + boost::beast::error_code error; + + boost::format target("/cgi-bin/gettoken?corpid=%1%&corpsecret=%2%"); + target % corpid % corpsecret; + + auto response = Https::get(m_ioContext, host, port, target.str(), error); + if (error) { + LOG(error) << error.message(); + return; + } + if (response.empty()) { + LOG(warning) << "response is empty."; + return; + } + + auto json = boost::json::parse(response); + auto &accessTokenObject = json.as_object(); + int errcode = accessTokenObject.count("errcode") > 0 ? accessTokenObject.at("errcode").as_int64() : -1; + if (errcode != 0) { + LOG(error) << "get access_token failed,code: " << errcode + << ", message: " << accessTokenObject.at("errmsg").as_string(); + return; + } + m_accessToken = accessTokenObject.at("access_token").as_string(); + auto expires_in = accessTokenObject.at("expires_in").as_int64(); + // LOG(info) << "access_token: " << m_accessToken; + LOG(info) << "re-access_token after " << expires_in << " s."; + m_timer.expires_after(std::chrono::seconds(expires_in)); + m_timer.async_wait([this](const boost::system::error_code &error) { + if (error) { + LOG(error) << error.message(); + return; + } + updateAccessToken(); + }); + + static bool started = true; + if (started) { + sendMessage(MessageType::Text, "您好,艾玛已上线......"); + started = false; + } +} diff --git a/Server/WeChatContext/CorporationContext.h b/Server/WeChatContext/CorporationContext.h new file mode 100644 index 0000000..b5ec800 --- /dev/null +++ b/Server/WeChatContext/CorporationContext.h @@ -0,0 +1,41 @@ +#ifndef __CORPORATIONCONTEXT_H__ +#define __CORPORATIONCONTEXT_H__ + +#include +#include + +class CorporationContext { +public: + enum MessageType { + Text, + Markdown, + }; + using RequestType = boost::beast::http::request; + + CorporationContext(boost::asio::io_context &ioContext); + void sendMessage(MessageType type,const std::string &message); + + /** + * @brief + * + * @param request + * @example curl -H "Content-Type: application/json" -X POST -d '{"user_id": "123", "msg":"OK!" }' + * https://amass.fun/notify + */ + void notify(const RequestType &request); + +protected: + void updateAccessToken(); + +private: + boost::asio::io_context &m_ioContext; + boost::asio::steady_timer m_timer; + std::string m_accessToken; + + constexpr static auto host = "qyapi.weixin.qq.com"; + constexpr static auto port = "443"; + constexpr static auto corpid = "ww1a786851749bdadc"; + constexpr static auto corpsecret = "LlyJmYLIBOxJkQxkhwyqNVf550AUQ3JT2MT4yuS31i0"; + constexpr static auto agentid = 1000002; +}; +#endif // __CORPORATIONCONTEXT_H__ \ No newline at end of file diff --git a/Server/WeChatContext/WeChatContext.cpp b/Server/WeChatContext/WeChatContext.cpp new file mode 100644 index 0000000..01e9725 --- /dev/null +++ b/Server/WeChatContext/WeChatContext.cpp @@ -0,0 +1,184 @@ +#include "WeChatContext.h" +#include "../ServiceManager.h" +#include "BoostLog.h" +#include "WeChatSession.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +std::string WeChatContext::reply(const std::string &body) { + std::ostringstream oss; + LOG(info) << "someone send message: \n" << body; + boost::property_tree::ptree ptree; + std::istringstream iss(body); + boost::property_tree::read_xml(iss, ptree); + + auto ToUserName = ptree.get_optional("xml.ToUserName"); + if (!ToUserName) { + LOG(error) << "request dont contain ToUserName."; + return oss.str(); + } + + auto FromUserName = ptree.get("xml.FromUserName"); + auto CreateTime = ptree.get("xml.CreateTime"); + auto MsgType = ptree.get("xml.MsgType"); + auto content = ptree.get("xml.Content"); + auto MsgId = ptree.get("xml.MsgId"); + + std::shared_ptr session; + if (m_sessions.count(FromUserName) > 0) { + session = m_sessions.at(FromUserName); + } else { + session = std::make_shared(FromUserName); + m_sessions.emplace(FromUserName, session); + } + boost::algorithm::trim(content); + auto reply = session->processInput(content); + + boost::property_tree::ptree sendXml; + sendXml.put("xml.Content", reply); + LOG(info) << "send " << FromUserName << ": " << reply; + + sendXml.put("xml.ToUserName", FromUserName); + sendXml.put("xml.FromUserName", *ToUserName); + sendXml.put("xml.CreateTime", CreateTime); + sendXml.put("xml.MsgType", MsgType); + + boost::property_tree::write_xml(oss, sendXml); + // LOG(info) << "reply content:\n " << oss.str(); + return oss.str(); +} + +WeChatContext::WeChatContext(boost::asio::io_context &ioContext) + : m_ioContext(ioContext), m_timer(ioContext), m_sessionsExpireTimer(ioContext) { + + boost::asio::defer([this]() { updateAccessToken(); }); +} + +void WeChatContext::updateAccessToken() { + boost::beast::error_code error; + + boost::format target("/cgi-bin/token?grant_type=client_credential&appid=%1%&secret=%2%"); + target % appid % secret; + + auto response = Https::get(m_ioContext, host, port, target.str(), error); + if (error) { + LOG(error) << error.message(); + return; + } + if (response.empty()) { + LOG(warning) << "response is empty."; + return; + } + + auto json = boost::json::parse(response); + auto &accessTokenObject = json.as_object(); + if (accessTokenObject.count("errcode")) { + LOG(error) << "get access_token failed,code: " << accessTokenObject.at("errcode").as_int64() + << ", message: " << accessTokenObject.at("errmsg").as_string(); + return; + } + m_accessToken = accessTokenObject.at("access_token").as_string(); + auto expires_in = accessTokenObject.at("expires_in").as_int64(); + // LOG(info) << "access_token: " << m_accessToken; + LOG(info) << "re-access_token after " << expires_in << " s."; + m_timer.expires_after(std::chrono::seconds(expires_in)); + m_timer.async_wait([this](const boost::system::error_code &error) { + if (error) { + LOG(error) << error.message(); + return; + } + updateAccessToken(); + }); + broadcast("hello,amass."); +} + +WeChatContext::OpenIds WeChatContext::users() { + boost::beast::error_code error; + + boost::format target("/cgi-bin/user/get?access_token=%1%"); + + target % m_accessToken; + auto response = Https::get(m_ioContext, host, port, target.str(), error); + if (error) { + LOG(error) << error.message(); + return {}; + } + + auto json = boost::json::parse(response); + auto &responseObject = json.as_object(); + if (responseObject.contains("errcode")) { + LOG(error) << responseObject.at("errmsg").as_string(); + return {}; + } + auto &users = responseObject.at("data").as_object().at("openid").as_array(); + if (users.empty()) { + LOG(info) << "now we have no users."; + } + OpenIds ret; + for (auto &id : users) { + ret.emplace_back(id.as_string()); + } + return ret; +} + +std::string WeChatContext::broadcast(const std::string_view &message) { + boost::json::object messageObject; + auto users = this->users(); + LOG(info) << "users: " << users; + if (users.size() < 2) users.emplace_back("fake_user"); + boost::json::array usersArray; + for (auto &user : users) { + usersArray.emplace_back(user); + } + messageObject.emplace("touser", std::move(usersArray)); + messageObject.emplace("msgtype", "text"); + + boost::json::object textObject; + textObject.emplace("content", message.data()); + + messageObject.emplace("text", std::move(textObject)); + + boost::format target("/cgi-bin/message/mass/send?access_token=%1%"); + + target % m_accessToken; + + boost::system::error_code error; + + auto response = Https::post(m_ioContext, host, port, target.str(), boost::json::serialize(messageObject), error); + if (error) { + // LOG(error) << error.message(); + return response; + } + return response; +} + +void WeChatContext::cleanExpiredSessions(const boost::system::error_code &error) { + if (error) { + LOG(error) << error.message(); + return; + } + auto now = std::chrono::system_clock::now(); + for (auto iterator = m_sessions.begin(); iterator != m_sessions.cend();) { + if (std::chrono::duration_cast(now - iterator->second->lastAccessedTime()) > + sessionExpireTime) { + iterator = m_sessions.erase(iterator); + } else { + ++iterator; + } + } + m_sessionsExpireTimer.expires_after(sessionExpireTime); + m_sessionsExpireTimer.async_wait([ptr{weak_from_this()}](const boost::system::error_code &error) { + if (ptr.expired()) return; + ptr.lock()->cleanExpiredSessions(error); + }); +} diff --git a/Server/WeChatContext/WeChatContext.h b/Server/WeChatContext/WeChatContext.h new file mode 100644 index 0000000..b210efc --- /dev/null +++ b/Server/WeChatContext/WeChatContext.h @@ -0,0 +1,49 @@ +#ifndef WECHATCONTEXT_H +#define WECHATCONTEXT_H + +#include "Singleton.h" +#include +#include +#include + +class WeChatSession; + +class WeChatContext : public std::enable_shared_from_this { + friend class Amass::Singleton; + +public: + using OpenIds = std::vector; + + /** + * @brief onWechat()函数调用了此函数,对接收到的消息进行处理 + * + * @param body + * @return std::string 返回给微信服务器 + */ + std::string reply(const std::string &body); + +protected: + WeChatContext(boost::asio::io_context &ioContext); + + void updateAccessToken(); + OpenIds users(); + std::string broadcast(const std::string_view &message); + void cleanExpiredSessions(const boost::system::error_code &error = boost::system::error_code()); + +private: + boost::asio::io_context &m_ioContext; + boost::asio::steady_timer m_timer; + std::string m_accessToken; + + boost::asio::steady_timer m_sessionsExpireTimer; + std::unordered_map> m_sessions; + + constexpr static std::chrono::seconds sessionExpireTime{5}; + constexpr static int httpVersion = 11; + constexpr static auto host = "api.weixin.qq.com"; + constexpr static auto port = "443"; + constexpr static auto appid = "wxdb4253b5c4259708"; + constexpr static auto secret = "199780c4d3205d8b7b1f9be3382fbf82"; +}; + +#endif // WECHATCONTEXT_H diff --git a/Server/WeChatContext/WeChatSession.cpp b/Server/WeChatContext/WeChatSession.cpp new file mode 100644 index 0000000..244ed18 --- /dev/null +++ b/Server/WeChatContext/WeChatSession.cpp @@ -0,0 +1,101 @@ +#include "WeChatSession.h" +#include "../ServiceManager.h" +#include +#include + +WeChatSession::WeChatSession(const std::string_view &username) : m_username(username) { + m_lastAccessedTime = std::chrono::system_clock::now(); + initiate(); +} + +std::string WeChatSession::processInput(const std::string_view &text) { + ProcessInputEvent e; + e.text = text; + process_event(e); + m_lastAccessedTime = std::chrono::system_clock::now(); + + std::string ret = std::move(m_reply); + return ret; +} + +void WeChatSession::printHelp() { + std::ostringstream oss; + oss << "1:设置闹钟" << std::endl; + oss << "2:TTS" << std::endl; + oss << "3:当前时间" << std::endl; + oss << "4:随机播放音乐" << std::endl; + oss << "5:停止播放音乐" << std::endl; + oss << "<其它>:帮助" << std::endl; + setReply(oss.str()); +} + +void WeChatSession::printCurrentDateTime() { + auto manager = Amass::Singleton::instance(); + if (manager) manager->sendMessage(CurrentDatetime); + setReply("艾玛收到!将为您播报当前时间"); +} + +void WeChatSession::playRandomMusic() { + auto manager = Amass::Singleton::instance(); + if (manager) manager->sendMessage(PlayRandomMusic); + setReply("艾玛收到!将为您随机播放音乐"); +} + +void WeChatSession::stopPlayMusic() { + auto manager = Amass::Singleton::instance(); + if (manager) manager->sendMessage(StopPlayMusic); + setReply("艾玛收到!正在为您停止播放音乐"); +} + +std::chrono::system_clock::time_point WeChatSession::lastAccessedTime() const { + return m_lastAccessedTime; +} + +void WeChatSession::setReply(std::string &&reply) { + m_reply = std::move(reply); +} + +boost::statechart::result IdleState::react(const ProcessInputEvent &e) { + auto &text = e.text; + if (text == "1") { + outermost_context().setReply("请输入闹钟时间:"); + return transit(); + } else if (text == "2") { + outermost_context().setReply("请输入TTS文字:"); + return transit(); + } else if (text == "3") { + outermost_context().printCurrentDateTime(); + return discard_event(); + } else if (text == "4") { + outermost_context().playRandomMusic(); + return discard_event(); + } else if (text == "5") { + outermost_context().stopPlayMusic(); + return discard_event(); + } else { + outermost_context().stopPlayMusic(); + outermost_context().printHelp(); + return discard_event(); + } +} + +boost::statechart::result SetAlarmState::react(const ProcessInputEvent &e) { + auto &text = e.text; + auto [hour, minute, second] = DateTime::parseTime(text); + auto manager = Amass::Singleton::instance(); + if (manager) manager->sendMessage(SetAlarmClock, hour, minute); + std::ostringstream oss; + oss << "set alarm clock at " << (int)hour << ":" << (int)minute; + this->outermost_context().setReply(oss.str()); + return transit(); +} + +SetAlarmState::SetAlarmState() { +} + +boost::statechart::result SetTtsState::react(const ProcessInputEvent &e) { + auto manager = Amass::Singleton::instance(); + if (manager) manager->sendMessage(TextToSpeech, e.text); + outermost_context().setReply(e.text.data()); + return transit(); +} diff --git a/Server/WeChatContext/WeChatSession.h b/Server/WeChatContext/WeChatSession.h new file mode 100644 index 0000000..cc74036 --- /dev/null +++ b/Server/WeChatContext/WeChatSession.h @@ -0,0 +1,53 @@ +#ifndef __WECHATSESSION_H__ +#define __WECHATSESSION_H__ + +#include +#include +#include +#include +#include + +class ProcessInputEvent : public boost::statechart::event { +public: + std::string text; +}; + +class IdleState; + +class WeChatSession : public boost::statechart::state_machine { +public: + WeChatSession(const std::string_view &username); + std::string processInput(const std::string_view &text); + void printHelp(); + void printCurrentDateTime(); + void playRandomMusic(); + void stopPlayMusic(); + std::chrono::system_clock::time_point lastAccessedTime() const; + void setReply(std::string &&reply); + +private: + std::string m_username; + std::chrono::system_clock::time_point m_lastAccessedTime; + std::string m_reply; +}; + +class IdleState : public boost::statechart::simple_state { +public: + typedef boost::statechart::custom_reaction reactions; + boost::statechart::result react(const ProcessInputEvent &); +}; + +class SetAlarmState : public boost::statechart::simple_state { +public: + typedef boost::statechart::custom_reaction reactions; + boost::statechart::result react(const ProcessInputEvent &); + SetAlarmState(); +}; + +class SetTtsState : public boost::statechart::simple_state { +public: + typedef boost::statechart::custom_reaction reactions; + boost::statechart::result react(const ProcessInputEvent &); +}; + +#endif // __WECHATSESSION_H__ \ No newline at end of file diff --git a/Server/WebsocketSession.cpp b/Server/WebsocketSession.cpp new file mode 100644 index 0000000..09c528e --- /dev/null +++ b/Server/WebsocketSession.cpp @@ -0,0 +1,83 @@ +#include "WebsocketSession.h" +#include + +WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr const &state) + : m_ws(std::move(socket)), m_state(state) {} + +WebSocketSession::~WebSocketSession() { + // Remove this session from the list of active sessions + m_state->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; + } + + // Add this session to the list of active sessions + m_state->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())); + + // 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())); +} + +void WebSocketSession::send(std::shared_ptr 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(&WebSocketSession::onSend, shared_from_this(), ss)); +} + +void WebSocketSession::onSend(std::shared_ptr 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(&WebSocketSession::on_write, shared_from_this())); +} + +void WebSocketSession::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()); + + // Send the next message if any + if (!m_queue.empty()) + m_ws.async_write(boost::asio::buffer(*m_queue.front()), + boost::beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this())); +} diff --git a/Server/WebsocketSession.h b/Server/WebsocketSession.h new file mode 100644 index 0000000..7f56bd3 --- /dev/null +++ b/Server/WebsocketSession.h @@ -0,0 +1,56 @@ +#ifndef WEBSOCKETSESSION_H +#define WEBSOCKETSESSION_H + +#include "BoostLog.h" +#include "SharedState.h" +#include +#include +#include +#include +#include + +class SharedState; + +/** + * @brief Represents an active WebSocket connection to the server + */ +class WebSocketSession : public std::enable_shared_from_this { + +public: + WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr const &state); + + ~WebSocketSession(); + + template + void run(boost::beast::http::request> 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)); + + // 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 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 const &ss); + +private: + boost::beast::flat_buffer m_buffer; + boost::beast::websocket::stream m_ws; + std::shared_ptr m_state; + std::vector> m_queue; +}; + +#endif // WEBSOCKETSESSION_H diff --git a/Server/conf/fastcgi.conf b/Server/conf/fastcgi.conf new file mode 100644 index 0000000..0582e00 --- /dev/null +++ b/Server/conf/fastcgi.conf @@ -0,0 +1,26 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/Server/conf/fastcgi.conf.default b/Server/conf/fastcgi.conf.default new file mode 100644 index 0000000..0582e00 --- /dev/null +++ b/Server/conf/fastcgi.conf.default @@ -0,0 +1,26 @@ + +fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/Server/conf/fastcgi_params b/Server/conf/fastcgi_params new file mode 100644 index 0000000..c5b49a9 --- /dev/null +++ b/Server/conf/fastcgi_params @@ -0,0 +1,25 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/Server/conf/fastcgi_params.default b/Server/conf/fastcgi_params.default new file mode 100644 index 0000000..c5b49a9 --- /dev/null +++ b/Server/conf/fastcgi_params.default @@ -0,0 +1,25 @@ + +fastcgi_param QUERY_STRING $query_string; +fastcgi_param REQUEST_METHOD $request_method; +fastcgi_param CONTENT_TYPE $content_type; +fastcgi_param CONTENT_LENGTH $content_length; + +fastcgi_param SCRIPT_NAME $fastcgi_script_name; +fastcgi_param REQUEST_URI $request_uri; +fastcgi_param DOCUMENT_URI $document_uri; +fastcgi_param DOCUMENT_ROOT $document_root; +fastcgi_param SERVER_PROTOCOL $server_protocol; +fastcgi_param REQUEST_SCHEME $scheme; +fastcgi_param HTTPS $https if_not_empty; + +fastcgi_param GATEWAY_INTERFACE CGI/1.1; +fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; + +fastcgi_param REMOTE_ADDR $remote_addr; +fastcgi_param REMOTE_PORT $remote_port; +fastcgi_param SERVER_ADDR $server_addr; +fastcgi_param SERVER_PORT $server_port; +fastcgi_param SERVER_NAME $server_name; + +# PHP only, required if PHP was built with --enable-force-cgi-redirect +fastcgi_param REDIRECT_STATUS 200; diff --git a/Server/conf/koi-utf b/Server/conf/koi-utf new file mode 100644 index 0000000..1a32b36 --- /dev/null +++ b/Server/conf/koi-utf @@ -0,0 +1,109 @@ + +# This map is not a full koi8-r <> utf8 map: it does not contain +# box-drawing and some other characters. Besides this map contains +# several koi8-u and Byelorussian letters which are not in koi8-r. +# If you need a full and standard map, use contrib/unicode2nginx/koi-utf +# map instead. + +charset_map koi8-r utf-8 { + + 80 E282AC ; # euro + + 95 E280A2 ; # bullet + + 9A C2A0 ; #   + + 9E C2B7 ; # · + + A3 D191 ; # small yo + A4 D194 ; # small Ukrainian ye + + A6 D196 ; # small Ukrainian i + A7 D197 ; # small Ukrainian yi + + AD D291 ; # small Ukrainian soft g + AE D19E ; # small Byelorussian short u + + B0 C2B0 ; # ° + + B3 D081 ; # capital YO + B4 D084 ; # capital Ukrainian YE + + B6 D086 ; # capital Ukrainian I + B7 D087 ; # capital Ukrainian YI + + B9 E28496 ; # numero sign + + BD D290 ; # capital Ukrainian soft G + BE D18E ; # capital Byelorussian short U + + BF C2A9 ; # (C) + + C0 D18E ; # small yu + C1 D0B0 ; # small a + C2 D0B1 ; # small b + C3 D186 ; # small ts + C4 D0B4 ; # small d + C5 D0B5 ; # small ye + C6 D184 ; # small f + C7 D0B3 ; # small g + C8 D185 ; # small kh + C9 D0B8 ; # small i + CA D0B9 ; # small j + CB D0BA ; # small k + CC D0BB ; # small l + CD D0BC ; # small m + CE D0BD ; # small n + CF D0BE ; # small o + + D0 D0BF ; # small p + D1 D18F ; # small ya + D2 D180 ; # small r + D3 D181 ; # small s + D4 D182 ; # small t + D5 D183 ; # small u + D6 D0B6 ; # small zh + D7 D0B2 ; # small v + D8 D18C ; # small soft sign + D9 D18B ; # small y + DA D0B7 ; # small z + DB D188 ; # small sh + DC D18D ; # small e + DD D189 ; # small shch + DE D187 ; # small ch + DF D18A ; # small hard sign + + E0 D0AE ; # capital YU + E1 D090 ; # capital A + E2 D091 ; # capital B + E3 D0A6 ; # capital TS + E4 D094 ; # capital D + E5 D095 ; # capital YE + E6 D0A4 ; # capital F + E7 D093 ; # capital G + E8 D0A5 ; # capital KH + E9 D098 ; # capital I + EA D099 ; # capital J + EB D09A ; # capital K + EC D09B ; # capital L + ED D09C ; # capital M + EE D09D ; # capital N + EF D09E ; # capital O + + F0 D09F ; # capital P + F1 D0AF ; # capital YA + F2 D0A0 ; # capital R + F3 D0A1 ; # capital S + F4 D0A2 ; # capital T + F5 D0A3 ; # capital U + F6 D096 ; # capital ZH + F7 D092 ; # capital V + F8 D0AC ; # capital soft sign + F9 D0AB ; # capital Y + FA D097 ; # capital Z + FB D0A8 ; # capital SH + FC D0AD ; # capital E + FD D0A9 ; # capital SHCH + FE D0A7 ; # capital CH + FF D0AA ; # capital hard sign +} diff --git a/Server/conf/koi-win b/Server/conf/koi-win new file mode 100644 index 0000000..2ce48bc --- /dev/null +++ b/Server/conf/koi-win @@ -0,0 +1,103 @@ + +charset_map koi8-r windows-1251 { + + 80 88 ; # euro + + 95 95 ; # bullet + + 9A A0 ; #   + + 9E B7 ; # · + + A3 B8 ; # small yo + A4 BA ; # small Ukrainian ye + + A6 B3 ; # small Ukrainian i + A7 BF ; # small Ukrainian yi + + AD B4 ; # small Ukrainian soft g + AE A2 ; # small Byelorussian short u + + B0 B0 ; # ° + + B3 A8 ; # capital YO + B4 AA ; # capital Ukrainian YE + + B6 B2 ; # capital Ukrainian I + B7 AF ; # capital Ukrainian YI + + B9 B9 ; # numero sign + + BD A5 ; # capital Ukrainian soft G + BE A1 ; # capital Byelorussian short U + + BF A9 ; # (C) + + C0 FE ; # small yu + C1 E0 ; # small a + C2 E1 ; # small b + C3 F6 ; # small ts + C4 E4 ; # small d + C5 E5 ; # small ye + C6 F4 ; # small f + C7 E3 ; # small g + C8 F5 ; # small kh + C9 E8 ; # small i + CA E9 ; # small j + CB EA ; # small k + CC EB ; # small l + CD EC ; # small m + CE ED ; # small n + CF EE ; # small o + + D0 EF ; # small p + D1 FF ; # small ya + D2 F0 ; # small r + D3 F1 ; # small s + D4 F2 ; # small t + D5 F3 ; # small u + D6 E6 ; # small zh + D7 E2 ; # small v + D8 FC ; # small soft sign + D9 FB ; # small y + DA E7 ; # small z + DB F8 ; # small sh + DC FD ; # small e + DD F9 ; # small shch + DE F7 ; # small ch + DF FA ; # small hard sign + + E0 DE ; # capital YU + E1 C0 ; # capital A + E2 C1 ; # capital B + E3 D6 ; # capital TS + E4 C4 ; # capital D + E5 C5 ; # capital YE + E6 D4 ; # capital F + E7 C3 ; # capital G + E8 D5 ; # capital KH + E9 C8 ; # capital I + EA C9 ; # capital J + EB CA ; # capital K + EC CB ; # capital L + ED CC ; # capital M + EE CD ; # capital N + EF CE ; # capital O + + F0 CF ; # capital P + F1 DF ; # capital YA + F2 D0 ; # capital R + F3 D1 ; # capital S + F4 D2 ; # capital T + F5 D3 ; # capital U + F6 C6 ; # capital ZH + F7 C2 ; # capital V + F8 DC ; # capital soft sign + F9 DB ; # capital Y + FA C7 ; # capital Z + FB D8 ; # capital SH + FC DD ; # capital E + FD D9 ; # capital SHCH + FE D7 ; # capital CH + FF DA ; # capital hard sign +} diff --git a/Server/conf/mime.types b/Server/conf/mime.types new file mode 100644 index 0000000..e20549a --- /dev/null +++ b/Server/conf/mime.types @@ -0,0 +1,97 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/Server/conf/mime.types.default b/Server/conf/mime.types.default new file mode 100644 index 0000000..e20549a --- /dev/null +++ b/Server/conf/mime.types.default @@ -0,0 +1,97 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/Server/conf/nginx.conf b/Server/conf/nginx.conf new file mode 100644 index 0000000..f9dd190 --- /dev/null +++ b/Server/conf/nginx.conf @@ -0,0 +1,239 @@ + +user root; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + rewrite_log on; + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + gzip on; + gzip_types application/octet-stream text/markdown text/plain application/json application/x-javascript text/css application/xml text/javascript application/javascript application/x-httpd-php image/jpeg image/gif image/png; + + upstream local { + server 127.0.0.1:8080; + } + + upstream wiznote { + server 127.0.0.1:8081; + } + + upstream gitea { + server 127.0.0.1:8082; + } + + upstream twikoo { + server 127.0.0.1:8089; + } + + upstream drone_server { + server 127.0.0.1:1080; + } + + init_by_lua_file lua/settings.lua; + + server { + listen 443 ssl; + server_name wiznote.amass.fun; + + ssl_certificate cert/9890678_wiznote.amass.fun.pem; + ssl_certificate_key cert/9890678_wiznote.amass.fun.key; + ssl_session_timeout 5m; #缓存有效期 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法 + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议 + ssl_prefer_server_ciphers on; #使用服务器端的首选算法 + + location / { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header x-wiz-real-ip $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://wiznote; + } + } + + server { + listen 443 ssl; + server_name gitea.amass.fun; + + ssl_certificate cert/gitea.amass.fun.pem; + ssl_certificate_key cert/gitea.amass.fun.key; + ssl_session_timeout 5m; #缓存有效期 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法 + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议 + ssl_prefer_server_ciphers on; #使用服务器端的首选算法 + + location / { + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://gitea; + } + } + + server { + listen 443 ssl; + server_name drone.amass.fun; + + ssl_certificate cert/8839053_drone.amass.fun.pem; + ssl_certificate_key cert/8839053_drone.amass.fun.key; + ssl_session_timeout 5m; #缓存有效期 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法 + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议 + ssl_prefer_server_ciphers on; #使用服务器端的首选算法 + + location / { + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://drone_server; + } + } + + server { + listen 443 ssl; + server_name amass.fun; + + ssl_certificate cert/6232035_amass.fun.pem; + ssl_certificate_key cert/6232035_amass.fun.key; + ssl_session_timeout 5m; #缓存有效期 + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法 + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议 + ssl_prefer_server_ciphers on; #使用服务器端的首选算法 + + location / { + root amass_blog; + index index.html index.htm; + } + + location /lua { + default_type text/html; + content_by_lua_file lua/helloworld.lua; + } + + location = /blog/login { + content_by_lua_file lua/login.lua; + } + + location = /blog/profile { + content_by_lua_file lua/profile.lua; + } + + location /video { + access_by_lua_file lua/access.lua; + proxy_pass http://local; + } + + location /phtot_gallery { + proxy_pass http://local; + } + + location = /blog/list { + header_filter_by_lua_block { + ngx.header.content_length = nil + } + body_filter_by_lua_file lua/blog_list.lua; # 过滤掉 隐私文件夹 + proxy_pass http://local; + } + + location = /search/website_collections { + content_by_lua_file lua/request_website_collections.lua; + } + + location ~ /trigger-ci.+$ { + proxy_pass http://local; + } + + location ~ /notify.*$ { + proxy_pass http://local; + } + + location /InstallerRepository { + root .; + index index.html index.htm; + } + + location /Younger/ChatRoom { + + proxy_pass http://local; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 1200s; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + location ^~ /index/ { + try_files $uri /index.html; + } + + location /wechat { + proxy_pass http://local; + } + + location /twikoo { + proxy_pass http://twikoo; + } + } + + server { + listen 80; + server_name wiznote.amass.fun; + rewrite ^(.*)$ https://wiznote.amass.fun$1 permanent; + } + + server { + listen 80; + server_name gitea.amass.fun; + rewrite ^(.*)$ https://gitea.amass.fun$1 permanent; + } + + server { + listen 80; + server_name drone.amass.fun; + rewrite ^(.*)$ https://drone.amass.fun$1 permanent; + } + + server { + listen 80; + server_name amass.fun; + location /resource { + root .; + } + location / { + rewrite ^(.*)$ https://amass.fun$1 permanent; + } + } +} diff --git a/Server/conf/nginx.conf.default b/Server/conf/nginx.conf.default new file mode 100644 index 0000000..a34dd04 --- /dev/null +++ b/Server/conf/nginx.conf.default @@ -0,0 +1,117 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + #gzip on; + + server { + listen 80; + server_name localhost; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + location / { + root html; + index index.html index.htm; + } + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } + + + # another virtual host using mix of IP-, name-, and port-based configuration + # + #server { + # listen 8000; + # listen somename:8080; + # server_name somename alias another.alias; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + + # HTTPS server + # + #server { + # listen 443 ssl; + # server_name localhost; + + # ssl_certificate cert.pem; + # ssl_certificate_key cert.key; + + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 5m; + + # ssl_ciphers HIGH:!aNULL:!MD5; + # ssl_prefer_server_ciphers on; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + +} diff --git a/Server/conf/scgi_params b/Server/conf/scgi_params new file mode 100644 index 0000000..af6f736 --- /dev/null +++ b/Server/conf/scgi_params @@ -0,0 +1,17 @@ + +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; +scgi_param REQUEST_SCHEME $scheme; +scgi_param HTTPS $https if_not_empty; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/Server/conf/scgi_params.default b/Server/conf/scgi_params.default new file mode 100644 index 0000000..af6f736 --- /dev/null +++ b/Server/conf/scgi_params.default @@ -0,0 +1,17 @@ + +scgi_param REQUEST_METHOD $request_method; +scgi_param REQUEST_URI $request_uri; +scgi_param QUERY_STRING $query_string; +scgi_param CONTENT_TYPE $content_type; + +scgi_param DOCUMENT_URI $document_uri; +scgi_param DOCUMENT_ROOT $document_root; +scgi_param SCGI 1; +scgi_param SERVER_PROTOCOL $server_protocol; +scgi_param REQUEST_SCHEME $scheme; +scgi_param HTTPS $https if_not_empty; + +scgi_param REMOTE_ADDR $remote_addr; +scgi_param REMOTE_PORT $remote_port; +scgi_param SERVER_PORT $server_port; +scgi_param SERVER_NAME $server_name; diff --git a/Server/conf/uwsgi_params b/Server/conf/uwsgi_params new file mode 100644 index 0000000..226f701 --- /dev/null +++ b/Server/conf/uwsgi_params @@ -0,0 +1,17 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/Server/conf/uwsgi_params.default b/Server/conf/uwsgi_params.default new file mode 100644 index 0000000..226f701 --- /dev/null +++ b/Server/conf/uwsgi_params.default @@ -0,0 +1,17 @@ + +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/Server/conf/win-utf b/Server/conf/win-utf new file mode 100644 index 0000000..51fa705 --- /dev/null +++ b/Server/conf/win-utf @@ -0,0 +1,126 @@ + +# This map is not a full windows-1251 <> utf8 map: it does not +# contain Serbian and Macedonian letters. If you need a full map, +# use contrib/unicode2nginx/win-utf map instead. + +charset_map windows-1251 utf-8 { + + 82 E2809A ; # single low-9 quotation mark + + 84 E2809E ; # double low-9 quotation mark + 85 E280A6 ; # ellipsis + 86 E280A0 ; # dagger + 87 E280A1 ; # double dagger + 88 E282AC ; # euro + 89 E280B0 ; # per mille + + 91 E28098 ; # left single quotation mark + 92 E28099 ; # right single quotation mark + 93 E2809C ; # left double quotation mark + 94 E2809D ; # right double quotation mark + 95 E280A2 ; # bullet + 96 E28093 ; # en dash + 97 E28094 ; # em dash + + 99 E284A2 ; # trade mark sign + + A0 C2A0 ; #   + A1 D18E ; # capital Byelorussian short U + A2 D19E ; # small Byelorussian short u + + A4 C2A4 ; # currency sign + A5 D290 ; # capital Ukrainian soft G + A6 C2A6 ; # borken bar + A7 C2A7 ; # section sign + A8 D081 ; # capital YO + A9 C2A9 ; # (C) + AA D084 ; # capital Ukrainian YE + AB C2AB ; # left-pointing double angle quotation mark + AC C2AC ; # not sign + AD C2AD ; # soft hypen + AE C2AE ; # (R) + AF D087 ; # capital Ukrainian YI + + B0 C2B0 ; # ° + B1 C2B1 ; # plus-minus sign + B2 D086 ; # capital Ukrainian I + B3 D196 ; # small Ukrainian i + B4 D291 ; # small Ukrainian soft g + B5 C2B5 ; # micro sign + B6 C2B6 ; # pilcrow sign + B7 C2B7 ; # · + B8 D191 ; # small yo + B9 E28496 ; # numero sign + BA D194 ; # small Ukrainian ye + BB C2BB ; # right-pointing double angle quotation mark + + BF D197 ; # small Ukrainian yi + + C0 D090 ; # capital A + C1 D091 ; # capital B + C2 D092 ; # capital V + C3 D093 ; # capital G + C4 D094 ; # capital D + C5 D095 ; # capital YE + C6 D096 ; # capital ZH + C7 D097 ; # capital Z + C8 D098 ; # capital I + C9 D099 ; # capital J + CA D09A ; # capital K + CB D09B ; # capital L + CC D09C ; # capital M + CD D09D ; # capital N + CE D09E ; # capital O + CF D09F ; # capital P + + D0 D0A0 ; # capital R + D1 D0A1 ; # capital S + D2 D0A2 ; # capital T + D3 D0A3 ; # capital U + D4 D0A4 ; # capital F + D5 D0A5 ; # capital KH + D6 D0A6 ; # capital TS + D7 D0A7 ; # capital CH + D8 D0A8 ; # capital SH + D9 D0A9 ; # capital SHCH + DA D0AA ; # capital hard sign + DB D0AB ; # capital Y + DC D0AC ; # capital soft sign + DD D0AD ; # capital E + DE D0AE ; # capital YU + DF D0AF ; # capital YA + + E0 D0B0 ; # small a + E1 D0B1 ; # small b + E2 D0B2 ; # small v + E3 D0B3 ; # small g + E4 D0B4 ; # small d + E5 D0B5 ; # small ye + E6 D0B6 ; # small zh + E7 D0B7 ; # small z + E8 D0B8 ; # small i + E9 D0B9 ; # small j + EA D0BA ; # small k + EB D0BB ; # small l + EC D0BC ; # small m + ED D0BD ; # small n + EE D0BE ; # small o + EF D0BF ; # small p + + F0 D180 ; # small r + F1 D181 ; # small s + F2 D182 ; # small t + F3 D183 ; # small u + F4 D184 ; # small f + F5 D185 ; # small kh + F6 D186 ; # small ts + F7 D187 ; # small ch + F8 D188 ; # small sh + F9 D189 ; # small shch + FA D18A ; # small hard sign + FB D18B ; # small y + FC D18C ; # small soft sign + FD D18D ; # small e + FE D18E ; # small yu + FF D18F ; # small ya +} diff --git a/Server/main.cpp b/Server/main.cpp new file mode 100644 index 0000000..81260ed --- /dev/null +++ b/Server/main.cpp @@ -0,0 +1,183 @@ +#include "AlarmClockServer.h" +#include "BoostLog.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" +#include +#include +#include +#include + +void initSettings(); + +int main(int argc, char const *argv[]) { + initBoostLog("HttpServer"); + auto manager = Amass::Singleton::instance(); + + boost::program_options::options_description description("Allowed options"); + // clang-format off + description.add_options() + ("help,h", "produce help message.") + ("prefix", boost::program_options::value(),"set prefix path (default: ${pwd} )"); + // clang-format on + boost::program_options::variables_map values; + try { + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, description), values); + boost::program_options::notify(values); + } catch (const boost::program_options::invalid_command_line_syntax &e) { + LOG(fatal) << e.what(); + std::exit(-1); + } + + if (values.count("help")) { + std::cout << description << std::endl; + std::exit(0); + } + + std::error_code error; + auto prefix = std::filesystem::current_path(error); + if (error) { + LOG(fatal) << "cannot get current path,reason: " << error.message(); + return -1; + } + + if (values.count("prefix")) { + prefix = values["prefix"].as(); + if (prefix.empty() || !std::filesystem::exists(prefix)) { + LOG(fatal) << "working directory: " << prefix << " is not exists."; + return -1; + } + std::filesystem::current_path(prefix, error); + LOG_IF(fatal, error) << "cannot set current path,reason: " << error.message(); + } + initSettings(); + + 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("server"); + } + + auto port = ptree.get_optional("port"); + + auto docRoot = ptree.get("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); + } + + auto fileRoot = ptree.get("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); + } + + auto notebookRoot = ptree.get("notebookRoot"); + if (notebookRoot.empty()) { + LOG(fatal) << "please set notebookRoot."; + std::exit(105); + } else if (!std::filesystem::exists(notebookRoot)) { + LOG(fatal) << "notebook root: " << notebookRoot << " is not exists..."; + std::exit(106); + } + + if (!port) { + LOG(fatal) << "port is not a number."; + std::exit(255); + } + auto threads = ptree.get("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::instance(threads); + auto address = boost::asio::ip::make_address(server); + auto listener = std::make_shared(*ioContext->ioContext(), boost::asio::ip::tcp::endpoint{address, *port}, + std::make_shared(*ioContext->ioContext(), docRoot)); + listener->startAccept(); + + auto state = listener->state(); + state->setFileRoot(fileRoot); + state->setNotebookRoot(notebookRoot); + + auto alarmClockServer = Amass::Singleton::instance(*ioContext->ioContext()); + alarmClockServer->listen(server, "8089"); + + auto wechatContext = Amass::Singleton::instance(*ioContext->ioContext()); + auto corpContext = Amass::Singleton::instance(*ioContext->ioContext()); + + LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << threads; + LOG(info) << "working directory: " << prefix.generic_string(); + LOG(info) << "server: " << server << ",port: " << *port; + LOG(info) << "document root: " << state->docRoot(); + LOG(info) << "notebook root: " << state->notebookRoot(); + + // Capture SIGINT and SIGTERM to perform a clean shutdown +#ifndef WIN32 + boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM, SIGHUP); +#else + boost::asio::signal_set signals(*ioContext->ioContext(), SIGINT, SIGTERM); +#endif + signals.async_wait([&ioContext](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(); + }); + + auto udpServer = std::make_shared(*ioContext->ioContext()); + + auto proxyAddress = boost::asio::ip::make_address(server); + uint16_t proxyPort = 41091; + auto proxy = std::make_shared(*ioContext->ioContext(), + boost::asio::ip::tcp::endpoint{proxyAddress, proxyPort}); + boost::system::error_code perror; + proxy->run(perror); + + LOG(info) << "server start successful ..."; + ioContext->run(); + 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", 8080); + } + if (ptree.find("docRoot") == ptree.not_found()) { + ptree.put("docRoot", "."); + } + if (ptree.find("fileRoot") == ptree.not_found()) { + ptree.put("fileRoot", "."); + } + if (ptree.find("notebookRoot") == ptree.not_found()) { + ptree.put("notebookRoot", "."); + } + if (ptree.find("threads") == ptree.not_found()) { + ptree.put("threads", std::thread::hardware_concurrency()); + } + + boost::property_tree::write_ini("settings.ini", ptree); +} diff --git a/resource/deploy.sh b/resource/deploy.sh new file mode 100755 index 0000000..3369221 --- /dev/null +++ b/resource/deploy.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +base_path=$(pwd) +build_path=${base_path}/build +libraries_root="/opt/Libraries" +server_location=/root/HttpServer + +function cmake_scan() { + if [ ! -d ${build_path} ]; then + mkdir ${build_path} + fi + /opt/Qt/Tools/CMake/bin/cmake \ + -G Ninja \ + -S ${base_path} \ + -B ${build_path} \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBOOST_ROOT=${libraries_root}/boost_1_82_0 \ + -DZeroMQ_ROOT=${libraries_root}/zeromq-4.3.4_debug +} + +function build() { + if [ ! -f "${build_path}/CMakeCache.txt" ]; then + cmake_scan + fi + if [ $? -ne 0 ]; then + exit 1 + fi + /opt/Qt/Tools/CMake/bin/cmake \ + --build ${build_path} \ + --target all +} + +function deploy_backend() { + build + if [ $? -ne 0 ]; then + echo "build backend failed ..." + exit 1 + fi + rsync -azv build/Server/HttpServer Server/conf root@amass.fun:${server_location} + ssh root@amass.fun "pkill HttpServer; source /etc/profile && \ + nginx -p ${server_location} -s reload && \ + cd ${server_location}; \ + nohup ./HttpServer >logs/HttpServer.log 2>&1 &" +} + +function deploy() { + deploy_backend +} + +function main() { + local cmd=$1 + shift 1 + case $cmd in + deploy) + deploy + ;; + build) + build + ;; + *) + build + ;; + esac +} + +main $@ \ No newline at end of file diff --git a/resource/notify.tpl b/resource/notify.tpl new file mode 100644 index 0000000..6151fc1 --- /dev/null +++ b/resource/notify.tpl @@ -0,0 +1,6 @@ +repo: $DRONE_REPO_NAME +status: $DRONE_BUILD_STATUS +commit: $DRONE_COMMIT +message: $DRONE_COMMIT_MESSAGE +build link: $DRONE_BUILD_LINK +repo link: $DRONE_REPO_LINK \ No newline at end of file