add file surport.
This commit is contained in:
parent
fd432b29f6
commit
d0773d8b98
|
@ -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<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 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ 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) {
|
||||
void reply(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(
|
||||
|
|
|
@ -2,20 +2,7 @@
|
|||
#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,
|
||||
|
@ -31,4 +18,15 @@ serverError(const boost::beast::http::request<boost::beast::http::string_body> &
|
|||
res.prepare_payload();
|
||||
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
|
||||
|
|
|
@ -16,9 +16,6 @@ using StringRequest = boost::beast::http::request<boost::beast::http::string_bod
|
|||
|
||||
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);
|
||||
|
||||
|
@ -31,14 +28,13 @@ static void onGetBlogImage(SharedStatePtr state, StringRequest &&request, 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);
|
||||
|
||||
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>
|
||||
boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::request<RequestBody> &request,
|
||||
typename ResponseBody::value_type body,
|
||||
|
|
|
@ -14,94 +14,6 @@
|
|||
|
||||
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;
|
||||
|
|
|
@ -5,47 +5,66 @@
|
|||
#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_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) {
|
||||
// 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("/{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<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*}",
|
||||
[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<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() = "{}";
|
||||
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<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();
|
||||
corp->notify(request);
|
||||
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,
|
||||
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<std::mutex> lock(mutex_);
|
||||
sessions_.insert(session);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#ifndef SHAREDSTATE_H
|
||||
#define SHAREDSTATE_H
|
||||
|
||||
#include "TemplateMatchs.h"
|
||||
#include "UrlRouter.h"
|
||||
#include "router.hpp"
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/smart_ptr.hpp>
|
||||
|
@ -25,10 +24,10 @@ class SharedState:public std::enable_shared_from_this<SharedState> {
|
|||
|
||||
public:
|
||||
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);
|
||||
|
||||
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 <UrlRouter<Handler>> m_router;
|
||||
std::shared_ptr<boost::urls::router<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>;
|
||||
|
|
|
@ -88,15 +88,6 @@ int main(int argc, char const *argv[]) {
|
|||
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);
|
||||
|
@ -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<WeChatContext>::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) << "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());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user