From d0773d8b9861d1e0b1b78640e3b4b6ce5fc4e59b Mon Sep 17 00:00:00 2001 From: amass <168062547@qq.com> Date: Sun, 31 Dec 2023 01:13:26 +0800 Subject: [PATCH] add file surport. --- Server/HttpSession.cpp | 21 ++++++--- Server/HttpSession.h | 2 +- Server/ServiceLogic.cpp | 26 ++++++----- Server/ServiceLogic.h | 10 ++--- Server/ServiceLogic.inl | 88 ------------------------------------- Server/SharedState.cpp | 96 +++++++++++++++++++++++------------------ Server/SharedState.h | 17 +++----- Server/main.cpp | 14 ------ 8 files changed, 89 insertions(+), 185 deletions(-) diff --git a/Server/HttpSession.cpp b/Server/HttpSession.cpp index fe52c2a..364be5f 100644 --- a/Server/HttpSession.cpp +++ b/Server/HttpSession.cpp @@ -49,8 +49,9 @@ void HttpSession::doRead() { } void HttpSession::onRead(boost::beast::error_code ec, std::size_t) { + using namespace boost::beast; // 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); return; } @@ -62,22 +63,28 @@ void HttpSession::onRead(boost::beast::error_code ec, std::size_t) { auto &request = m_parser->get(); // 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 // of both the socket and the HTTP request. auto session = std::make_shared(m_stream.release_socket(), m_state); session->run(m_parser->release()); return; } - boost::urls::url_view view(request.target()); - auto path = boost::urls::parse_path(view.path()); - TemplateMatches matches; + auto path = boost::urls::parse_path(request.target()); + if (!path) { + LOG(error) << request.target() << "failed, error: " << path.error().message(); + errorReply(request, http::status::bad_request, "Illegal request-target"); + return; + } + boost::urls::matches 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); + std::ostringstream oss; + oss << "The resource '" << request.target() << "' was not found."; + auto message = oss.str(); + errorReply(request, http::status::not_found, message); LOG(error) << message; } } diff --git a/Server/HttpSession.h b/Server/HttpSession.h index 5804700..56b6873 100644 --- a/Server/HttpSession.h +++ b/Server/HttpSession.h @@ -20,7 +20,7 @@ public: using Request = boost::beast::http::request; HttpSession(boost::asio::ip::tcp::socket &&socket, std::shared_ptr const &state); template - void reply(const Response &&response) { + void reply(Response &&response) { using ResponseType = typename std::decay_t; auto sp = std::make_shared(std::forward(response)); boost::beast::http::async_write( diff --git a/Server/ServiceLogic.cpp b/Server/ServiceLogic.cpp index ad3f5e5..67f83e5 100644 --- a/Server/ServiceLogic.cpp +++ b/Server/ServiceLogic.cpp @@ -2,20 +2,7 @@ #include namespace ServiceLogic { - -boost::beast::http::response -notFound(const boost::beast::http::request &request) { - using namespace boost::beast; - http::response res{http::status::not_found, request.version()}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.keep_alive(request.keep_alive()); - std::ostringstream oss; - oss << "The resource '" << request.target() << "' was not found."; - res.body() = oss.str(); - res.prepare_payload(); - return res; -} +using namespace boost::beast; boost::beast::http::response serverError(const boost::beast::http::request &request, @@ -31,4 +18,15 @@ serverError(const boost::beast::http::request & res.prepare_payload(); return res; } + +http::response badRequest(const http::request &request, std::string_view why) { + http::response 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 diff --git a/Server/ServiceLogic.h b/Server/ServiceLogic.h index b7a9935..9da759b 100644 --- a/Server/ServiceLogic.h +++ b/Server/ServiceLogic.h @@ -16,9 +16,6 @@ using StringRequest = boost::beast::http::request -static void onDownload(SharedStatePtr /*state*/, StringRequest &&request, Send &&send); - template static void onGetBlogList(SharedStatePtr state, StringRequest &&request, Send &&send); @@ -31,14 +28,13 @@ static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send & template static void onWechat(SharedStatePtr state, StringRequest &&request, Send &&send); -// Returns a not found response -boost::beast::http::response -notFound(const boost::beast::http::request &request); - // Returns a server error response boost::beast::http::response serverError(const boost::beast::http::request &request, std::string_view errorMessage); +boost::beast::http::response +badRequest(const boost::beast::http::request &request, std::string_view why); + template boost::beast::http::response make_200(const boost::beast::http::request &request, typename ResponseBody::value_type body, diff --git a/Server/ServiceLogic.inl b/Server/ServiceLogic.inl index eedbd1b..71f5371 100644 --- a/Server/ServiceLogic.inl +++ b/Server/ServiceLogic.inl @@ -14,94 +14,6 @@ namespace ServiceLogic { -template -static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, Send &&send) { - using namespace boost::beast; - boost::urls::url url(request.target()); - if (url.path().size() < 12) return; - auto file = url.path().substr(12); - - auto root = state->notebookRoot(); - auto path = ResponseUtility::pathCat(root, file); - - boost::nowide::ifstream ifs(path, boost::nowide::ifstream::binary); - std::vector file_string(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); - boost::beast::error_code ec; - http::response> s; - s.set(http::field::server, BOOST_BEAST_VERSION_STRING); - s.set(http::field::content_type, ResponseUtility::mimeType(path)); - s.keep_alive(request.keep_alive()); - s.body() = std::move(file_string); - s.prepare_payload(); - return send(std::move(s)); -} - -template -static void onGetBlogContent(SharedStatePtr state, StringRequest &&request, Send &&send, const std::string &prefix) { - using namespace boost::beast; - boost::urls::url url(request.target()); - auto file = url.path().substr(prefix.length()); - - auto root = state->notebookRoot(); - auto path = ResponseUtility::pathCat(root, file); - LOG(info) << "get file: " << path; - - if (!std::filesystem::exists(path)) { - return send(notFound(request)); - } - boost::nowide::ifstream ifs(path); - std::string file_string(std::istreambuf_iterator{ifs}, std::istreambuf_iterator{}); - http::response s; - s.set(http::field::server, BOOST_BEAST_VERSION_STRING); - s.set(http::field::content_type, "text/markdown;charset=UTF-8"); - s.keep_alive(request.keep_alive()); - s.body() = file_string; - s.prepare_payload(); - return send(std::move(s)); -} - -template -void onDownload(SharedStatePtr state, StringRequest &&request, Send &&send) { - LOG(info) << "onDownload"; - using namespace boost::beast; - std::string_view file; - if (request.target() == "/download") { - auto requestData = boost::json::parse(request.body()); - file = requestData.as_object()["file"].as_string(); - } else { - file = request.target().substr(9); - } - auto path = ResponseUtility::pathCat(state->fileRoot(), file); - LOG(info) << "get file: " << path; - - // Attempt to open the file - boost::beast::error_code ec; - http::file_body::value_type body; - body.open(path.c_str(), boost::beast::file_mode::scan, ec); - - // Handle the case where the file doesn't exist - if (ec == boost::system::errc::no_such_file_or_directory) { - return send(notFound(request)); - } else if (ec) { // Handle an unknown error - return send(serverError(request, ec.message())); - } - - // Cache the size since we need it after the move - auto const size = body.size(); - - // Respond to GET request - http::response res{std::piecewise_construct, std::make_tuple(std::move(body)), - std::make_tuple(http::status::ok, request.version())}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, ResponseUtility::mimeType(path)); - res.set(http::field::content_description, file); - res.set(http::field::accept_charset, "utf-8"); - res.content_length(size); - res.keep_alive(request.keep_alive()); - - return send(std::move(res)); -} - template static void onWechat(SharedStatePtr state, const StringRequest &request, Send &&send) { using namespace boost::beast; diff --git a/Server/SharedState.cpp b/Server/SharedState.cpp index a86a6f8..33ac085 100644 --- a/Server/SharedState.cpp +++ b/Server/SharedState.cpp @@ -5,56 +5,75 @@ #include "WebsocketSession.h" SharedState::SharedState(boost::asio::io_context &ioContext, std::string doc_root) - : m_ioContext(ioContext), m_router{std::make_shared>()}, m_docRoot(std::move(doc_root)) { + : m_ioContext(ioContext), m_router{std::make_shared>()}, + m_docRoot(std::move(doc_root)) { - m_router->insert("/", [](HttpSession &session, const Request &request, const TemplateMatches &matches) { - // Send content message to client and wait to receive next request - session.reply(ServiceLogic::make_200(request, "Main page\n", "text/html")); + m_router->insert("/{path*}",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { + using namespace boost::beast; + 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 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*}", - [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, [&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->insert("/api/v1/tasklist",[this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { + using namespace boost::beast; + http::response s; + s.set(http::field::server, BOOST_BEAST_VERSION_STRING); + s.set(http::field::content_type, "text/markdown;charset=UTF-8"); + s.keep_alive(request.keep_alive()); + s.body() = "{}"; + s.prepare_payload(); + session.reply(std::move(s)); + }); - // 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) { + m_router->insert("/trigger-ci.hook", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { LOG(info) << "webhook: " << request; session.reply(ServiceLogic::make_200(request, "Main page\n", "text/html")); }); - m_router->insert("/notify", [this](HttpSession &session, const Request &request, const TemplateMatches &matches) { - auto corp = Amass::Singleton::instance(); - corp->notify(request); - session.reply( - ServiceLogic::make_200(request, "notify successed.\n", "text/html")); - }); + m_router->insert("/notify", [this](HttpSession &session, const Request &request, const boost::urls::matches &matches) { + auto corp = Amass::Singleton::instance(); + corp->notify(request); + session.reply( + ServiceLogic::make_200(request, "notify successed.\n", "text/html")); + }); } const SharedState::Handler *SharedState::find(boost::urls::segments_encoded_view path, - TemplateMatchStorageBase &matches) const noexcept { + boost::urls::matches_base &matches) const noexcept { return m_router->find(path, matches); } @@ -67,15 +86,6 @@ void SharedState::setFileRoot(const std::string_view &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) { std::lock_guard lock(mutex_); sessions_.insert(session); diff --git a/Server/SharedState.h b/Server/SharedState.h index 1891d72..5819ad5 100644 --- a/Server/SharedState.h +++ b/Server/SharedState.h @@ -1,8 +1,7 @@ #ifndef SHAREDSTATE_H #define SHAREDSTATE_H -#include "TemplateMatchs.h" -#include "UrlRouter.h" +#include "router.hpp" #include #include #include @@ -15,7 +14,7 @@ class HttpSession; class WebSocketSession; // Represents the shared server state -class SharedState:public std::enable_shared_from_this { +class SharedState : public std::enable_shared_from_this { // This mutex synchronizes all access to sessions_ std::mutex mutex_; @@ -25,10 +24,10 @@ class SharedState:public std::enable_shared_from_this { public: using Request = boost::beast::http::request; - using Handler = std::function; - SharedState(boost::asio::io_context &ioContext,std::string doc_root); + using Handler = std::function; + SharedState(boost::asio::io_context &ioContext, std::string doc_root); - const Handler *find(boost::urls::segments_encoded_view path, TemplateMatchStorageBase &matches) const noexcept ; + const Handler *find(boost::urls::segments_encoded_view path, boost::urls::matches_base &matches) const noexcept; inline std::string_view docRoot() const noexcept { return m_docRoot; @@ -40,9 +39,6 @@ public: } 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); @@ -53,11 +49,10 @@ public: private: boost::asio::io_context &m_ioContext; - std::shared_ptr > m_router; + std::shared_ptr> m_router; std::string m_docRoot; std::string m_galleryRoot = "/root/photos"; std::string m_fileRoot; - std::string m_notebookRoot; }; using SharedStatePtr = std::shared_ptr; diff --git a/Server/main.cpp b/Server/main.cpp index f8125f5..f6bdb99 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -88,15 +88,6 @@ int main(int argc, char const *argv[]) { std::exit(104); } - auto notebookRoot = ptree.get("notebookRoot"); - if (notebookRoot.empty()) { - LOG(fatal) << "please set notebookRoot."; - std::exit(105); - } else if (!std::filesystem::exists(notebookRoot)) { - LOG(fatal) << "notebook root: " << notebookRoot << " is not exists..."; - std::exit(106); - } - if (!port) { LOG(fatal) << "port is not a number."; std::exit(255); @@ -114,7 +105,6 @@ int main(int argc, char const *argv[]) { auto state = listener->state(); state->setFileRoot(fileRoot); - state->setNotebookRoot(notebookRoot); auto wechatContext = Amass::Singleton::instance(*ioContext->ioContext()); auto corpContext = Amass::Singleton::instance(*ioContext->ioContext()); @@ -123,7 +113,6 @@ int main(int argc, char const *argv[]) { 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 @@ -172,9 +161,6 @@ void initSettings() { 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()); }