vitakot
vitakot

Reputation: 3844

Simple HTTPS server and Keep-Alive connections

I am trying to write a simple HTTPS server by modifying the Boost ASIO example. It works, but the problem is that it establishes a secured connection for each request coming from a client, which terribly slows down the communication.

I know the HTTPS server should use Keep-Alive connections. But I do not know how to extend the mentioned example to do that. The situation is very simple in my case as I always have 1:1 connection, so there is no need for session management, there is always only a one valid session.

I use two kinds of HTTP responses:

std::string HTTPS::createResponse(status_type status)
{
    std::stringstream response;
    response<<statusCodeToString(status);
    response<<"Content-Length: "<<statusToContentString(status).size()<<"\r\n";
    response<<"Content-Type: text/html\r\n\r\n";
    response<<statusToContentString(status);

    return response.str();
}

std::string HTTPS::createResponse(status_type status, const std::string &data)
{
    std::stringstream response;
    response<<"HTTP/1.1 200 OK\r\n";
    response<<"Content-Length: "<<data.size()<<"\r\n";
    response<<"Content-Type: text/plain\r\n\r\n";
    response<<data;

    return response.str();
}

std::string HLSProxyServerSSL::statusCodeToString(status_type status)
{
    static const char ok_s[] =
        "HTTP/1.1 200 OK\r\n";
    static const char created_s[] =
        "HTTP/1.1 201 Created\r\n";
    static const char accepted_s[] =
        "HTTP/1.1 202 Accepted\r\n";
    static const char no_content_s[] =
        "HTTP/1.1 204 No Content\r\n";
    static const char multiple_choices_s[] =
        "HTTP/1.1 300 Multiple Choices\r\n";
    static const char moved_permanently_s[] =
        "HTTP/1.1 301 Moved Permanently\r\n";
    static const char moved_temporarily_s[] =
        "HTTP/1.1 302 Moved Temporarily\r\n";
    static const char not_modified_s[] =
        "HTTP/1.1 304 Not Modified\r\n";
    static const char bad_request_s[] =
        "HTTP/1.1 400 Bad Request\r\n";
    static const char unauthorized_s[] =
        "HTTP/1.1 401 Unauthorized\r\n";
    static const char forbidden_s[] =
        "HTTP/1.1 403 Forbidden\r\n";
    static const char not_found_s[] =
        "HTTP/1.1 404 Not Found\r\n";
    static const char not_supported_s[] =
        "HTTP/1.1 405 Method Not Supported\r\n";
    static const char not_acceptable_s[] =
        "HTTP/1.1 406 Method Not Acceptable\r\n";
    static const char internal_server_error_s[] =
        "HTTP/1.1 500 Internal Server Error\r\n";
    static const char not_implemented_s[] =
        "HTTP/1.1 501 Not Implemented\r\n";
    static const char bad_gateway_s[] =
        "HTTP/1.1 502 Bad Gateway\r\n";
    static const char service_unavailable_s[] =
        "HTTP/1.1 503 Service Unavailable\r\n";

    switch (status)
    {
        case ok:
            return ok_s;
        case created:
            return created_s;
        case accepted:
            return accepted_s;
        case no_content:
            return no_content_s;
        case multiple_choices:
            return multiple_choices_s;
        case moved_permanently:
            return moved_permanently_s;
        case moved_temporarily:
            return moved_temporarily_s;
        case not_modified:
            return not_modified_s;
        case bad_request:
            return bad_request_s;
        case unauthorized:
            return unauthorized_s;
        case forbidden:
            return forbidden_s;
        case not_found:
            return not_found_s;
        case not_supported:
            return not_supported_s;
        case not_acceptable:
            return not_acceptable_s;
        case internal_server_error:
            return internal_server_error_s;
        case not_implemented:
            return not_implemented_s;
        case bad_gateway:
            return bad_gateway_s;
        case service_unavailable:
            return service_unavailable_s;
        default:
            return internal_server_error_s;
    }
}

