2023-07-21 16:17:01 +08:00
|
|
|
|
#include "HttpSession.h"
|
2024-01-24 23:19:53 +08:00
|
|
|
|
#include "Application.h"
|
2023-07-21 16:17:01 +08:00
|
|
|
|
#include <boost/config.hpp>
|
2024-05-06 16:54:34 +08:00
|
|
|
|
#include <boost/stacktrace.hpp>
|
2023-07-21 16:17:01 +08:00
|
|
|
|
#include <boost/url/parse_path.hpp>
|
|
|
|
|
#include <boost/url/url_view.hpp>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
2024-01-24 23:19:53 +08:00
|
|
|
|
HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket) : m_stream(std::move(socket)) {
|
2023-07-21 16:17:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2023-12-31 01:13:26 +08:00
|
|
|
|
using namespace boost::beast;
|
2023-07-21 16:17:01 +08:00
|
|
|
|
// This means they closed the connection
|
2023-12-31 01:13:26 +08:00
|
|
|
|
if (ec == http::error::end_of_stream) {
|
2023-07-21 16:17:01 +08:00
|
|
|
|
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();
|
2023-12-31 01:13:26 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2024-01-24 23:19:53 +08:00
|
|
|
|
|
|
|
|
|
auto application = Amass::Singleton<Application>::instance();
|
|
|
|
|
|
2023-12-31 01:13:26 +08:00
|
|
|
|
boost::urls::matches matches;
|
2024-01-24 23:19:53 +08:00
|
|
|
|
auto handler = application->find(*path, matches);
|
2023-07-21 16:17:01 +08:00
|
|
|
|
if (handler) {
|
2024-05-06 16:54:34 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2023-07-21 16:17:01 +08:00
|
|
|
|
} else {
|
2023-12-31 01:13:26 +08:00
|
|
|
|
std::ostringstream oss;
|
|
|
|
|
oss << "The resource '" << request.target() << "' was not found.";
|
|
|
|
|
auto message = oss.str();
|
|
|
|
|
errorReply(request, http::status::not_found, message);
|
2023-07-21 16:17:01 +08:00
|
|
|
|
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();
|
|
|
|
|
}
|