EN VI

C++ - Asio no response afer sending a basic http request?

2024-03-10 00:30:05
How to C++ - 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

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login