#include "HttpSession.h" #include "BoostLog.h" #include #include #include #include #include #include #include #include HttpSession::HttpSession(boost::asio::ip::tcp::socket &&socket, const std::shared_ptr> &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 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::max()); m_parser->header_limit(std::numeric_limits::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(); }