std::string HLSProxyServerSSL::statusToContentString(status_type status)
{
    static const char ok_s[] = "";
    static const char created_s[] =
        "<html>"
        "<head><title>Created</title></head>"
        "<body><h1>201 Created</h1></body>"
        "</html>";
    static const char accepted_s[] =
        "<html>"
        "<head><title>Accepted</title></head>"
        "<body><h1>202 Accepted</h1></body>"
        "</html>";
    static const char no_content_s[] =
        "<html>"
        "<head><title>No Content</title></head>"
        "<body><h1>204 Content</h1></body>"
        "</html>";
    static const char multiple_choices_s[] =
        "<html>"
        "<head><title>Multiple Choices</title></head>"
        "<body><h1>300 Multiple Choices</h1></body>"
        "</html>";
    static const char moved_permanently_s[] =
        "<html>"
        "<head><title>Moved Permanently</title></head>"
        "<body><h1>301 Moved Permanently</h1></body>"
        "</html>";
    static const char moved_temporarily_s[] =
        "<html>"
        "<head><title>Moved Temporarily</title></head>"
        "<body><h1>302 Moved Temporarily</h1></body>"
        "</html>";
    static const char not_modified_s[] =
        "<html>"
        "<head><title>Not Modified</title></head>"
        "<body><h1>304 Not Modified</h1></body>"
        "</html>";
    static const char bad_request_s[] =
        "<html>"
        "<head><title>Bad Request</title></head>"
        "<body><h1>400 Bad Request</h1></body>"
        "</html>";
    static const char unauthorized_s[] =
        "<html>"
        "<head><title>Unauthorized</title></head>"
        "<body><h1>401 Unauthorized</h1></body>"
        "</html>";
    static const char forbidden_s[] =
        "<html>"
        "<head><title>Forbidden</title></head>"
        "<body><h1>403 Forbidden</h1></body>"
        "</html>";
    static const char not_found_s[] =
        "<html>"
        "<head><title>Not Found</title></head>"
        "<body><h1>404 Not Found</h1></body>"
        "</html>";
    static const char not_supported_s[] =
        "<html>"
        "<head><title>Method Not Supported</title></head>"
        "<body><h1>Method Not Supported</h1></body>"
        "</html>";
    static const char not_acceptable_s[] =
        "<html>"
        "<head><title>Request Not Acceptable</title></head>"
        "<body><h1>Request Not Acceptable</h1></body>"
        "</html>";
    static const char internal_server_error_s[] =
        "<html>"
        "<head><title>Internal Server Error</title></head>"
        "<body><h1>500 Internal Server Error</h1></body>"
        "</html>";
    static const char not_implemented_s[] =
        "<html>"
        "<head><title>Not Implemented</title></head>"
        "<body><h1>501 Not Implemented</h1></body>"
        "</html>";
    static const char bad_gateway_s[] =
        "<html>"
        "<head><title>Bad Gateway</title></head>"
        "<body><h1>502 Bad Gateway</h1></body>"
        "</html>";
    static const char service_unavailable_s[] =
        "<html>"
        "<head><title>Service Unavailable</title></head>"
        "<body><h1>503 Service Unavailable</h1></body>"
        "</html>";

    switch (status)
    {
        case ok:
            return ok_s;
        case created:
            return created_s;
        case accepted:
            return accepted_s;
        case no_content:
            return no_content_s;
        case multiple_choices:
            return multiple_choices_s;
        case moved_permanently:
            return moved_permanently_s;
        case moved_temporarily:
            return moved_temporarily_s;
        case not_modified:
            return not_modified_s;
        case bad_request:
            return bad_request_s;
        case unauthorized:
            return unauthorized_s;
        case forbidden:
            return forbidden_s;
        case not_found:
            return not_found_s;
        case not_supported:
            return not_supported_s;
        case not_acceptable:
            return not_acceptable_s;
        case internal_server_error:
            return internal_server_error_s;
        case not_implemented:
            return not_implemented_s;
        case bad_gateway:
            return bad_gateway_s;
        case service_unavailable:
            return service_unavailable_s;
        default:
            return internal_server_error_s;
        }
}

