Coyote21
Coyote21

Reputation: 462

Asio and HTTP keep-alive

I'm trying to implement an web server using asio, but in spite of setting the acceptor as keep-alive, when I receive a connection, after I send the response the connection socket is always closed, even if I re-read from the socket (in order to implement keep-alive from http).

I've tried to insert printfs in the code, and for some reason, after I write the response, when the client makes a new connection, it calls the acceptor on my socket, shouldn't it just use the old socket and re-read from it, instead of calling acceptor?

Or am I doing the keep-alive feature in the wrong way...

The relevant portion of the code is:

#include "connection.hpp"
#include <vector>
#include <boost/bind.hpp>
#include "connection_manager.hpp"
#include "request_handler.hpp"
#include <iostream>

namespace http {
namespace server {

connection::connection(asio::io_service& io_service,
    connection_manager& manager, request_handler& handler)
  : socket_(io_service),
    connection_manager_(manager),
    request_handler_(handler),
    request_parser_(new http_visitor()),
    keep_alive_(true)
{
}

asio::ip::tcp::socket& connection::socket()
{
  return socket_;
}

void connection::start()
{
  socket_.async_read_some(asio::buffer(buffer_),
      boost::bind(&connection::handle_read, shared_from_this(),
        asio::placeholders::error,
        asio::placeholders::bytes_transferred));
  std::cout << "In connection start" << std::endl;
}

void connection::stop()
{
  socket_.close();
}

void connection::handle_read(const asio::error_code& e,
    std::size_t bytes_transferred)
{
  if (!e)
  {
    boost::tribool result;
    boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
        request_, buffer_.data(), buffer_.data() + bytes_transferred);

    if (result)
    {
      request_handler_.handle_request(request_, reply_);

      std::vector<header>::iterator hdr_it;

      for(hdr_it = request_.headers.begin(); hdr_it != request_.headers.end();     hdr_it++)
      {
            if( (hdr_it->name.compare("Connection" ) == 0) && 
                (hdr_it->value.compare("close" ) == 0 ) )
        { keep_alive_ = false; }   
      }

      asio::async_write(socket_, reply_.to_buffers(),
                    boost::bind(&connection::handle_write, shared_from_this(),
                    asio::placeholders::error));
    }
    else if (!result)
    {
      reply_ = reply::stock_reply(reply::bad_request);
      asio::async_write(socket_, reply_.to_buffers(),
          boost::bind(&connection::handle_write, shared_from_this(),
            asio::placeholders::error));
    }
    else
    {
      socket_.async_read_some(asio::buffer(buffer_),
          boost::bind(&connection::handle_read, shared_from_this(),
            asio::placeholders::error,
            asio::placeholders::bytes_transferred));
    }
  }
  else if (e != asio::error::operation_aborted)
  {
    connection_manager_.stop(shared_from_this());
  }
}

void connection::handle_write(const asio::error_code& e)
{
  if (!e)
  {

    std::cout << keep_alive_ << std::endl;
    if(keep_alive_)
    {
      buffer_.assign(0);
      request_.clear();
      keep_alive_ = false;
      start();
    }
    else
    {
      socket_.close();
    }  
  }

  if (e != asio::error::operation_aborted)
  {
    connection_manager_.stop(shared_from_this());
  }
}
}
}

and

#include "server.hpp"
#include <boost/bind.hpp>
#include <signal.h>
#include <iostream>

namespace http {
namespace server {

server::server(const std::string& address, const std::string& port,
    const std::string& doc_root)
  : io_service_(),
    signals_(io_service_),
    acceptor_(io_service_),
    connection_manager_(),
    new_connection_(new connection(io_service_, connection_manager_, request_handler_)),
    request_handler_(doc_root)
{
  signals_.add(SIGINT);
  signals_.add(SIGTERM);
  signals_.async_wait(boost::bind(&server::handle_stop, this));

  // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
  asio::ip::tcp::resolver resolver(io_service_);
  asio::ip::tcp::resolver::query query(address, port);
  asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
  acceptor_.open(endpoint.protocol());
  acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true));
  acceptor_.set_option(asio::ip::tcp::acceptor::keep_alive(true));
  acceptor_.bind(endpoint);
  acceptor_.listen();
  acceptor_.async_accept(new_connection_->socket(),
      boost::bind(&server::handle_accept, this,
        asio::placeholders::error));
}

void server::run()
{
    io_service_.run();
}

void server::stop()
{
  io_service_.post(boost::bind(&server::handle_stop, this));
}

void server::handle_accept(const asio::error_code& e)
{
  if (!e)
  {
   std::cout << "In accept" << std::endl;
    connection_manager_.start(new_connection_);
    new_connection_.reset(new connection(io_service_, connection_manager_,     request_handler_));
    acceptor_.async_accept(new_connection_->socket(),
        boost::bind(&server::handle_accept, this, asio::placeholders::error));
  }
}

void server::handle_stop()
{
  acceptor_.close();
  connection_manager_.stop_all();
}

} // namespace server
} // namespace http

In this code the prints are "In connection start", "In accept" and "In connection start", shouldn't it be just the first "In connection start", when there is another connection from the same client to the server...

Upvotes: 3

Views: 3896

Answers (1)

Nick
Nick

Reputation: 25799

Without full visibility of the code I'm guessing that new_connection_ is a shared_ptr and that you're not keeping a reference to it anywhere. This will then cause the smart pointer to delete the connection and close the socket.

Stick a breakpoint in your connections destructor and check whether this is the case.

Without seeing more code it's hard to diagnose the issue.

Upvotes: 2

Related Questions