add file surport.

This commit is contained in:
amass 2023-12-31 01:13:26 +08:00
parent fd432b29f6
commit d0773d8b98
8 changed files with 89 additions and 185 deletions

View File

@ -49,8 +49,9 @@ void HttpSession::doRead() {
} }
void HttpSession::onRead(boost::beast::error_code ec, std::size_t) { void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
using namespace boost::beast;
// This means they closed the connection // This means they closed the connection
if (ec == boost::beast::http::error::end_of_stream) { if (ec == http::error::end_of_stream) {
m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec); m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, ec);
return; return;
} }
@ -62,22 +63,28 @@ void HttpSession::onRead(boost::beast::error_code ec, std::size_t) {
auto &request = m_parser->get(); auto &request = m_parser->get();
// See if it is a WebSocket Upgrade // See if it is a WebSocket Upgrade
if (boost::beast::websocket::is_upgrade(request)) { if (websocket::is_upgrade(request)) {
// Create a websocket session, transferring ownership // Create a websocket session, transferring ownership
// of both the socket and the HTTP request. // of both the socket and the HTTP request.
auto session = std::make_shared<WebSocketSession>(m_stream.release_socket(), m_state); auto session = std::make_shared<WebSocketSession>(m_stream.release_socket(), m_state);
session->run(m_parser->release()); session->run(m_parser->release());
return; return;
} }
boost::urls::url_view view(request.target()); auto path = boost::urls::parse_path(request.target());
auto path = boost::urls::parse_path(view.path()); if (!path) {
TemplateMatches matches; LOG(error) << request.target() << "failed, error: " << path.error().message();
errorReply(request, http::status::bad_request, "Illegal request-target");
return;
}
boost::urls::matches matches;
auto handler = m_state->find(*path, matches); auto handler = m_state->find(*path, matches);
if (handler) { if (handler) {
(*handler)(*this, request, matches); (*handler)(*this, request, matches);
} else { } else {
auto message = "The resource '" + std::string(path->buffer()) + "' was not found."; std::ostringstream oss;
errorReply(request, boost::beast::http::status::not_found, message); oss << "The resource '" << request.target() << "' was not found.";
auto message = oss.str();
errorReply(request, http::status::not_found, message);
LOG(error) << message; LOG(error) << message;
} }
} }

View File

