c++asio

Asio no response afer sending a basic http request


I'm new to asio and was follwing a tutorial. After sending a pretty basic http get request to example.com there is no response.

#include<json/json.h>
#include<asio/ts/buffer.hpp>
#include<asio/ts/internet.hpp>
#include<iostream>

#define ASIO_STANDALONE

int main(){
    asio::error_code ec;
    asio::io_context context;
    asio::ip::tcp::endpoint endpoint(asio::ip::make_address("93.184.216.34", ec), 80);
    asio::ip::tcp::socket socket(context);
    socket.connect(endpoint, ec);
    
    std::cout << ec.message() << '\n';

    if(socket.is_open())
    {
        std::string sRequest = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
        std::cout << sRequest.data();
        socket.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec);
    
        int bytes = socket.available();
        std::cout << bytes << '\n';

        if(bytes > 0)
        {
            std::vector<char> vBuffer(bytes);
            socket.read_some(asio::buffer(vBuffer.data(), vBuffer.size()), ec);

            for(auto c : vBuffer)
            {
                std::cout << c;
            }
        }
    }

    return 0;
}

What is weird is that from tcpdump it seems that there was a response sent back.

16:53:57.116316 IP arch.44680 > 93.184.216.34.http: Flags [S], seq 999815019, win 32120, options [mss 1460,sackOK,TS val 2462554605 ecr 0,nop,wscale 7], length 0
16:53:57.173058 IP arch.35949 > _gateway.domain: 48878+ PTR? 34.216.184.93.in-addr.arpa. (44)
16:53:57.193671 IP _gateway.domain > arch.35949: 48878 NXDomain 0/1/0 (115)
16:53:57.236277 IP 93.184.216.34.http > arch.44680: Flags [S.], seq 729800927, ack 999815020, win 65535, options [mss 1452,sackOK,TS val 3857096832 ecr 2462554605,nop,wscale 9], length 0
16:53:57.236306 IP arch.44680 > 93.184.216.34.http: Flags [.], ack 1, win 251, options [nop,nop,TS val 2462554725 ecr 3857096832], length 0
16:53:57.236454 IP arch.44680 > 93.184.216.34.http: Flags [P.], seq 1:52, ack 1, win 251, options [nop,nop,TS val 2462554726 ecr 3857096832], length 51: HTTP: GET /index.html HTTP/1.1
16:53:57.236490 IP arch.44680 > 93.184.216.34.http: Flags [F.], seq 52, ack 1, win 251, options [nop,nop,TS val 2462554726 ecr 3857096832], length 0
16:53:57.357833 IP 93.184.216.34.http > arch.44680: Flags [.], ack 52, win 128, options [nop,nop,TS val 3857096953 ecr 2462554726], length 0
16:53:57.358428 IP 93.184.216.34.http > arch.44680: Flags [P.], seq 1:1613, ack 53, win 128, options [nop,nop,TS val 3857096953 ecr 2462554726], length 1612: HTTP: HTTP/1.1 200 OK
16:53:57.358429 IP 93.184.216.34.http > arch.44680: Flags [F.], seq 1613, ack 53, win 128, options [nop,nop,TS val 3857096953 ecr 2462554726], length 0
16:53:57.358458 IP arch.44680 > 93.184.216.34.http: Flags [R], seq 999815072, win 0, length 0
16:53:57.358487 IP arch.44680 > 93.184.216.34.http: Flags [R], seq 999815072, win 0, length 0

Solution

  •     std::vector<char> vBuffer(bytes);
    

    Your vector is empty because bytes is a TOCTOU race. You printed bytes, and it is likely 0. At least, for me it is:

    Success
    GET / HTTP/1.1
    Host: example.com
    Connection: close
    
    0
    

    Instead, accept a dynamic read until "\r\n\r\n":

    std::vector<char> vBuffer;
    read_until(socket, asio::dynamic_buffer(vBuffer), "\r\n\r\n", ec);
    

    Actually, you need to do the same on the write, because write_some may write only part of the message. Also, don't manually specify the buffer and size, reducing room for errors.

    E.g. Live On Coliru

    #include <iostream>
    #include <span>
    
    #if 0
        #include <asio.hpp>
        #define ASIO_STANDALONE
        using asio::error_code;
    #else
        #include <boost/asio.hpp>
        namespace asio = boost::asio;
        using boost::system::error_code;
    #endif
    
    int main() {
        using asio::ip::tcp;
        error_code       ec;
        asio::io_context context;
        tcp::socket      socket(context);
        socket.connect({asio::ip::make_address("93.184.216.34", ec), 80}, ec);
    
        std::cout << ec.message() << '\n';
    
        if (socket.is_open()) {
            std::string sRequest = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
            write(socket, asio::buffer(sRequest), ec);
    
            std::vector<char> vBuffer;
            auto n = read_until(socket, asio::dynamic_buffer(vBuffer), "\r\n\r\n", ec);
    
            auto headers = std::span(vBuffer).subspan(0, n);
            for (auto c : headers) {
                std::cout << c;
            }
        }
    }
    

    Showing

    enter image description here

    Out of the box

    Of course, now you woud need to parse the headers, get the content-length, read the body (taking into account the part of the body that was already received in vBuffer).

    That's only scratching the surface because the response may use chunked encoding.

    You have to implement all that.

    OR you could use a library like Beast to do it for you instead of reinventing the wheel (likely with security vulnerabilities).

    Live On Coliru

    #include <boost/beast.hpp>
    #include <iostream>
    namespace asio = boost::asio;
    namespace http = boost::beast::http;
    using asio::ip::tcp;
    using boost::system::system_error;
    
    int main() try {
        asio::io_context context;
        tcp::socket      socket(context);
        socket.connect({asio::ip::make_address("93.184.216.34"), 80});
    
        http::request<http::empty_body> req(http::verb::get, "/", 11);
        req.set(http::field::host, "example.com");
        req.set(http::field::connection, "close");
        req.prepare_payload(); // not required, there's no body, but for instructional value
    
        write(socket, req);
    
        http::response<http::string_body> res;
        boost::beast::flat_buffer buf;
        read(socket, buf, res);
    
        std::cout << res.base() << std::endl; // headers
        if (res.chunked())
            std::cout << "Response used chunked encoding" << std::endl;
        std::string body = std::move(res.body());
        std::cout << "The body size is " << body.length() << std::endl;
        // std::cout << body << std::endl;
    } catch (system_error const& se) {
        std::cerr << "Error: " << se.code().message() << '\n';
    }
    

    Printing e.g.

    enter image description here