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
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
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).
#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.