@ -20,7 +20,7 @@ public:
using Request = boost::beast::http::request<boost::beast::http::string_body>; using Request = boost::beast::http::request<boost::beast::http::string_body>;
HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state); HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr<SharedState> const &state);
template <typename Response> template <typename Response>
void reply(const Response &&response) { void reply(Response &&response) {
using ResponseType = typename std::decay_t<decltype(response)>; using ResponseType = typename std::decay_t<decltype(response)>;
auto sp = std::make_shared<ResponseType>(std::forward<decltype(response)>(response)); auto sp = std::make_shared<ResponseType>(std::forward<decltype(response)>(response));
boost::beast::http::async_write( boost::beast::http::async_write(

View File

@ -2,20 +2,7 @@
#include <sstream> #include <sstream>
namespace ServiceLogic { 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; 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> boost::beast::http::response<boost::beast::http::string_body>
serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, serverError(const boost::beast::http::request<boost::beast::http::string_body> &request,
@ -31,4 +18,15 @@ serverError(const boost::beast::http::request<boost::beast::http::string_body> &
res.prepare_payload(); res.prepare_payload();
return res; return res;
} }
http::response<http::string_body> badRequest(const http::request<http::string_body> &request, std::string_view why) {
http::response<http::string_body> res{http::status::bad_request, 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() = std::string(why);
res.prepare_payload();
return res;
}
} // namespace ServiceLogic } // namespace ServiceLogic

View File

@ -16,9 +16,6 @@ using StringRequest = boost::beast::http::request<boost::beast::http::string_bod
namespace ServiceLogic { namespace ServiceLogic {
template <class Send>
static void onDownload(SharedStatePtr /*state*/, StringRequest &&request, Send &&send);
template <class Send> template <class Send>
static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send); static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send);
@ -31,14 +28,13 @@ static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &
template <class Send> template <class Send>
static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&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 // Returns a server error response
boost::beast::http::response<boost::beast::http::string_body> 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); serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view errorMessage);
boost::beast::http::response<boost::beast::http::string_body>
badRequest(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view why);
template <class ResponseBody, class RequestBody> template <class ResponseBody, class RequestBody>
boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::request<RequestBody> &request, boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::request<RequestBody> &request,
typename ResponseBody::value_type body, typename ResponseBody::value_type body,

View File

@ -14,94 +14,6 @@
namespace ServiceLogic { 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> template <class Send>
static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) { static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) {
using namespace boost::beast; using namespace boost::beast;

View File

@ -5,47 +5,66 @@
#include "WebsocketSession.h" #include "WebsocketSession.h"
SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_root) 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_ioContext(ioContext), m_router{std::make_shared<boost::urls::router<Handler>>()},
m_docRoot(std::move(doc_root)) {
m_router->insert("/", [](HttpSession &session, const Request &request, const TemplateMatches &matches) { m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
// Send content message to client and wait to receive next request using namespace boost::beast;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html")); boost::urls::url_view view(request.target());
auto target = view.path();
LOG(info) << target;
if (target.find("..") != boost::beast::string_view::npos) {
session.reply(ServiceLogic::badRequest(request, "Illegal request-target"));
return;
}
std::string path = ResponseUtility::pathCat(m_docRoot, target);
if (target.back() == '/') path.append("index.html");
if (std::filesystem::is_directory(path)) path.append("/index.html");
boost::beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), boost::beast::file_mode::scan, ec);
if (ec == boost::beast::errc::no_such_file_or_directory) {
std::ostringstream oss;
oss << "The resource '" << target << "' was not found.";
session.errorReply(request, http::status::not_found, oss.str());
return;
} else if (ec) {
session.reply(ServiceLogic::serverError(request, ec.message()));
return;
}
auto const size = body.size();
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.content_length(size);
res.keep_alive(request.keep_alive());
session.reply(std::move(res));
}); });
m_router->insert("/wechat/{session*}", m_router->insert("/wechat/{session*}",
[this](HttpSession &session, const Request &request, const TemplateMatches &matches) { [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
ServiceLogic::onWechat(shared_from_this(), request, ServiceLogic::onWechat(shared_from_this(), request,
[&session](auto &&response) { session.reply(std::move(response)); }); [&session](auto &&response) { session.reply(std::move(response)); });
}); });
// m_router->all(R"(^/download/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { m_router->insert("/api/v1/tasklist",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
// ServiceLogic::onDownload(shared_from_this(), std::move(request), using namespace boost::beast;
// [&context](auto &&response) { context.send(std::move(response)); }); http::response<boost::beast::http::string_body> s;
// }); s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
// m_router->all(R"(^/blog/list$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { s.set(http::field::content_type, "text/markdown;charset=UTF-8");
// ServiceLogic::onGetBlogList(shared_from_this(), std::move(request), s.keep_alive(request.keep_alive());
// [&context](auto &&response) { context.send(response); }); s.body() = "{}";
// }); s.prepare_payload();
session.reply(std::move(s));
});
// m_router->all(R"(^/blog/image/.+$)", [this](HttpSession::RequestType request, HttpSession::ContextType context) { m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
// 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; LOG(info) << "webhook: " << request;
session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html")); session.reply(ServiceLogic::make_200<boost::beast::http::string_body>(request, "Main page\n", "text/html"));
}); });
m_router->insert("/notify", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) { m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) {
auto corp = Amass::Singleton<CorporationContext>::instance(); auto corp = Amass::Singleton<CorporationContext>::instance();
corp->notify(request); corp->notify(request);
session.reply( session.reply(
@ -54,7 +73,7 @@ SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_roo
} }
const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path, const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path,
TemplateMatchStorageBase &matches) const noexcept { boost::urls::matches_base &matches) const noexcept {
return m_router->find(path, matches); return m_router->find(path, matches);
} }
@ -67,15 +86,6 @@ void SharedState::setFileRoot(const std::string_view &root) {
m_fileRoot = root; 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) { void SharedState::join(WebSocketSession *session) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
sessions_.insert(session); sessions_.insert(session);

View File

@ -1,8 +1,7 @@
#ifndef SHAREDSTATE_H #ifndef SHAREDSTATE_H
#define SHAREDSTATE_H #define SHAREDSTATE_H
#include "TemplateMatchs.h" #include "router.hpp"
#include "UrlRouter.h"
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <boost/beast/http/string_body.hpp> #include <boost/beast/http/string_body.hpp>
#include <boost/smart_ptr.hpp> #include <boost/smart_ptr.hpp>
@ -25,10 +24,10 @@ class SharedState:public std::enable_shared_from_this<SharedState> {
public: public:
using Request = boost::beast::http::request<boost::beast::http::string_body>; using Request = boost::beast::http::request<boost::beast::http::string_body>;
using Handler = std::function<void(HttpSession &, const Request &, const TemplateMatches &)>; using Handler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
SharedState(boost::asio::io_context &ioContext, std::string doc_root); SharedState(boost::asio::io_context &ioContext, std::string doc_root);
const Handler *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept ; const Handler *find(boost::urls::segments_encoded_view path, boost::urls::matches_base &matches) const noexcept;
inline std::string_view docRoot() const noexcept { inline std::string_view docRoot() const noexcept {
return m_docRoot; return m_docRoot;
@ -40,9 +39,6 @@ public:
} }
void setFileRoot(const std::string_view &root); void setFileRoot(const std::string_view &root);
std::string_view notebookRoot() const;
void setNotebookRoot(const std::string_view &root);
void join(WebSocketSession *session); void join(WebSocketSession *session);
void leave(WebSocketSession *session); void leave(WebSocketSession *session);
@ -53,11 +49,10 @@ public:
private: private:
boost::asio::io_context &m_ioContext; boost::asio::io_context &m_ioContext;
std::shared_ptr <UrlRouter<Handler>> m_router; std::shared_ptr<boost::urls::router<Handler>> m_router;
std::string m_docRoot; std::string m_docRoot;
std::string m_galleryRoot = "/root/photos"; std::string m_galleryRoot = "/root/photos";
std::string m_fileRoot; std::string m_fileRoot;
std::string m_notebookRoot;
}; };
using SharedStatePtr = std::shared_ptr<SharedState>; using SharedStatePtr = std::shared_ptr<SharedState>;

