In my application I want to stream jpg images through a HTTP web server using the POCO library, and for this I am using multipart/x-mixed-replace content type in my response body. This means when a GET request arrives to the HTTP server, it is continously sending images to the client in parts in a single http response.
The stream should close and the request handler should return when the client closes the window. But my problem with the HTTPServerResponse.send() stream is that it only gets destroyed, when the response object is destroyed, so I do not know when the client has left, and when do I have to stop sending the images.
That is how the code looks like:
#pragma once
#include <fstream>
#include <sstream>
#include <string>
#include "Poco/Net/HTTPRequestHandler.h"
class StreamHandler : public Poco::Net::HTTPRequestHandler {
public:
void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response) override {
std::ifstream imgFile("../../assets/imgs/random.jpg");
std::stringstream ss{};
ss << imgFile.rdbuf();
std::string buf = ss.str();
std::string boundary = "--BOUNDARY--";
response.setVersion(request.getVersion());
response.setStatus(Poco::Net::HTTPServerResponse::HTTP_OK);
response.setChunkedTransferEncoding(false);
response.setKeepAlive(false);
response.setContentType("multipart/x-mixed-replace; boundary=" + boundary);
response.set("Access-Control-Allow-Origin", "*");
response.set("Connection", "Close");
response.set("Cache-Control",
"no-cache, no-store, must-revalidate, pre-check=0, post-check=0, max-age=0, false");
response.set("Pragma", "no-cache");
std::ostream& ostr = response.send();
while (true) { // <-- What is the stop condition?
ostr << boundary << "\r\n";
ostr << "Content-Type: image/jpeg\r\n"
"Content-Length: " +
std::to_string(buf.length()) + "\r\n\r\n";
ostr << buf;
ostr << "\r\n";
}
}
};
Is there a way to detect whether the client has left?
PS: Possibly I would like a solution within the boundaries of Poco::Net::HTTPRequestHandler. I do not want any solution that requires opening another port, or using another third party library only for the streaming. For example, I am already using nadjieb/cpp-mjpeg-streamer which is working great, but I want to simplify my code and depend only on POCO. In lower level libraries I saw good implementations that are using the following strategy:
while (true) {
if (res) {
res = http_res_send_chunk(req, part_buf, part_len);
} else {
break;
}
}
The send command returns with some positive integer in case of success, and with 0 if the send function failed. However, I cannot replicate this with POCO unfortunately. Any ideas how to get around this?
I finally found a solution and posted it here. If my answer gets accepted, I will flag this question as a duplicate.
Related
I'm working on a project that is relying fairly heavily on packet latency. The architect for the overall system wants to use the Poco::Net::Websocket protocol as the transport layer between the different nodes. We have been running the application with ZMQ till this point, but there's too much overhead and we are not seeing the speeds we need. So I'm tasked with converting the ZMQ portion of the system over to WebSockets.
I've never used WebSockets before and I'm having issues understanding what's going on. I know WebSockets were originally designed as a way to speed up communication between the web browser and the page server decreasing latency through the use of full-duplex bi-directional communication.
There are a lot of tutorials for javascript, nodejs, and even python, but it's more limited when you're talking about C++, which is what I'm working with. The examples that I have found are geared more towards browser requests, which I'm not surprised by. Rather than general TCP packet transfer, which is what our use case would be.
Here are my thoughts and questions.
Seeing how I couldn't really understand what was happening, I decided to start with a Poco::Net::SocketStream configuration first. I chose to do it this way, based on the thought that Websockets are nothing more than a TCP socket inserted into a higher-level protocol framework. I figured transitioning would be easier than trying from scratch. So I spun up a server and client application using standard Poco socket libraries.
I used the example here as a base. I just ended up moving everything to a single file. I also spun up a Poco::Net::TCPServer as well, a sample found here.
I modified both to manage the fact that I'm sending JSON packets between the client and server. I took snippets of our original code to format the JSON packets and encode them with CBOR. I ended up keeping the zmq::message_t format that I was originally using, as I ran into issues receiving the packets on the other end. So as it wasn't the major issue I just skipped that part for now. So I'm aware the ZMQ portions of the code below aren't necessary, a small problem for another time.
Here is the Client code for reference.
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/HTTPMessage.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/HTTPClientSession.h"
#include <iostream>
#include "Poco/Net/SocketAddress.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/SocketStream.h"
#include "Poco/Net/WebSocket.h"
#include <jsoncons/json.hpp>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/cbor/cbor.hpp>
#include <jsoncons_ext/jsonpath/json_query.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
#include <iostream>
#include <string>
#include <zmq.hpp>
using namespace jsoncons;
using Poco::Net::HTTPClientSession;
using Poco::Net::HTTPMessage;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::WebSocket;
class ClientHandler
{
private:
std::string host;
int port;
// IP endpoint/socket address (consists of host addr and port #)
Poco::Net::SocketAddress socketAddr;
// Interface to a TCP stream socket
// Poco::Net::StreamSocket socket;
Poco::Net::Socket socket;
// Stream for reading from / writing to a socket (accepts a socket)
Poco::Net::SocketStream stream;
public:
ClientHandler(std::string h, int p) : host(h), port(p), socketAddr(h, p), socket(), stream(socket)
{
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
// Connect to the initialized socket address' hostname and port
bool connected()
{
std::cout << "Creating a connection with ["
<< socketAddr.host().toString()
<< "] through port [" << socketAddr.port() << "] ...";
try
{
// socket.connect(socketAddr);
// std::cout << "Success!" << std::endl;
}
catch (Poco::Exception err)
{
std::cout << std::endl;
std::cout << "Socket connection error: [" << err.displayText() << "]" << std::endl;
return false;
}
return true;
}
// Send a message to the connected server
bool sendMessage()
{
std::cout << std::endl;
const char channel[] = "fool";
std::string channelPort = std::to_string(5577);
try
{
std::string message;
std::cout << "Enter a message to send to the server: ";
std::cin >> message;
json msg = json(json_object_arg, {{"$op", "create_channel"}, {"$svc", "zmq"}, {"channel_name", channel}, {"port", channelPort}});
std::vector<uint8_t> buffer;
jsoncons::cbor::encode_cbor(msg, buffer);
zmq::const_buffer msg_out = zmq::buffer(buffer);
if (message.compare("exit") != 0)
{
std::cout << "Sending the message to the server!\n\t";
// socket.sendBytes(msg_out.data(), msg_out.size());
return true;
}
else
return false;
}
catch (Poco::Exception err)
{
std::cout << "Data send error: [" << err.displayText() << "]" << std::endl;
return false;
}
}
};
int main(int argc, char **argv)
{
int port = 2001;
std::string hostname = "10.0.12.97";
// Handle the server-client connection and send some JSON
try
{
ClientHandler handler(hostname, port);
if (handler.connected())
while (handler.sendMessage())
;
}
catch (Poco::Exception err)
{
std::cout << "Handler Error -> " << err.displayText() << std::endl;
}
return 0;
}
So, How do I get from this ^ to Websockets?
I have been reading and digging through the documentation for Poco Websockets. At first, I thought I needed to figure out the HTTPClientSession, HTTPRequest, and HTTPResponse objects I saw in so many examples. However, looking at Documentation for Poco Websockets constructors, it appeared all I needed to do was pass it a socket connection. So spent some time trying to figure that out, but after going back to the documentation it appears that it specifies that I need to send it a socket that is already a Websocket.
Creates a WebSocket from another Socket, which must be a WebSocket,
That's what I get for not actually reading it. So I spent some more time reading through the constructors and it appears that I do in fact have to send at least 3 arguments.
WebSocket
WebSocket(
HTTPClientSession & cs,
HTTPRequest & request,
HTTPResponse & response
);
Creates a client-side WebSocket, using the given HTTPClientSession and HTTPRequest for the initial handshake (HTTP Upgrade request).
So I'm now back to HTTPClientSession, HTTPRequest, and HTTPResponse objects.
I think I understand HTTPClientSession. Here I should provide the Server name and port. So updating the code above should look something like this
...
Poco::Net::HTTPClientSession cs;
public:
ClientHandler(std::string h, int p) : host(h), port(p), cs(h, p)
...
HTTPResponse looks to be a pointer to a response object for the WebSocket to return the HTTPRequest response. Looks to contain a status code, date, reason(not sure what reason is)
The HTTPRequest is a point of interest. I'm lost as to what I need to format this as. There are three arguments for the request.
Method - this looks like nothing more than telling the server that you are requesting something or looking to hand something over.
HTTP_POST - I would assume this is what I want?
HTTP_GET
URI - for HTTP_GET this would be the path of the resource I'm looking to retrieve. And for Post, this would be the path that I would like to post something to on the server
Version - looks to be self-explanatory, as this is the HTTP version I would like to use.
but here is the rub of it, I do not want to post/get anything that's associated with a URI, I'm just looking to transfer a JSON packet and manage it on the other end. I've found a sample code of a client that uses this information, but I don't understand why it's using HTTP_GET, and the reason for the /?encode=text reference. I can't seem to find details anywhere on it. That leads me to the set value of the request object, don't understand what the "set()" call is for or how it would work in my situation.
With the new information, I updated my constructor to this.
...
Poco::Net::WebSocket *ws;
public:
ClientHandler(std::string h, int p) : host(h), port(p)
{
std::cout << "TEST" << std::endl;
// Poco::Net::HTTPClientSession cs(h, p);
Poco::Net::HTTPClientSession cs(host, port);
Poco::Net::HTTPRequest req(HTTPRequest::HTTP_GET, "/?encoding=text", HTTPMessage::HTTP_1_1);
// req.set("origin", "http://www.websocket.org");
Poco::Net::HTTPResponse resp;
ws = new WebSocket(cs, req, resp);
std::cout << "Host: " << host << "\tPort: " << port << std::endl;
}
...
However, I enter the constructor but never leave it. It gets hung up when I attempt to create the WebSocket. I'm assuming it doesn't like something. I tried the HTTPClientSesseion cs two ways. and leaving the req.set() in as well. Just hangs.
Could someone lend me some insight as to how I can get Poco::Net::Websocket running? and some information so I can understand what's going on a bit better. So when I attempt the server-side I will have some more information that will help.
I recently upgraded from Boost 1.67.0 to Boost 1.75.0 and immediately ran into issues with boost beast in code that talks to a REST API.
The code was previously working, but now it appears to be sending garbage to the server for the content and I have absolutely no clue why.
Here is the code, which posts a JSON string to the Kubernetes API to specify a custom resource. The specifics of the REST API are immaterial as the Kubernetes API server can't even read the boost POST request:
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <boost/beast/core.hpp>
#include <boost/beast/version.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace bip = boost::asio::ip;
namespace bhttp = boost::beast::http;
namespace ssl = boost::asio::ssl;
void postServiceEndpoint(std::string topicName, std::string url,
std::string host, std::string port, std::string discoveryNamespace)
{
boost::asio::io_context context;
boost::asio::ip::tcp::resolver resolver(context);
ssl::context sslCtx({ssl::context::sslv23_client});
boost::asio::ssl::stream<boost::beast::tcp_stream> sslStream(context, sslCtx);
auto const results = resolver.resolve(host, port);
SSL_set_tlsext_host_name(sslStream.native_handle(), host.c_str());
boost::beast::get_lowest_layer(sslStream).connect(results);
sslStream.handshake(ssl::stream_base::client);
//Load the bearer token for authenticating with K8s...
std::ifstream t("/var/run/secrets/kubernetes.io/serviceaccount/token");
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
std::string bearerToken = str;
std::string target = "/apis/sdsendpoints.net/v1/namespaces/" + discoveryNamespace + "sdsendpoints";
//Because the endpoint hasn't been created yet, we cant use it in the target
//string, but if we want to retrieve the endpoint later, we have to use its name
//in the target string... Kubernetes's REST API be weird like that.
bhttp::request<bhttp::string_body> request(bhttp::verb::post, target, HTTPV1DOT1);
request.set(bhttp::field::host, host);
request.set("Content-Type", "application/json");
request.set("Authorization", "Bearer " + bearerToken);
boost::property_tree::ptree requestTree;
requestTree.put("apiVersion", "sdsendpoints.net/v1");
requestTree.put("kind", "SdsEndpoint");
requestTree.put("metadata.name", topicName);
requestTree.put("spec.endpointURL", url);
std::stringstream jsonStream;
boost::property_tree::write_json(jsonStream, requestTree);
request.body() = jsonStream.str();
request.prepare_payload();
std::cout << "REQUEST: \n" << request << std::endl;
bhttp::write(sslStream, request);
boost::beast::flat_buffer buffer;
bhttp::response<bhttp::string_body> response;
bhttp::read(sslStream, buffer, response);
if(response.result_int() >= 400)
{
std::cout << "Got failure on post endpoint: " << response.result_int() << ": " << response.result() << " : " << response.body() << std::endl;
}
//Cleanup the SSL socket...
boost::system::error_code ec;
sslStream.shutdown(ec);
if(ec == boost::asio::error::eof)
{
//This is fine. I am okay with the events that are unfolding currently.
ec.assign(0, ec.category());
}
if(ec)
{
std::cout << "Got error code: " << ec << " on socket cleanup in SSL shutdown" << std::endl;
}
sslStream.lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if(ec)
{
std::cout << "Got error code: " << ec << " on socket cleanup in TCP socket shutdown." << std::endl;
}
}
Running this code gives the following output for the boost HTTP request:
POST apis/sdsendpoints.net/v1/namespaces/sds-test/sdsendpoints HTTP/1.1
Host: kubernetes
Authorization: Bearer <REDACTED>
Content-Type: application/json
Content-length: 133
{"apiVersion" : "sdsendpoints.net/v1", "kind":"SdsEndpoint","metadata":{"name":"sds-tester"},"spec":{"endpointURL":"tcp://test-host:31337"}}
Which appears to be a completely valid HTTP request.
However, what I get back from the server now (under 1.75.0 that I did not get under 1.67.0) is:
Got failure on post endpoint: 400: Bad Request : {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"the object provided is unrecognized (must be of type SdsEndpoint): couldn't get version/kind; json parse error: unexpected end of JSON input (\u003cempty\u003e)","reason":"BadRequest","code":400}
Which seems to indicate the actual HTTP request is getting scrambled somehow.
Additionally, while cleaning up the SSL socket I get the error code asio.ssl:2, which makes me wonder if the scrambling is due to some kind of error in setting up the connection. However, the code appears to follow the boost 1.75.0 example for synchronous HTTP SSL connections, and both this version and the 1.67.0 form that uses a TCP socket in the ssl stream instead of a boost tcp stream fail in the same way (with the 400 error).
Thinking it might be an issue in boost property tree, I rewrote that section using boost JSON and still got the same 400 error, suggesting that the problem is not in the JSON string itself but whatever boost beast is doing in this newer version with the request.
As a final sanity check I manually http POST'd using curl:
curl -x POST -h "Host: kubernetes" -H "Authorization: Bearer <REDACTED>" -H "Content-Type: application/json" --data '{"apiVersion" : "sdsendpoints.net/v1", "kind":"SdsEndpoint","metadata":{"name":"sds-tester"},"spec":{"endpointURL":"tcp://test-host:31337"}}' https://kubernetes:6443/apis/sdsendpoints.net/v1/namespaces/sds-test/sdsendpoints/ --insecure
And curl had no problem successfully posting to the kubernetes API server (and using the JSON output generated by boost property tree no less).
So right now the focus is clearly on some kind of change in boost::beast between 1.67.0 and 1.75.0. I'm left absolutely scratching my head here wondering if there is some kind of new regression that was introduced in 1.75.0...
I have tried two different compilers for this code: GCC 4.8.5 and Intel icpc 19.1.0.166 20191121. The code is being compiled and run on RHEL 7.9.
The problem, as Yuri pointed out, turned out to be due to a spurious newline at the end of the bearer token file that was being read in.
I am trying to send a get request to acounts.google.com to be able to implement a library for C++ OAuth to learn it.
I get the following code from this post: Creating a HTTPS request using Boost Asio and OpenSSL and modified it as follow:
int main()
{
try
{
std::string request = "/o/oauth2/v2/auth";
boost::system::error_code ec;
using namespace boost::asio;
// what we need
io_service svc;
ssl::context ctx(svc, ssl::context::method::sslv23_client);
ssl::stream<ip::tcp::socket> ssock(svc, ctx);
ip::tcp::resolver resolver(svc);
auto it = resolver.resolve({ "accounts.google.com", "443" }); // https://accouts.google.com:443
boost::asio::connect(ssock.lowest_layer(), it);
ssock.handshake(ssl::stream_base::handshake_type::client);
// send request
std::string fullResuest = "GET " + request + " HTTP/1.1\r\n\r\n";
boost::asio::write(ssock, buffer(fullResuest));
// read response
std::string response;
do
{
char buf[1024];
size_t bytes_transferred = ssock.read_some(buffer(buf), ec);
if (!ec) response.append(buf, buf + bytes_transferred);
std::cout << "Response received: '" << response << "'\n"; // I add this to see what I am getting from the server, so it should not be here.
} while (!ec);
// print and exit
std::cout << "Response received: '" << response << "'\n";
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
if (std::string const * extra = boost::get_error_info<my_tag_error_info>(e))
{
std::cout << *extra << std::endl;
}
}
}
The problem that I have is as follow:
1- The results that I am getting is not what I am getting when I visit https://accounts.google.com/o/oauth2/v2/auth using a web browser. I essentially getting a message that they can not find the requested URL /o/oauth2/v2/auth
<p>The requested URL <code>/o/oauth2/v2/auth</code> was not found on this server. <ins>ThatÔÇÖs all we know.</ins>
How should I setup the GET commend so I can get the same result that I am getting with a browser?
2- The application hangs getting data from server, apparently the following loop is not right:
do
{
char buf[1024];
size_t bytes_transferred = ssock.read_some(buffer(buf), ec);
if (!ec) response.append(buf, buf + bytes_transferred);
} while (!ec);
What is the correct way of reading responce from the web server which is fast and read all data?
Edit 1
For reference based on accepted answer, I fixed the problem using the correct GET header as shown below:
// send request
std::string fullResuest = "GET " + request + " HTTP/1.1\r\n";
fullResuest+= "Host: " + server + "\r\n";
fullResuest += "Accept: */*\r\n";
fullResuest += "Connection: close\r\n\r\n";
boost::asio::write(ssock, buffer(fullResuest));
A HTTP/1.1 request must have a Host header. A simple experiment with OpenSSL will show the problem, i.e. the missing header:
$ openssl s_client -connect accounts.google.com:443
...
GET /o/oauth2/v2/auth HTTP/1.1
... The requested URL <code>/o/oauth2/v2/auth</code> was not found on this server. <ins>That’s all we know.</ins>
When adding the Host header instead we get a different response:
$ openssl s_client -connect accounts.google.com:443
...
GET /o/oauth2/v2/auth HTTP/1.1
Host: accounts.google.com
... >Required parameter is missing: response_type<
Apart from that HTTP/1.1 implicitly uses HTTP keep-alive, i.e. server and client might keep the connection open after the response is done. This means you should not read until the end of connection but should instead properly parse the HTTP header, extract the Content-length header and/or Transfer-Encoding header and behave according to their values. Or if you want it simpler use HTTP/1.0 instead.
For more information see the HTTP/1.1 standard.
My C++ application requires of an HTTP server and I decided to make my own, which is correctly working when sending HTTP Get requests but has some problems when reading HTTP Post requests.
The problem is that when sending HTTP Posts requests, the last header isn't read properly, so I can't get the post request.
This is my code:
void HttpSession::readHeaders(std::shared_ptr<HttpSession> pThis) {
boost::asio::async_read_until(pThis->socket_, pThis->buff_, '\r\n\r\n',
[pThis](const boost::system::error_code &e, std::size_t s) {
std::istream headersStream(&pThis->buff_);
std::string header;
while(std::getline(headersStream, header, '\n')) {
if(header != "\r") {
if(header.back() == '\r') header = header.substr(0, header.length() - 1);
qDebug() << QString::fromStdString(header);
//Some stuff to get content-length to contentLength_
}
}
if(contentLength_ > 0) {
qDebug() << "Reading:";
readBody(pThis);
}
std::shared_ptr<std::string> str = std::make_shared<std::string>(pThis->headers_.getResponse());
boost::asio::async_write(pThis->socket_, boost::asio::buffer(str->c_str(), str->length()),
[pThis, str](const boost::system::error_code &e, std::size_t s) {
qDebug() << "Written";
});
}
void HttpSession::readBody(std::shared_ptr<HttpSession> pThis) {
boost::asio::async_read(pThis->socket_, pThis->data_, boost::asio::transfer_at_least(1),
[pThis](const boost::system::error_code &e, std::size_t s) {
std::istream body(&pThis->data_);
std::string line;
body >> line;
qDebug() << QString::fromStdString(line);
});
}
The buff_ and data_ variable are declared as: boost::asio::streambuf. And the HttpSession class is the one which stores the headers and handles all the webpage serving. I didn't include the code of that class since it's not the problem.
The output of an HTTP Post request to /url is this one:
"POST /url HTTP/1.1"
"Host: 192.168.1.41:8080"
"Connection: keep-alive"
"Content-Length: 10"
"Cache-Control: max-age=0"
"Origin: http://192.168.1.41:8080"
"Upgrade-Insecure-Requests: 1"
"User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"
"Content-Type: application/x-www-form-urlencoded"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
"Referer: http://192.168.1.41:8080/"
"Accept-Encoding: gzip, deflate"
"Accept-Langua"
Reading:
"ge:"
So as you can see, the Accept-Language header is not read properly although in this image you can see that the POST request is made properly.
Note that when sending GET requests everything works properly. So my guess is that it has something to do with reading asynchronously, since the async_read_until doesn't block, so async_read of the readBody function, reads before it should. Should I read synchronously, then? Will I be able to handle more than one client either way if I create one HttpSession class for each client? (I really don't need to support more than one user, but still, it would be nice).
For the client handling I do like this:
void HttpServer::run() {
using namespace boost::asio;
io_service io_service;
ip::tcp::endpoint endpoint{ip::tcp::v4(), 8080};
ip::tcp::acceptor acceptor{io_service, endpoint};
acceptor.listen();
runServer(acceptor, io_service);
io_service.run();
while(true) QThread::msleep(5000);
}
void HttpServer::runServer(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& io_service) {
std::shared_ptr<HttpSession> ses = std::make_shared<HttpSession>(io_service);
acceptor.async_accept(ses->socket_,
[ses, &acceptor, &io_service](const boost::system::error_code& accept_error) {
runServer(acceptor, io_service);
if(!accept_error) HttpSession::interact(ses);
});
}
All kind of help will be appreciated! If you have any code improvement, please tell me. Thanks!
I think the problem is with '\r\n\r\n'. That's not a string but a char.
The compiler should normally warn you about this, with something like:
warning: implicit conversion from 'int' to 'char' changes value from 218762506 to 10 [-Wconstant-conversion]
Try replacing that with "\r\n\r\n".
I need to write a command line client for playing tic-tac-toe over a server.
the server accepts http requests and sends back json to my client. i am looking for a quick way to send a http request and receive the json as a string using boost libraries.
example http request = "http://???/newGame?name=david"
example json response = "\"status\":\"okay\", \"id\":\"game-23\", \"letter\":2"
The simplest thing that fits the description:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
int main() {
boost::system::error_code ec;
using namespace boost::asio;
// what we need
io_service svc;
ip::tcp::socket sock(svc);
sock.connect({ {}, 8087 }); // http://localhost:8087 for testing
// send request
std::string request("GET /newGame?name=david HTTP/1.1\r\n\r\n");
sock.send(buffer(request));
// read response
std::string response;
do {
char buf[1024];
size_t bytes_transferred = sock.receive(buffer(buf), {}, ec);
if (!ec) response.append(buf, buf + bytes_transferred);
} while (!ec);
// print and exit
std::cout << "Response received: '" << response << "'\n";
}
This receives the full response. You can test it with a dummy server:(also Live On Coliru):
netcat -l localhost 8087 <<< '"status":"okay", "id":"game-23", "letter":2'
This will show that the request is received, and the response will be written out by our client code above.
Note that for more ideas you could look at the examples http://www.boost.org/doc/libs/release/doc/html/boost_asio/examples.html (although they focus on asynchronous communications, because that's the topic of the Asio library)