I believe the responses are OK, but please check it – It is no my area of expertise.

The communication flow is folowing now:

1, Server starts to accept a new connection – creates a session, binds the asynchronous acceptor which waits for incoming request for handshake.

2, if an incoming handshake occurs, then the server starts handshake on session created in the point 1.

3, once the handshake finishes, it process the incoming message

The session handshake handle is below. It reads one HTTP command and processes it. I intentionally do not care of the rest of the header because from the server side only the GET commands are valid – the server works like a simple re-sender.

void SSLSession::handleHandshake(const boost::system::error_code& error)
{
    __log_print(LOG_INFO, "SSLSession", "handleHandshake");

    if (!error)
    {
        boost::asio::async_read_until(_socket, _data, "\r\n", boost::bind(&SSLSession::handleHttpRequestLine, this, _1));
    }
    else
    {
        delete this;
    }
}

void SSLSession::handleHttpRequestLine(boost::system::error_code ec)
{
    std::string method, uri, version;

    if (!ec)
    {
        char sp1, sp2, cr, lf;
        std::istream is(&_data);
        is.unsetf(std::ios_base::skipws);
        is >> method >> sp1 >> uri >> sp2 >> version >> cr >> lf;
    }

    handleHttpRequest(method, uri);
}

So my question is, how can I modify method:

void SSLSession::handleHandshake(const boost::system::error_code& error)

to start listen not only to one command, but to continuously incoming GET commands which are valid for this session? I have tried adding another

boost::asio::async_read_until(_socket, _data, "\r\n", boost::bind(&SSLSession::handleHttpRequestLine, this, _1));

call to the end of handleHttpRequest(method, uri); method, but I got a deadlock... I believe it must be trivial, but I am lost in all these async acceptors and handlers...

Upvotes: 3

Views: 1848

Answers (1)

Tanner Sansbury
Tanner Sansbury

Reputation: 51871

To continuously listen for incoming GET commands, the easiest solution is to initiate an async_read_until operation from within the chain that handles the async_read_until. In the example code, having it in handleHttpRequest sounds like a viable option.

If you are getting lost in the asynchronous call chains, then consider illustrating them:

void SSLSession::handleHandshake(...)
{
  if (!error)
  {
    boost::asio::async_read_until(...);  ----.
  }                                          |
}              .-----------------------------'
               |  .--------------------------.                     
               v  v                          |
void SSLSession::handleHttpRequestLine(...)  |
{                                            |
  handleHttpRequest(...); --.                |
}                           |                |
               .------------'                |
               v                             |
void SSLSession::handleHttpRequest(...)      |
{                                            |
  boost::asio::async_read_until(...);  ------'
}

For Boost.Asio 1.47+, another great option for debugging the chains is enabling handle tracking. Simply define BOOST_ASIO_ENABLE_HANDLER_TRACKING and Boost.Asio will write debug output, including timestamps, to the standard error stream. This answer goes into more detail on debugging handlers.

If you are getting a deadlock, then it is likely in the application code. For most Boost.Asio types, it is safe to have multiple asynchronous operations pending on an object; it is just specified that concurrent calls on the object are unsafe. Nevertheless, it normally does not present a problem on some types, such as ip::tcp::socket. However, ssl::stream accentuates that:

The application must also ensure that all asynchronous operations are performed within the same implicit or explicit strand.

Undefined behavior can occur, which may result in a deadlock, as presented in this question. If multiple threads are processing the io_service event loop, then consider running the asynchronous operations in a boost::asio::strand.

Upvotes: 3

Related Questions