Implementación de Servidor HTTP con Beast y Boost.Asio

Este artículo detalla la implementación de un servidor HTTP utilizando las bibliotecas Beast y Boost.Asio de C++. Se enfoca en la gestión de conexiones entrantes y el inicio del ciclo de vida de las mismas.

1. Configuración del Aceptador y el Contexto de E/S

Primero, definimos la estructura básica de nuestra clase CServer. Esta clase se encargará de aceptar las conexiones entrantes. Se incluyen los miembros necesarios para el aceptaodr, el contexto de E/S de Boost.Asio y el socket para las nuevas conexiones.


#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <iostream>

namespace net = boost::asio;
using tcp = net::ip::tcp;
namespace beast = boost::beast;

// Forward declaration
class HttpConnection;

class CServer : public std::enable_shared_from_this<CServer>
{
public:
    CServer(net::io_context& ioc, unsigned short port);
    void StartAccepting(); // Renamed for clarity
private:
    tcp::acceptor m_acceptor;
    net::io_context& m_ioc;
    tcp::socket m_socket;
};

2. Inicio del Ciclo de Aceptación de Conexiones

La función StartAccepting inicia el proceso asíncrono de aceptación de nuevas conexiones. Utiliza async_accept para esperar una conexión entrante. Cuando una conexión es establecida o ocurre un error, se ejecuta una función de callback (un lambda en este caso).

Dentro del callback, se manejan los posibles errores. Si la conexión es exitosa, se crea una nueva instancia de HttpConnection (que se encargará del manejo de la petición HTTP) y se le transfiere la propiedad del socket. El socket original m_socket se reutiliza para aceptar la siguiente conexión. Es crucial para la gestión de la vida útil del objeto CServer usar shared_from_this() dentro de los callbacks asíncronos para asegurar que la instancia del servidor no sea destruida prematuramente.


// Constructor implementation
CServer::CServer(net::io_context& ioc, unsigned short port)
    : m_ioc(ioc),
      m_acceptor(ioc, tcp::endpoint(tcp::v4(), port)),
      m_socket(ioc)
{
    std::cout << "Server listening on port " << port << std::endl;
}

// Function to start accepting connections
void CServer::StartAccepting()
{
    auto self = shared_from_this(); // Ensure 'this' object is alive

    m_acceptor.async_accept(m_socket,
        [self](beast::error_code ec, tcp::endpoint endpoint) {
            if (!ec)
            {
                // Connection successful, transfer socket ownership to HttpConnection
                // and start processing the request.
                std::make_shared<HttpConnection>(std::move(self->m_socket), self->m_ioc)->StartHandling();
            }
            else
            {
                std::cerr << "Accept error: " << ec.message() << std::endl;
            }

            // Continue accepting new connections regardless of success or failure
            self->StartAccepting();
        });
}

// Example placeholder for HttpConnection class
class HttpConnection : public std::enable_shared_from_this<HttpConnection>
{
public:
    HttpConnection(tcp::socket socket, net::io_context& ioc)
        : m_socket(std::move(socket)), m_ioc(ioc) {}

    void StartHandling() {
        // Logic to read HTTP request, process it, and send response
        std::cout << "New connection accepted." << std::endl;
        // Example: Read request
        auto shared_self = shared_from_this();
        beast::flat_buffer buffer;
        m_socket.async_read_some(net::buffer(buffer.data(), buffer.size()),
            [shared_self, &buffer](beast::error_code ec, std::size_t bytes_transferred) {
                if (!ec) {
                    std::cout << "Read " << bytes_transferred << " bytes." << std::endl;
                    // Process buffer data...
                    // For now, just close the connection
                    beast::error_code ignored_ec;
                    shared_self->m_socket.shutdown(tcp::socket::shutdown_both, ignored_ec);
                } else {
                    std::cerr << "Read error: " << ec.message() << std::endl;
                }
            });
    }

private:
    tcp::socket m_socket;
    net::io_context& m_ioc;
};

3. Transferencia de Socket y Creación de Conexión HTTP

La línea clave std::make_shared<HttpConnection>(std::move(self->m_socket), self->m_ioc)->StartHandling(); realiza varias acciones importantes:

  • Crea una nueva instancia de HttpConnection utilizando std::make_shared, gestionándola con un puntero inteligente.
  • Transfiere la propiedad del m_socket (que ahora contiene la conexión establecida) a la nueva instancia de HttpConnection usando std::move. Esto es necesario porque async_accept completa la conexión en el socket proporcionado, y este socket debe ser manejado por el objeto de conexión.
  • Llama al método StartHandling() en la nueva instancia de HttpConnection para iniciar el procesamiento de la solicitud HTTP.

Después de iniciar el manejo de la conexión actual, la llamada recursiva self->StartAccepting(); asegura que el servidor esté listo para aceptar la próxima conexión inmediatamente.

Etiquetas: boost.asio boost.beast servidor http C++ redes

Publicado el 7-29 20:35