Mirgate HttpServer finished.
This commit is contained in:
parent
8919d993ab
commit
15024841d0
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -40,3 +40,4 @@ target_wrapper.*
|
|||
|
||||
# QtCreator CMake
|
||||
CMakeLists.txt.user*
|
||||
build
|
|
@ -1,3 +1,11 @@
|
|||
cmake_minimum_required(VERSION 3.17)
|
||||
|
||||
project(Older)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(Kylin
|
||||
GIT_REPOSITORY https://gitea.amass.fun/amass/Kylin.git
|
||||
)
|
||||
FetchContent_MakeAvailable(Kylin)
|
||||
|
||||
add_subdirectory(Server)
|
92
Server/AlarmClockServer.cpp
Normal file
92
Server/AlarmClockServer.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
#include "AlarmClockServer.h"
|
||||
#include "ServiceManager.h"
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
|
||||
AlarmClockServer::AlarmClockServer(boost::asio::io_context &context)
|
||||
: m_publisher(context, ZeroMQ::SocketType::Pub), m_timer(context) {
|
||||
auto manager = Amass::Singleton<ServiceManager>::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;
|
||||
}
|
28
Server/AlarmClockServer.h
Normal file
28
Server/AlarmClockServer.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef __ALARMCLOCKSERVER_H__
|
||||
#define __ALARMCLOCKSERVER_H__
|
||||
|
||||
#include "ZeroMQSocket.h"
|
||||
#include <Singleton.h>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
class AlarmClockServer {
|
||||
friend class Amass::Singleton<AlarmClockServer>;
|
||||
|
||||
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__
|
27
Server/CMakeLists.txt
Normal file
27
Server/CMakeLists.txt
Normal file
|
@ -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
|
||||
)
|
100
Server/HttpSession.cpp
Normal file
100
Server/HttpSession.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#include "HttpSession.h"
|
||||
#include "WebsocketSession.h"
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/url/parse_path.hpp>
|
||||
#include <boost/url/url_view.hpp>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr<SharedState> &state)
|
||||
: m_stream(std::move(socket)), m_state(state) {
|
||||
// m_buffer.reserve(1000 * 1000 * 1000);
|
||||
}
|
||||
|
||||
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<http::string_body> 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<std::uint64_t>::max());
|
||||
m_parser->header_limit(std::numeric_limits<std::uint32_t>::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<WebSocketSession>(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();
|
||||
}
|
42
Server/HttpSession.h
Normal file
42
Server/HttpSession.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include "SharedState.h"
|
||||
#include "boost/beast.hpp"
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/** Represents an established HTTP connection
|
||||
*/
|
||||
class HttpSession : public std::enable_shared_from_this<HttpSession> {
|
||||
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<boost::beast::http::string_body> &&response);
|
||||
|
||||
public:
|
||||
using Request = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
|
||||
template <typename Response>
|
||||
void reply(const Response &&response) {
|
||||
using ResponseType = typename std::decay_t<decltype(response)>;
|
||||
auto sp = std::make_shared<ResponseType>(std::forward<decltype(response)>(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<std::uint64_t>::max()};
|
||||
SharedStatePtr m_state;
|
||||
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
|
||||
};
|
||||
|
||||
#endif // HTTPSESSION_H
|
64
Server/Listener.cpp
Normal file
64
Server/Listener.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
#include "Listener.h"
|
||||
#include "HttpSession.h"
|
||||
#include <boost/asio.hpp>
|
||||
#include <iostream>
|
||||
|
||||
Listener::Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint,
|
||||
std::shared_ptr<SharedState> const &state)
|
||||
: m_ioContext(ioc), m_acceptor(ioc), m_state(state) {
|
||||
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::ip::tcp::socket>(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<boost::asio::ip::tcp::socket> 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<HttpSession>(std::move(*socket), m_state);
|
||||
session->run();
|
||||
}
|
||||
startAccept();
|
||||
}
|
33
Server/Listener.h
Normal file
33
Server/Listener.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef LISTENER_H
|
||||
#define LISTENER_H
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class SharedState;
|
||||
|
||||
// Accepts incoming connections and launches the sessions
|
||||
class Listener : public std::enable_shared_from_this<Listener> {
|
||||
public:
|
||||
Listener(boost::asio::io_context &ioc, boost::asio::ip::tcp::endpoint endpoint,
|
||||
std::shared_ptr<SharedState> const &state);
|
||||
|
||||
// Start accepting incoming connections
|
||||
void startAccept();
|
||||
inline std::shared_ptr<SharedState> state() const {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
void fail(boost::beast::error_code ec, char const *what);
|
||||
void onAccept(boost::beast::error_code ec, std::shared_ptr<boost::asio::ip::tcp::socket> socket);
|
||||
|
||||
private:
|
||||
boost::asio::io_context &m_ioContext;
|
||||
boost::asio::ip::tcp::acceptor m_acceptor;
|
||||
std::shared_ptr<SharedState> m_state;
|
||||
};
|
||||
|
||||
#endif // LISTENER_H
|
51
Server/ResponseUtility.cpp
Normal file
51
Server/ResponseUtility.cpp
Normal file
|
@ -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
|
19
Server/ResponseUtility.h
Normal file
19
Server/ResponseUtility.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
#ifndef RESPONSEUTILITY_H
|
||||
#define RESPONSEUTILITY_H
|
||||
|
||||
#include <string_view>
|
||||
|
||||
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
|
34
Server/ServiceLogic.cpp
Normal file
34
Server/ServiceLogic.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "ServiceLogic.h"
|
||||
#include <sstream>
|
||||
|
||||
namespace ServiceLogic {
|
||||
|
||||
boost::beast::http::response<boost::beast::http::string_body>
|
||||
notFound(const boost::beast::http::request<boost::beast::http::string_body> &request) {
|
||||
using namespace boost::beast;
|
||||
http::response<http::string_body> 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<boost::beast::http::string_body>
|
||||
serverError(const boost::beast::http::request<boost::beast::http::string_body> &request,
|
||||
std::string_view errorMessage) {
|
||||
using namespace boost::beast;
|
||||
http::response<http::string_body> 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
|
59
Server/ServiceLogic.h
Normal file
59
Server/ServiceLogic.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef SERVICELOGIC_H
|
||||
#define SERVICELOGIC_H
|
||||
|
||||
#include "ResponseUtility.h"
|
||||
#include "SharedState.h"
|
||||
#include "StringUtility.h"
|
||||
#include <boost/beast/http/message.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/vector_body.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <fstream>
|
||||
|
||||
using StringRequest = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
namespace ServiceLogic {
|
||||
|
||||
template <class Send>
|
||||
static void onDownload(SharedStatePtr /*state*/, StringRequest &&request, Send &&send);
|
||||
|
||||
template <class Send>
|
||||
static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send);
|
||||
|
||||
template <class Send>
|
||||
static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix);
|
||||
|
||||
template <class Send>
|
||||
static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send);
|
||||
|
||||
template <class Send>
|
||||
static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&send);
|
||||
|
||||
// Returns a not found response
|
||||
boost::beast::http::response<boost::beast::http::string_body>
|
||||
notFound(const boost::beast::http::request<boost::beast::http::string_body> &request);
|
||||
|
||||
// Returns a server error response
|
||||
boost::beast::http::response<boost::beast::http::string_body>
|
||||
serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view errorMessage);
|
||||
|
||||
template <class ResponseBody, class RequestBody>
|
||||
boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::request<RequestBody> &request,
|
||||
typename ResponseBody::value_type body,
|
||||
boost::beast::string_view content) {
|
||||
boost::beast::http::response<ResponseBody> 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
|
130
Server/ServiceLogic.inl
Normal file
130
Server/ServiceLogic.inl
Normal file
|
@ -0,0 +1,130 @@
|
|||
#ifndef SERVICELOGIC_INL
|
||||
#define SERVICELOGIC_INL
|
||||
|
||||
#include "BoostLog.h"
|
||||
#include "ServiceLogic.h"
|
||||
#include "WeChatContext/WeChatContext.h"
|
||||
#include <boost/beast/http/empty_body.hpp>
|
||||
#include <boost/beast/http/file_body.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/url.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
namespace ServiceLogic {
|
||||
|
||||
template <class Send>
|
||||
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<char> file_string(std::istreambuf_iterator<char>{ifs}, std::istreambuf_iterator<char>{});
|
||||
boost::beast::error_code ec;
|
||||
http::response<boost::beast::http::vector_body<char>> 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 <class Send>
|
||||
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<char>{ifs}, std::istreambuf_iterator<char>{});
|
||||
http::response<boost::beast::http::string_body> 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 <class Send>
|
||||
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<http::file_body> 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 <class Send>
|
||||
static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) {
|
||||
using namespace boost::beast;
|
||||
boost::urls::url url(request.target());
|
||||
auto context = Amass::Singleton<WeChatContext>::instance();
|
||||
http::response<boost::beast::http::string_body> 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
|
24
Server/ServiceManager.h
Normal file
24
Server/ServiceManager.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef __SERVICEMANAGER_H__
|
||||
#define __SERVICEMANAGER_H__
|
||||
|
||||
#include <MessageManager.h>
|
||||
#include <Singleton.h>
|
||||
|
||||
constexpr auto HeartBeat = "HeartBeat";
|
||||
constexpr auto SetAlarmClock = "setAlarmClock";
|
||||
constexpr auto CurrentDatetime = "CurrentDatetime";
|
||||
constexpr auto PlayRandomMusic = "PlayRandomMusic";
|
||||
|
||||
constexpr auto NotifyServerChan = MessageManager::Message<const std::string &>("NotifyServerChan");
|
||||
constexpr auto TextToSpeech = MessageManager::Message<const std::string &>("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<MessageManager>;
|
||||
};
|
||||
|
||||
#endif // __SERVICEMANAGER_H__
|
107
Server/SharedState.cpp
Normal file
107
Server/SharedState.cpp
Normal file
|
@ -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<UrlRouter<Handler>>()}, 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<boost::beast::http::string_body>(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<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
|
||||
});
|
||||
|
||||
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) {
|
||||
auto corp = Amass::Singleton<CorporationContext>::instance();
|
||||
corp->notify(request);
|
||||
session.reply(
|
||||
ServiceLogic::make_200<boost::beast::http::string_body>(request, "notify successed.\n", "text/html"));
|
||||
});
|
||||
}
|
||||
|
||||
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<std::mutex> lock(mutex_);
|
||||
sessions_.insert(session);
|
||||
}
|
||||
|
||||
void SharedState::leave(WebSocketSession *session) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
sessions_.erase(session);
|
||||
}
|
||||
|
||||
void SharedState::send(std::string message) {
|
||||
// Put the message in a shared pointer so we can re-use it for each client
|
||||
auto const ss = std::make_shared<std::string const>(std::move(message));
|
||||
|
||||
// Make a local list of all the weak pointers representing
|
||||
// the sessions, so we can do the actual sending without
|
||||
// holding the mutex:
|
||||
std::vector<std::weak_ptr<WebSocketSession>> v;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
v.reserve(sessions_.size());
|
||||
for (auto p : sessions_) v.emplace_back(p->weak_from_this());
|
||||
}
|
||||
|
||||
// For each session in our local list, try to acquire a strong
|
||||
// pointer. If successful, then send the message on that session.
|
||||
for (auto const &wp : v)
|
||||
if (auto sp = wp.lock()) sp->send(ss);
|
||||
}
|
65
Server/SharedState.h
Normal file
65
Server/SharedState.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef SHAREDSTATE_H
|
||||
#define SHAREDSTATE_H
|
||||
|
||||
#include "TemplateMatchs.h"
|
||||
#include "UrlRouter.h"
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/smart_ptr.hpp>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
class HttpSession;
|
||||
class WebSocketSession;
|
||||
|
||||
// Represents the shared server state
|
||||
class SharedState:public std::enable_shared_from_this<SharedState> {
|
||||
|
||||
// This mutex synchronizes all access to sessions_
|
||||
std::mutex mutex_;
|
||||
|
||||
// Keep a list of all the connected clients
|
||||
std::unordered_set<WebSocketSession *> sessions_;
|
||||
|
||||
public:
|
||||
using Request = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
using Handler = std::function<void(HttpSession &, const Request &, const TemplateMatches &)>;
|
||||
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 <UrlRouter<Handler>> 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<SharedState>;
|
||||
|
||||
#endif // SHAREDSTATE_H
|
78
Server/UdpServer.cpp
Normal file
78
Server/UdpServer.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include "UdpServer.h"
|
||||
#include "BoostLog.h"
|
||||
#include "boost/endian.hpp"
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
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<char> &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<uint32_t> distribution(std::numeric_limits<uint32_t>::min(),
|
||||
std::numeric_limits<uint32_t>::max());
|
||||
|
||||
std::array<char, 408> data;
|
||||
data[0] = 0xAA;
|
||||
data[1] = 0xFF;
|
||||
data[2] = 0x55;
|
||||
data[3] = 0x00;
|
||||
auto bigEndian = reinterpret_cast<uint32_t *>(&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();
|
||||
});
|
||||
}
|
36
Server/UdpServer.h
Normal file
36
Server/UdpServer.h
Normal file
|
@ -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<char> &data);
|
||||
|
||||
void sendData();
|
||||
|
||||
private:
|
||||
boost::asio::ip::udp::socket m_socket;
|
||||
boost::asio::ip::udp::endpoint m_remotePoint; //客户端端点
|
||||
std::vector<char> m_reveiveBuffer;
|
||||
boost::asio::steady_timer m_timer;
|
||||
};
|
||||
|
||||
#endif // UDPSERVER_H
|
113
Server/WeChatContext/CorporationContext.cpp
Normal file
113
Server/WeChatContext/CorporationContext.cpp
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "CorporationContext.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include "BoostLog.h"
|
||||
#include "NetworkUtility.h"
|
||||
#include <boost/asio/defer.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
|
||||
CorporationContext::CorporationContext(boost::asio::io_context &ioContext)
|
||||
: m_ioContext(ioContext), m_timer(ioContext) {
|
||||
boost::asio::defer([this]() { updateAccessToken(); });
|
||||
|
||||
auto manager = Amass::Singleton<ServiceManager>::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;
|
||||
}
|
||||
}
|
41
Server/WeChatContext/CorporationContext.h
Normal file
41
Server/WeChatContext/CorporationContext.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef __CORPORATIONCONTEXT_H__
|
||||
#define __CORPORATIONCONTEXT_H__
|
||||
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
|
||||
class CorporationContext {
|
||||
public:
|
||||
enum MessageType {
|
||||
Text,
|
||||
Markdown,
|
||||
};
|
||||
using RequestType = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
|
||||
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__
|
184
Server/WeChatContext/WeChatContext.cpp
Normal file
184
Server/WeChatContext/WeChatContext.cpp
Normal file
|
@ -0,0 +1,184 @@
|
|||
#include "WeChatContext.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include "BoostLog.h"
|
||||
#include "WeChatSession.h"
|
||||
#include <NetworkUtility.h>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
#include <boost/asio/defer.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/serialize.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/xml_parser.hpp>
|
||||
#include <sstream>
|
||||
|
||||
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<std::string>("xml.ToUserName");
|
||||
if (!ToUserName) {
|
||||
LOG(error) << "request dont contain ToUserName.";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
auto FromUserName = ptree.get<std::string>("xml.FromUserName");
|
||||
auto CreateTime = ptree.get<std::string>("xml.CreateTime");
|
||||
auto MsgType = ptree.get<std::string>("xml.MsgType");
|
||||
auto content = ptree.get<std::string>("xml.Content");
|
||||
auto MsgId = ptree.get<std::string>("xml.MsgId");
|
||||
|
||||
std::shared_ptr<WeChatSession> session;
|
||||
if (m_sessions.count(FromUserName) > 0) {
|
||||
session = m_sessions.at(FromUserName);
|
||||
} else {
|
||||
session = std::make_shared<WeChatSession>(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<std::chrono::seconds>(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);
|
||||
});
|
||||
}
|
49
Server/WeChatContext/WeChatContext.h
Normal file
49
Server/WeChatContext/WeChatContext.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
#ifndef WECHATCONTEXT_H
|
||||
#define WECHATCONTEXT_H
|
||||
|
||||
#include "Singleton.h"
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
class WeChatSession;
|
||||
|
||||
class WeChatContext : public std::enable_shared_from_this<WeChatContext> {
|
||||
friend class Amass::Singleton<WeChatContext>;
|
||||
|
||||
public:
|
||||
using OpenIds = std::vector<std::string>;
|
||||
|
||||
/**
|
||||
* @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<std::string, std::shared_ptr<WeChatSession>> 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
|
101
Server/WeChatContext/WeChatSession.cpp
Normal file
101
Server/WeChatContext/WeChatSession.cpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#include "WeChatSession.h"
|
||||
#include "../ServiceManager.h"
|
||||
#include <BoostLog.h>
|
||||
#include <DateTime.h>
|
||||
|
||||
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<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<CurrentDatetimeService>(CurrentDatetime);
|
||||
setReply("艾玛收到!将为您播报当前时间");
|
||||
}
|
||||
|
||||
void WeChatSession::playRandomMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<PlayRandomMusicService>(PlayRandomMusic);
|
||||
setReply("艾玛收到!将为您随机播放音乐");
|
||||
}
|
||||
|
||||
void WeChatSession::stopPlayMusic() {
|
||||
auto manager = Amass::Singleton<ServiceManager>::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<SetAlarmState>();
|
||||
} else if (text == "2") {
|
||||
outermost_context().setReply("请输入TTS文字:");
|
||||
return transit<SetTtsState>();
|
||||
} 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<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage<SetAlarmClockService>(SetAlarmClock, hour, minute);
|
||||
std::ostringstream oss;
|
||||
oss << "set alarm clock at " << (int)hour << ":" << (int)minute;
|
||||
this->outermost_context().setReply(oss.str());
|
||||
return transit<IdleState>();
|
||||
}
|
||||
|
||||
SetAlarmState::SetAlarmState() {
|
||||
}
|
||||
|
||||
boost::statechart::result SetTtsState::react(const ProcessInputEvent &e) {
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance();
|
||||
if (manager) manager->sendMessage(TextToSpeech, e.text);
|
||||
outermost_context().setReply(e.text.data());
|
||||
return transit<IdleState>();
|
||||
}
|
53
Server/WeChatContext/WeChatSession.h
Normal file
53
Server/WeChatContext/WeChatSession.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef __WECHATSESSION_H__
|
||||
#define __WECHATSESSION_H__
|
||||
|
||||
#include <boost/statechart/custom_reaction.hpp>
|
||||
#include <boost/statechart/simple_state.hpp>
|
||||
#include <boost/statechart/state_machine.hpp>
|
||||
#include <chrono>
|
||||
#include <string_view>
|
||||
|
||||
class ProcessInputEvent : public boost::statechart::event<ProcessInputEvent> {
|
||||
public:
|
||||
std::string text;
|
||||
};
|
||||
|
||||
class IdleState;
|
||||
|
||||
class WeChatSession : public boost::statechart::state_machine<WeChatSession, IdleState> {
|
||||
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<IdleState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
class SetAlarmState : public boost::statechart::simple_state<SetAlarmState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
SetAlarmState();
|
||||
};
|
||||
|
||||
class SetTtsState : public boost::statechart::simple_state<SetTtsState, WeChatSession> {
|
||||
public:
|
||||
typedef boost::statechart::custom_reaction<ProcessInputEvent> reactions;
|
||||
boost::statechart::result react(const ProcessInputEvent &);
|
||||
};
|
||||
|
||||
#endif // __WECHATSESSION_H__
|
83
Server/WebsocketSession.cpp
Normal file
83
Server/WebsocketSession.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include "WebsocketSession.h"
|
||||
#include <iostream>
|
||||
|
||||
WebSocketSession::WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state)
|
||||
: m_ws(std::move(socket)), m_state(state) {}
|
||||
|
||||
WebSocketSession::~WebSocketSession() {
|
||||
// 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<std::string const> const &ss) {
|
||||
// Post our work to the strand, this ensures
|
||||
// that the members of `this` will not be
|
||||
// accessed concurrently.
|
||||
m_ws.text();
|
||||
boost::asio::post(m_ws.get_executor(),
|
||||
boost::beast::bind_front_handler(&WebSocketSession::onSend, shared_from_this(), ss));
|
||||
}
|
||||
|
||||
void WebSocketSession::onSend(std::shared_ptr<std::string const> const &ss) {
|
||||
// Always add to queue
|
||||
m_queue.push_back(ss);
|
||||
|
||||
// Are we already writing?
|
||||
if (m_queue.size() > 1) return;
|
||||
|
||||
// We are not currently writing, so send this immediately
|
||||
m_ws.async_write(boost::asio::buffer(*m_queue.front()),
|
||||
boost::beast::bind_front_handler(&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()));
|
||||
}
|
56
Server/WebsocketSession.h
Normal file
56
Server/WebsocketSession.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#ifndef WEBSOCKETSESSION_H
|
||||
#define WEBSOCKETSESSION_H
|
||||
|
||||
#include "BoostLog.h"
|
||||
#include "SharedState.h"
|
||||
#include <boost/beast.hpp>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class SharedState;
|
||||
|
||||
/**
|
||||
* @brief Represents an active WebSocket connection to the server
|
||||
*/
|
||||
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
|
||||
|
||||
public:
|
||||
WebSocketSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
|
||||
|
||||
~WebSocketSession();
|
||||
|
||||
template <class Body, class Allocator>
|
||||
void run(boost::beast::http::request<Body, boost::beast::http::basic_fields<Allocator>> request) {
|
||||
using namespace boost::beast::http;
|
||||
using namespace boost::beast::websocket;
|
||||
// Set suggested timeout settings for the websocket
|
||||
m_ws.set_option(stream_base::timeout::suggested(boost::beast::role_type::server));
|
||||
|
||||
// Set a decorator to change the Server of the handshake
|
||||
m_ws.set_option(stream_base::decorator([](response_type &response) {
|
||||
response.set(field::server, std::string(BOOST_BEAST_VERSION_STRING) + " websocket-chat-multi");
|
||||
}));
|
||||
// LOG(info) << request.base().target(); //get path
|
||||
// Accept the websocket handshake
|
||||
m_ws.async_accept(request, [self{shared_from_this()}](boost::beast::error_code ec) { self->onAccept(ec); });
|
||||
}
|
||||
|
||||
// Send a message
|
||||
void send(std::shared_ptr<std::string const> const &ss);
|
||||
|
||||
protected:
|
||||
void onAccept(boost::beast::error_code ec);
|
||||
void on_read(boost::beast::error_code ec, std::size_t bytes_transferred);
|
||||
void on_write(boost::beast::error_code ec, std::size_t bytes_transferred);
|
||||
void onSend(std::shared_ptr<std::string const> const &ss);
|
||||
|
||||
private:
|
||||
boost::beast::flat_buffer m_buffer;
|
||||
boost::beast::websocket::stream<boost::beast::tcp_stream> m_ws;
|
||||
std::shared_ptr<SharedState> m_state;
|
||||
std::vector<std::shared_ptr<std::string const>> m_queue;
|
||||
};
|
||||
|
||||
#endif // WEBSOCKETSESSION_H
|
26
Server/conf/fastcgi.conf
Normal file
26
Server/conf/fastcgi.conf
Normal file
|
@ -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;
|
26
Server/conf/fastcgi.conf.default
Normal file
26
Server/conf/fastcgi.conf.default
Normal file
|
@ -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;
|
25
Server/conf/fastcgi_params
Normal file
25
Server/conf/fastcgi_params
Normal file
|
@ -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;
|
25
Server/conf/fastcgi_params.default
Normal file
25
Server/conf/fastcgi_params.default
Normal file
|
@ -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;
|
109
Server/conf/koi-utf
Normal file
109
Server/conf/koi-utf
Normal file
|
@ -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
|
||||
}
|
103
Server/conf/koi-win
Normal file
103
Server/conf/koi-win
Normal file
|
@ -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
|
||||
}
|
97
Server/conf/mime.types
Normal file
97
Server/conf/mime.types
Normal file
|
@ -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;
|
||||
}
|
97
Server/conf/mime.types.default
Normal file
97
Server/conf/mime.types.default
Normal file
|
@ -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;
|
||||
}
|
239
Server/conf/nginx.conf
Normal file
239
Server/conf/nginx.conf
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
117
Server/conf/nginx.conf.default
Normal file
117
Server/conf/nginx.conf.default
Normal file
|
@ -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;
|
||||
# }
|
||||
#}
|
||||
|
||||
}
|
17
Server/conf/scgi_params
Normal file
17
Server/conf/scgi_params
Normal file
|
@ -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;
|
17
Server/conf/scgi_params.default
Normal file
17
Server/conf/scgi_params.default
Normal file
|
@ -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;
|
17
Server/conf/uwsgi_params
Normal file
17
Server/conf/uwsgi_params
Normal file
|
@ -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;
|
17
Server/conf/uwsgi_params.default
Normal file
17
Server/conf/uwsgi_params.default
Normal file
|
@ -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;
|
126
Server/conf/win-utf
Normal file
126
Server/conf/win-utf
Normal file
|
@ -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
|
||||
}
|
183
Server/main.cpp
Normal file
183
Server/main.cpp
Normal file
|
@ -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 <boost/program_options.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
void initSettings();
|
||||
|
||||
int main(int argc, char const *argv[]) {
|
||||
initBoostLog("HttpServer");
|
||||
auto manager = Amass::Singleton<ServiceManager>::instance<Amass::Construct>();
|
||||
|
||||
boost::program_options::options_description description("Allowed options");
|
||||
// clang-format off
|
||||
description.add_options()
|
||||
("help,h", "produce help message.")
|
||||
("prefix", boost::program_options::value<std::string>(),"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<std::string>();
|
||||
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<std::string>("server");
|
||||
}
|
||||
|
||||
auto port = ptree.get_optional<uint16_t>("port");
|
||||
|
||||
auto docRoot = ptree.get<std::string>("docRoot");
|
||||
if (docRoot.empty()) {
|
||||
LOG(fatal) << "please set docRoot.";
|
||||
std::exit(101);
|
||||
} else if (!std::filesystem::exists(docRoot)) {
|
||||
LOG(fatal) << "document root: " << docRoot << " is not exists...";
|
||||
std::exit(102);
|
||||
}
|
||||
|
||||
auto fileRoot = ptree.get<std::string>("fileRoot");
|
||||
if (fileRoot.empty()) {
|
||||
LOG(fatal) << "please set fileRoot.";
|
||||
std::exit(103);
|
||||
} else if (!std::filesystem::exists(fileRoot)) {
|
||||
LOG(fatal) << "file root: " << fileRoot << " is not exists...";
|
||||
std::exit(104);
|
||||
}
|
||||
|
||||
auto notebookRoot = ptree.get<std::string>("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<unsigned int>("threads");
|
||||
if (threads <= 0 || threads > std::thread::hardware_concurrency()) threads = std::thread::hardware_concurrency();
|
||||
|
||||
BOOST_ASSERT_MSG(!server.empty(), "server.empty() == true");
|
||||
|
||||
auto ioContext = Amass::Singleton<IoContext>::instance<Amass::Construct>(threads);
|
||||
auto address = boost::asio::ip::make_address(server);
|
||||
auto listener = std::make_shared<Listener>(*ioContext->ioContext(), boost::asio::ip::tcp::endpoint{address, *port},
|
||||
std::make_shared<SharedState>(*ioContext->ioContext(), docRoot));
|
||||
listener->startAccept();
|
||||
|
||||
auto state = listener->state();
|
||||
state->setFileRoot(fileRoot);
|
||||
state->setNotebookRoot(notebookRoot);
|
||||
|
||||
auto alarmClockServer = Amass::Singleton<AlarmClockServer>::instance<Amass::Construct>(*ioContext->ioContext());
|
||||
alarmClockServer->listen(server, "8089");
|
||||
|
||||
auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext());
|
||||
auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext());
|
||||
|
||||
LOG(info) << "hardware_concurrency: " << std::thread::hardware_concurrency() << ",threads: " << threads;
|
||||
LOG(info) << "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<UdpServer>(*ioContext->ioContext());
|
||||
|
||||
auto proxyAddress = boost::asio::ip::make_address(server);
|
||||
uint16_t proxyPort = 41091;
|
||||
auto proxy = std::make_shared<ProxyListener>(*ioContext->ioContext(),
|
||||
boost::asio::ip::tcp::endpoint{proxyAddress, proxyPort});
|
||||
boost::system::error_code perror;
|
||||
proxy->run(perror);
|
||||
|
||||
LOG(info) << "server start successful ...";
|
||||
ioContext->run<IoContext::Mode::Synchronous>();
|
||||
LOG(info) << "server exit successful ...";
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void initSettings() {
|
||||
boost::property_tree::ptree ptree;
|
||||
|
||||
if (std::filesystem::exists(std::filesystem::path("settings.ini")))
|
||||
boost::property_tree::read_ini("settings.ini", ptree);
|
||||
|
||||
if (ptree.find("server") == ptree.not_found()) {
|
||||
ptree.put("server", "0.0.0.0");
|
||||
}
|
||||
if (ptree.find("port") == ptree.not_found()) {
|
||||
ptree.put("port", 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);
|
||||
}
|
66
resource/deploy.sh
Executable file
66
resource/deploy.sh
Executable file
|
@ -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 $@
|
6
resource/notify.tpl
Normal file
6
resource/notify.tpl
Normal file
|
@ -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
|
Loading…
Reference in New Issue
Block a user