Skip to content

src/api/transport/impl/http/http_session.cpp

Namespaces

Name
sgns
sgns::api

Source code

#include "api/transport/impl/http/http_session.hpp"

#include <boost/config.hpp>

#include "outcome/outcome.hpp"

namespace sgns::api {

  HttpSession::HttpSession(Context &context, Configuration config)
      : strand_(boost::asio::make_strand(context)),
        config_{config},
        stream_(boost::asio::ip::tcp::socket(strand_)) {}

  void HttpSession::start() {
    asyncRead();
  }

  void HttpSession::stop() {
    boost::system::error_code ec;
    stream_.socket().shutdown(Socket::shutdown_both, ec);
    boost::ignore_unused(ec);
  }

  auto HttpSession::makeBadResponse(std::string_view message,
                                    unsigned version,
                                    bool keep_alive) {
    Response<StringBody> res{boost::beast::http::status::bad_request, version};
    res.set(boost::beast::http::field::server, kServerName);
    res.set(boost::beast::http::field::content_type, "text/html");
    res.keep_alive(keep_alive);
    res.body() = message;
    res.prepare_payload();
    return res;
  }

  template <class Body>
  void HttpSession::handleRequest(boost::beast::http::request<Body> &&req) {
    // allow only POST method
    if (req.method() != boost::beast::http::verb::post) {
      return asyncWrite(makeBadResponse(
          "Unsupported HTTP-method", req.version(), req.keep_alive()));
    }

    processRequest(req.body(), shared_from_this());
  }

  void HttpSession::asyncRead() {
    parser_ = std::make_unique<Parser>();
    parser_->body_limit(config_.max_request_size);
    stream_.expires_after(config_.operation_timeout);

    boost::beast::http::async_read(
        stream_,
        buffer_,
        parser_->get(),
        [self = shared_from_this()](auto ec, auto count) {
          auto *s = dynamic_cast<HttpSession *>(self.get());
          BOOST_ASSERT_MSG(s != nullptr, "cannot cast to HttpSession");
          s->onRead(ec, count);
        });
  }

  template <class Message>
  void HttpSession::asyncWrite(Message &&message) {
    // we need to put message into a shared ptr for async operations
    using message_type = decltype(message);
    auto m = std::make_shared<std::decay_t<message_type>>(
        std::forward<message_type>(message));

    // write response
    boost::beast::http::async_write(
        stream_, *m, [self = shared_from_this(), m](auto ec, auto size) {
          self->onWrite(ec, size, m->need_eof());
        });
  }

  void HttpSession::respond(std::string_view response) {
    StringBody::value_type body;
    body.assign(response);

    const auto size = body.size();

    // send response
    Response<StringBody> res(std::piecewise_construct,
                             std::make_tuple(std::move(body)));
    res.set(HttpField::server, kServerName);
    res.set(HttpField::content_type, "text/html");
    res.content_length(size);
    res.keep_alive(true);

    return asyncWrite(std::move(res));
  }

  void HttpSession::onRead(boost::system::error_code ec, std::size_t) {
    if (ec) {
      if (HttpError::end_of_stream != ec) {
        reportError(ec, "unknown error occurred");
      }

      stop();
    }

    handleRequest(parser_->release());
  }

  void HttpSession::onWrite(boost::system::error_code ec,
                            std::size_t,
                            bool should_stop) {
    if (ec) {
      reportError(ec, "failed to write message");
      return stop();
    }

    if (should_stop) {
      return stop();
    }

    // read next request
    asyncRead();
  }

  void HttpSession::reportError(boost::system::error_code ec,
                                std::string_view message) {
    logger_->error("error occured:{}, code: {}", message, ec.message());
  }
}  // namespace sgns::api

Updated on 2026-03-04 at 13:10:44 -0800