Last active
August 16, 2020 10:43
-
-
Save aromanro/d053c1e66bbf29fa6b123186607158a2 to your computer and use it in GitHub Desktop.
How to use boost for a https api call
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <boost/asio.hpp> | |
#include <boost/asio/ssl.hpp> | |
#include <boost/beast.hpp> | |
#include <boost/format.hpp> | |
namespace net = boost::asio; // from <boost/asio.hpp> | |
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp> | |
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp> | |
namespace http = boost::beast::http; // from <boost/beast/http.hpp> | |
namespace beast = boost::beast; // from <boost/beast.hpp> | |
#include <boost/property_tree/ptree.hpp> | |
#include <boost/property_tree/json_parser.hpp> | |
//#define USE_WINGOZ 1 | |
#ifdef USE_WINGOZ | |
#include "wincrypt.h" | |
inline void load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) | |
{ | |
HCERTSTORE hstore = CertOpenSystemStore(NULL, L"ROOT"); | |
if (!hstore) return; | |
char* data = NULL; | |
PCCERT_CONTEXT pcert_context = NULL; | |
do | |
{ | |
pcert_context = CertEnumCertificatesInStore(hstore, pcert_context); | |
if (!pcert_context) break; | |
X509* x509 = d2i_X509(NULL, const_cast<const BYTE**>(&pcert_context->pbCertEncoded), pcert_context->cbCertEncoded); | |
BIO* bio = BIO_new(BIO_s_mem()); | |
if (PEM_write_bio_X509(bio, x509)) | |
{ | |
const long int len = BIO_get_mem_data(bio, &data); | |
ctx.add_certificate_authority(boost::asio::buffer(data, static_cast<std::size_t>(len)), ec); | |
} | |
BIO_free(bio); | |
X509_free(x509); | |
} while (!ec); | |
CertCloseStore(hstore, 0); | |
} | |
inline void load_root_certificates(ssl::context& ctx) | |
{ | |
boost::system::error_code ec; | |
load_root_certificates(ctx, ec); | |
if (ec) throw boost::system::system_error{ ec }; | |
} | |
#endif | |
int main() | |
{ | |
try | |
{ | |
boost::system::error_code ec; | |
const std::string hostName = "jsonplaceholder.typicode.com"; | |
net::io_service io_ctxt; | |
// DNS stuff | |
tcp::resolver resolver(io_ctxt); | |
tcp::resolver::query query(hostName, "https"); | |
auto res = resolver.resolve(query); | |
// now the certification stuff | |
ssl::context ctx(ssl::context::method::sslv23_client); | |
ctx.set_verify_mode(ssl::verify_peer); | |
#ifdef USE_WINGOZ | |
load_root_certificates(ctx); // if you want to use windows root certificates store | |
#else | |
ctx.load_verify_file("cacert.pem"); // taken from mozilla | |
#endif | |
ssl::stream<tcp::socket> ssock(io_ctxt, ctx); // secure socket stream | |
tcp::socket::lowest_layer_type& sock = ssock.lowest_layer(); // for convenience | |
connect(sock, res); | |
sock.set_option(tcp::no_delay(true)); | |
// Perform SSL handshake and verify the remote host's | |
// certificate. | |
ssock.set_verify_mode(ssl::verify_peer); | |
ssock.set_verify_callback(ssl::rfc2818_verification(hostName)); | |
// not all servers need it | |
if (!SSL_set_tlsext_host_name(ssock.native_handle(), hostName.c_str())) | |
{ | |
beast::error_code ecssl{ static_cast<int>(::ERR_get_error()), net::error::get_ssl_category() }; | |
throw beast::system_error{ ecssl }; | |
} | |
ssock.handshake(ssl::stream_base::client); | |
// send request: | |
// Set up an HTTP GET request message | |
http::request<http::string_body> req{ http::verb::get, "/todos", 10 }; | |
req.set(http::field::host, hostName); | |
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); | |
// Send the HTTP request to the remote host | |
http::write(ssock, req); | |
// read response | |
// first, the header | |
// Read the response status line and headers | |
net::streambuf response; | |
const size_t nr = read_until(ssock, response, "\r\n\r\n"); | |
// if you want to parse the header uncomment this: | |
/* | |
std::string istr((std::istreambuf_iterator<char>(&response)), std::istreambuf_iterator<char>()); | |
// save what was read supplementary and is part of the body | |
std::string startBody = istr.substr(nr); | |
istr.resize(nr); // resize to contain only the headers | |
std::cout << istr; | |
// Declare a parser for an HTTP response | |
beast::http::response_parser<beast::http::string_body> parser; | |
parser.put(buffer(istr), ec); | |
if (ec) | |
{ | |
std::cout << "Error parsing header: " << ec.message() << std::endl; | |
return EXIT_FAILURE; | |
} | |
*/ | |
response.consume(nr); | |
do | |
{ | |
read(ssock, response, ec); | |
} while (!ec); | |
// Gracefully close the stream | |
ssock.shutdown(ec); | |
if (ec == net::error::eof || ec == ssl::error::stream_truncated) | |
ec = {}; | |
if (ec) throw beast::system_error{ ec }; | |
// If we get here then the connection is closed gracefully | |
std::string respstr((std::istreambuf_iterator<char>(&response)), std::istreambuf_iterator<char>()); | |
// uncomment this if you parse the header | |
//respstr = startBody + respstr; | |
// now the json stuff | |
boost::property_tree::ptree pt; | |
std::istringstream strstrm(respstr); | |
boost::property_tree::read_json(strstrm, pt); | |
// TODO: deal with it | |
int completed = 0; | |
int notCompleted = 0; | |
for (const auto& val : pt) | |
{ | |
if (val.second.get_child("completed").data() == "true") | |
++completed; | |
else | |
++notCompleted; | |
} | |
std::cout << "Completed TODOs: " << completed << std::endl; | |
std::cout << "Not completed TODOs: " << notCompleted << std::endl; | |
} | |
catch (std::exception const& e) | |
{ | |
std::cerr << "Error: " << e.what() << std::endl; | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment