add camera control.
This commit is contained in:
120
Main/HttpServer/HttpSession.cpp
Normal file
120
Main/HttpServer/HttpSession.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "HttpSession.h"
|
||||
#include "Core/Logger.h"
|
||||
#include <boost/beast/http/read.hpp>
|
||||
#include <boost/beast/version.hpp>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/stacktrace.hpp>
|
||||
#include <boost/url/parse_path.hpp>
|
||||
#include <boost/url/url_view.hpp>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
namespace Danki {
|
||||
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket,
|
||||
const std::shared_ptr<boost::urls::router<RequestHandler>> &router)
|
||||
: m_stream(std::move(socket)), m_router(router) {
|
||||
}
|
||||
|
||||
void HttpSession::run() {
|
||||
doRead();
|
||||
}
|
||||
|
||||
boost::beast::tcp_stream::executor_type HttpSession::executor() {
|
||||
return m_stream.get_executor();
|
||||
}
|
||||
|
||||
boost::asio::ip::tcp::socket HttpSession::releaseSocket() {
|
||||
return m_stream.release_socket();
|
||||
}
|
||||
|
||||
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));
|
||||
// clang-format off
|
||||
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);
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void HttpSession::onRead(const boost::beast::error_code &error, std::size_t) {
|
||||
using namespace boost::beast;
|
||||
if (error) {
|
||||
if (error == http::error::end_of_stream) {
|
||||
boost::beast::error_code e;
|
||||
m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_send, e);
|
||||
} else if (error != boost::asio::error::operation_aborted) {
|
||||
LOG(info) << error << " : " << error.message();
|
||||
}
|
||||
return;
|
||||
} else if (m_router.expired()) {
|
||||
LOG(error) << "router is null.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto &request = m_parser->get();
|
||||
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;
|
||||
}
|
||||
auto router = m_router.lock();
|
||||
boost::urls::matches matches;
|
||||
auto handler = router->find(*path, matches);
|
||||
if (handler) {
|
||||
try {
|
||||
(*handler)(*this, request, matches);
|
||||
} catch (const std::exception &e) {
|
||||
boost::stacktrace::stacktrace trace = boost::stacktrace::stacktrace::from_current_exception();
|
||||
LOG(error) << e.what() << ", trace:\n" << trace;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
} // namespace Danki
|
52
Main/HttpServer/HttpSession.h
Normal file
52
Main/HttpServer/HttpSession.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef HTTPSESSION_H
|
||||
#define HTTPSESSION_H
|
||||
|
||||
#include "Router/router.hpp"
|
||||
#include <boost/beast/core/flat_buffer.hpp>
|
||||
#include <boost/beast/core/tcp_stream.hpp>
|
||||
#include <boost/beast/http/parser.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/write.hpp>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Danki {
|
||||
|
||||
/** Represents an established HTTP connection
|
||||
*/
|
||||
class HttpSession : public std::enable_shared_from_this<HttpSession> {
|
||||
void doRead();
|
||||
void onWrite(boost::beast::error_code ec, std::size_t, bool close);
|
||||
|
||||
public:
|
||||
using Request = boost::beast::http::request<boost::beast::http::string_body>;
|
||||
using RequestHandler = std::function<void(HttpSession &, const Request &, const boost::urls::matches &)>;
|
||||
HttpSession(boost::asio::ip::tcp::socket &&socket,
|
||||
const std::shared_ptr<boost::urls::router<RequestHandler>> &router);
|
||||
template <typename 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(
|
||||
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);
|
||||
boost::beast::tcp_stream::executor_type executor();
|
||||
boost::asio::ip::tcp::socket releaseSocket();
|
||||
void run();
|
||||
|
||||
protected:
|
||||
void onRead(const boost::beast::error_code &error, std::size_t);
|
||||
|
||||
private:
|
||||
boost::beast::tcp_stream m_stream;
|
||||
std::weak_ptr<boost::urls::router<RequestHandler>> m_router;
|
||||
boost::beast::flat_buffer m_buffer{std::numeric_limits<std::uint32_t>::max()};
|
||||
std::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> m_parser;
|
||||
};
|
||||
} // namespace Danki
|
||||
|
||||
#endif // HTTPSESSION_H
|
51
Main/HttpServer/ResponseUtility.cpp
Normal file
51
Main/HttpServer/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
Main/HttpServer/ResponseUtility.h
Normal file
19
Main/HttpServer/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
|
100
Main/HttpServer/ServiceLogic.cpp
Normal file
100
Main/HttpServer/ServiceLogic.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
#include "ServiceLogic.h"
|
||||
#include "../Settings.h"
|
||||
#include "Core/Logger.h"
|
||||
#include "Core/Singleton.h"
|
||||
#include "HttpSession.h"
|
||||
#include <boost/beast/http/file_body.hpp>
|
||||
#include <boost/url/url_view.hpp>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
|
||||
namespace ServiceLogic {
|
||||
using namespace boost::beast;
|
||||
|
||||
std::string extractToken(const std::string &cookieHeader, const std::string &tokenName = "access_token") {
|
||||
// 格式示例:"access_token=abc123; Path=/; Expires=Wed, 21 Oct 2023 07:28:00 GMT"
|
||||
size_t startPos = cookieHeader.find(tokenName + "=");
|
||||
if (startPos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
startPos += tokenName.size() + 1; // 跳过 "token_name="
|
||||
size_t endPos = cookieHeader.find(';', startPos);
|
||||
if (endPos == std::string::npos) {
|
||||
endPos = cookieHeader.size();
|
||||
}
|
||||
std::string token = cookieHeader.substr(startPos, endPos - startPos);
|
||||
|
||||
// 移除可能的引号和空格
|
||||
token.erase(std::remove(token.begin(), token.end(), '"'), token.end());
|
||||
token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
|
||||
return token;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void staticFilesDeploy() {
|
||||
using namespace Core;
|
||||
using namespace boost::urls;
|
||||
auto application = Singleton<Danki::Application>::instance();
|
||||
// clang-format off
|
||||
application->insertUrl("/{path*}", [](Danki::HttpSession &session, const Danki::Application::Request &request, const matches &matches) {
|
||||
using namespace boost::beast;
|
||||
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;
|
||||
}
|
||||
auto settings = Singleton<Danki::Settings>::instance();
|
||||
std::string path = ResponseUtility::pathCat(settings->documentRoot(), 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.";
|
||||
LOG(error) << oss.str();
|
||||
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));
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
} // namespace ServiceLogic
|
43
Main/HttpServer/ServiceLogic.h
Normal file
43
Main/HttpServer/ServiceLogic.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifndef SERVICELOGIC_H
|
||||
#define SERVICELOGIC_H
|
||||
|
||||
#include "../Application.h"
|
||||
#include "ResponseUtility.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 {
|
||||
|
||||
// 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,
|
||||
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;
|
||||
}
|
||||
|
||||
void staticFilesDeploy();
|
||||
|
||||
}; // namespace ServiceLogic
|
||||
|
||||
#endif // SERVICELOGIC_H
|
Reference in New Issue
Block a user