View File

@ -88,15 +88,6 @@ int main(int argc, char const *argv[]) {
std::exit(104); 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) { if (!port) {
LOG(fatal) << "port is not a number."; LOG(fatal) << "port is not a number.";
std::exit(255); std::exit(255);
@ -114,7 +105,6 @@ int main(int argc, char const *argv[]) {
auto state = listener->state(); auto state = listener->state();
state->setFileRoot(fileRoot); state->setFileRoot(fileRoot);
state->setNotebookRoot(notebookRoot);
auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext()); auto wechatContext = Amass::Singleton<WeChatContext>::instance<Amass::Construct>(*ioContext->ioContext());
auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext()); auto corpContext = Amass::Singleton<CorporationContext>::instance<Amass::Construct>(*ioContext->ioContext());
@ -123,7 +113,6 @@ int main(int argc, char const *argv[]) {
LOG(info) << "working directory: " << prefix.generic_string(); LOG(info) << "working directory: " << prefix.generic_string();
LOG(info) << "server: " << server << ",port: " << *port; LOG(info) << "server: " << server << ",port: " << *port;
LOG(info) << "document root: " << state->docRoot(); LOG(info) << "document root: " << state->docRoot();
LOG(info) << "notebook root: " << state->notebookRoot();
// Capture SIGINT and SIGTERM to perform a clean shutdown // Capture SIGINT and SIGTERM to perform a clean shutdown
#ifndef WIN32 #ifndef WIN32
@ -172,9 +161,6 @@ void initSettings() {
if (ptree.find("fileRoot") == ptree.not_found()) { if (ptree.find("fileRoot") == ptree.not_found()) {
ptree.put("fileRoot", "."); ptree.put("fileRoot", ".");
} }
if (ptree.find("notebookRoot") == ptree.not_found()) {
ptree.put("notebookRoot", ".");
}
if (ptree.find("threads") == ptree.not_found()) { if (ptree.find("threads") == ptree.not_found()) {
ptree.put("threads", std::thread::hardware_concurrency()); ptree.put("threads", std::thread::hardware_concurrency());
} }