C++ initiates https request

Article directory

    • foreword
    • Qt initiates a get request for https
    • beast initiates a HTTPS get request
    • socket + openssl + http_parse initiate https get request
    • other

Foreword

It is not an easy task to initiate an https request with c/c++.

The general logic is as follows: socket network programming is the basis; the content sent and received is http message; in order to ensure security, TLS is inserted between tcp (network layer) and http (application layer).

As for programming to implement the above logic, there are different options in different library scenarios.

  • If it is desktop client programming, and the company has purchased qt, the easiest way is to use QT-HTTP Client
  • If you don’t use qt and you can use a higher version of boost, you can use How to send a https request with boost beast
  • If the boost library is not available, and it is C ++ programming, you can find some open source implementations on github.
  • If it is a C environment, C++ cannot be used. Then you have to use socket programming + openssl programming + http message construction and analysis (very troublesome). You can refer to: Simple C example of doing an HTTP POST and consuming the response, how to do http & amp; https request with openssl

Although it is troublesome, I have achieved it. See the repository for detailed code.

qt initiates a get request for https

Qt encapsulates network requests very well and is relatively easy to use. But the commercial version requires a fee.

Reference: QT-HTTP Client

#include <QApplication>
#include <QDebug>
#include <QEventLoop>
#include <QLoggingCategory>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QScopeGuard>
#include <QSslKey>
#include <QUrl>

void handleSslErrors(QNetworkReply *reply, const QList<QSslError> & amp;errors) {<!-- -->
  qDebug() << "SSL verification errors:";
  for (const auto & amp;error : errors) {<!-- -->
    qDebug() << "Error: " << error. errorString();
    const auto cert = error. certificate();
    if (!cert.isNull()) {<!-- -->
      qDebug() << "Issuer: " << cert. issuerInfo(QSslCertificate::CommonName);
      qDebug() << "Subject: " << cert.subjectInfo(QSslCertificate::CommonName);
      qDebug() << "Expiration date: " << cert.expiryDate().toString();
      if (cert.isBlacklisted()) {<!-- -->
        qDebug() << "Certificate is blacklisted!";
      }
      if (cert. publicKey(). isNull()) {<!-- -->
        qDebug() << "Certificate has no public key!";
      }
    }
  }
  reply->ignoreSslErrors();
}

void http_get(QString url_str) {<!-- -->
  QUrl url(url_str);
  QNetworkRequest request;
  QNetworkAccessManager manager;

  if (url. scheme() == "https") {<!-- -->
    QSslConfiguration sslConfiguration = request.sslConfiguration();
    sslConfiguration.setProtocol(QSsl::TlsV1_2OrLater);
    // Requires that the certificate is valid
    sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer);
    request.setSslConfiguration(sslConfiguration);
  }
  request.setUrl(url);

  QNetworkReply *reply = manager. get(request);
  QObject::connect(reply, &QNetworkReply::sslErrors, &QNetworkReply::sslErrors);
  auto guard = qScopeGuard([ & amp;reply] {<!-- --> reply->deleteLater(); });
  QEventLoop loop;
  QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
  loop.exec();

  qDebug()
      << "HTTP Status Code: "
      << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  if (reply->error() != QNetworkReply::NoError) {<!-- -->
    qDebug() << reply->errorString();
  } else {<!-- -->
    qDebug() << reply->readAll();
  }
}

int main(int argc, char *argv[]) {<!-- -->
  QApplication app(argc, argv);
  QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, true);
  http_get("https://www.baidu.com/");
  // app. exec();
  return 0;
}

beast initiates a https get request

beast is more difficult to use than qt. There are very few examples in Chinese, just look at the official demo, see the link below.

Reference: How to send a https request with boost beast, What do I need to do to make Boost.Beast HTTP parser find the end of the body?, Using Boost-Beast (Asio) http client with SSL (HTTPS)

#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <iostream>
#include <string>

class req_info {<!-- -->
public:
  std::string host;
  std::string service;
  std::string target;
  boost::beast::http::verb method;
};

class request {<!-- -->
public:
  request() = default;
  request(req_info info_) : info(info_) {<!-- -->
    IS_HTTPS = info. service == "https" ? true : false;
  }

  void get_response() {<!-- -->
    if (IS_HTTPS) {<!-- -->
      https_get();
    } else {<!-- -->
      http_get();
    }
  }

private:
  void http_get() {<!-- -->
    try {<!-- -->
      req. method(info. method);
      req. target(info. target);
      req.set(boost::beast::http::field::host, info.host);
      req.set(boost::beast::http::field::user_agent, "boost beast test");
      boost::asio::io_context ioc;
      boost::asio::ip::tcp::resolver resolver(ioc);
      auto results = resolver.resolve(info.host, info.service);

      boost::beast::tcp_stream stream(ioc);
      stream. connect(results);

      boost::beast::http::write(stream, req);

      boost::beast::flat_buffer buffer;
      boost::beast::http::read(stream, buffer, res);

      std::cout << "return code: " << res.result_int() << std::endl;
      std::cout << "HTTP/" << res.version() << " " << res.result() << " "
                << res.reason() << "\
";
      std::cout << "Body: "
                << boost::beast::buffers_to_string(res.body().data()) << "\
";
      stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
    } catch (std::exception const &e) {<!-- -->
      std::cerr << "Error: " << e.what() << std::endl;
    }
  }

  void https_get() {<!-- -->
    try {<!-- -->
      boost::asio::io_context ioc;
      boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client);
      ctx.set_verify_mode(boost::asio::ssl::verify_none);
      boost::asio::ip::tcp::resolver resolver(ioc);
      auto results = resolver.resolve(info.host, info.service);

      boost::beast::ssl_stream<boost::beast::tcp_stream> stream(ioc, ctx);
      // Set SNI Hostname (many hosts need this to handshake successfully)
      SSL_set_tlsext_host_name(stream.native_handle(), info.host.c_str());

      boost::beast::get_lowest_layer(stream).connect(results);

      // Perform the SSL handshake
      stream.handshake(boost::asio::ssl::stream_base::client);

      req. method(info. method);
      req. target(info. target);
      req.set(boost::beast::http::field::host, info.host);
      req.set(boost::beast::http::field::user_agent, "boost beast https test");

      boost::beast::http::write(stream, req);

      boost::beast::flat_buffer buffer;
      boost::beast::http::read(stream, buffer, res);

      std::cout << "https response" << std::endl;
      std::cout << "return code: " << res.result_int() << std::endl;
      std::cout << "HTTP/" << res.version() << " " << res.result() << " "
                << res.reason() << "\
";
      std::cout << "Body: "
                << boost::beast::buffers_to_string(res.body().data()) << "\
";
      stream. shutdown();
    } catch (std::exception const &e) {<!-- -->
      std::cerr << "Error: " << e.what() << std::endl;
    }
  }

private:
  req_info info;
  bool IS_HTTPS;
  boost::beast::http::request<boost::beast::http::string_body> req;
  boost::beast::http::response<boost::beast::http::dynamic_body> res;
};

int main(int argc, char *argv[]) {<!-- -->
  req_info info;
  info.host = "www.baidu.com";
  // info.service = "http";
  info.service = "https";
  info.target = "/";
  info.method = boost::beast::http::verb::get;

  request req(info);
  req. get_response();
}

socket + openssl + http_parse initiates a get request for https

Do not use this scenario unless absolutely necessary. Too much trouble.

Reference: Simple C example of doing an HTTP POST and consuming the response, C to perform HTTPS requests with openssl, how to do http & amp; https request with openssl

Because http/https requests are to be supported at the same time, function pointers are used. Structurally referred to: wrk2-sock

The code below is incomplete, see the repository for details.

The first is the need for a structure to store connections.

typedef enum {<!-- --> OK, ERROR, RETRY } status;

typedef struct connection {<!-- -->
  http_parser parser;
  http_parser_settings settings;
  int fd;
  SSL_CTX *ctx;
  SSL *ssl;
  char url[URL_MAX_LENGTH];
  struct http_parser_url url_parts;
  char send_uf[SENDBUF];
  char recv_buf[RECVBUF];
  int recv_n;
  int is_recv_all;
} connection;

Use function pointers to unify the http/https connection process.

struct sock {<!-- -->
  status (*init)(connection *c);
  status (*connect)(connection *c);
  status (*read)(connection *c);
  status (*write)(connection *c);
  status (*close)(connection *c);
  size_t (*readable)(connection *c);
};

For http, only socket + http_parse is needed.

#include "net.h"
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

status socket_init(connection *con) {<!-- -->
  // do nothing
  return OK;
}

status socket_connect(connection *con) {<!-- -->
  int status;
  struct addrinfo hints;
  struct addrinfo *res;
  memset( & hints, 0, sizeof(hints));
  hints.ai_flags = 0;
  hints.ai_family = AF_INET; // AF_UNSPEC is AF_INET or AF_INET6
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP; // specify the protocol as TCP

  char *host = copy_url_part(con->url, & con->url_parts, UF_HOST);
  char *schema = copy_url_part(con->url, & con->url_parts, UF_SCHEMA);
  if ((status = getaddrinfo(host, schema, &hints, &res)) != 0) {<!-- -->
    fprintf(stderr, "getaddrinfo: %s\
", gai_strerror(status));
    return ERROR;
  }

  for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {<!-- -->
    int sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
    if (sockfd == -1) {<!-- -->
      continue;
    }
    if (connect(sockfd, p->ai_addr, p->ai_addrlen) != -1) {<!-- -->
      con->fd = sockfd;
      break;
    }
  }

  free(host);
  free(schema);
  return OK;
}

status socket_write(connection *con) {<!-- -->
  int n = 0;
  while (n < strlen(con->send_uf)) {<!-- -->
    int send_n = write(con->fd, con->send_uf + n, strlen(con->send_uf) - n);
    if (send_n == -1) {<!-- -->
      return ERROR;
    }
    n + = send_n;
  }
  return OK;
}

status socket_read(connection *con) {<!-- -->
  while (con->is_recv_all == 0 & amp; & amp; (sizeof(con->recv_buf) - con->recv_n > 0)) {<!-- -->
    // Synchronous mode, when a complete response is read, stop
    int n = read(con->fd, con->recv_buf + con->recv_n,
                 sizeof(con->recv_buf) - con->recv_n);
    con->recv_n += n;
    http_parser_execute( &con->parser, &con->settings, con->recv_buf,
                        con->recv_n);
  }
  return OK;
}

status socket_close(connection *con) {<!-- -->
  if (con->fd > 0) {<!-- -->
    close(con->fd);
  }
}

size_t sock_readable(connection *c) {<!-- -->
  int n, rc;
  rc = ioctl(c->fd, FIONREAD, &n);
  return rc == -1 ? 0 : n;
}

For https connections, openssl needs to be introduced.

#include "ssl.h"
#include "net.h"

status ssl_init(connection *con) {<!-- -->
  if (con->ctx = SSL_CTX_new(SSLv23_client_method())) {<!-- -->
    SSL_CTX_set_verify(con->ctx, SSL_VERIFY_NONE, NULL);
    SSL_CTX_set_verify_depth(con->ctx, 0);
    SSL_CTX_set_mode(con->ctx, SSL_MODE_AUTO_RETRY);
    con->ssl = SSL_new(con->ctx);
    return OK;
  }
  return ERROR;
}

status ssl_connect(connection *con) {<!-- -->
  socket_connect(con);
  int r;
  SSL_set_fd(con->ssl, con->fd);
  char *host = copy_url_part(con->url, & con->url_parts, UF_HOST);
  SSL_set_tlsext_host_name(con->ssl, host);
  if ((r = SSL_connect(con->ssl)) != 1) {<!-- -->
    switch (SSL_get_error(con->ssl, r)) {<!-- -->
    case SSL_ERROR_WANT_READ:
      return RETRY;
    case SSL_ERROR_WANT_WRITE:
      return RETRY;
    default:
      return ERROR;
    }
  }
  return OK;
}

status ssl_read(connection *con) {<!-- -->
  while (con->is_recv_all == 0 & amp; & amp; (sizeof(con->recv_buf) - con->recv_n > 0)) {<!-- -->
    int n = SSL_read(con->ssl, con->recv_buf, sizeof(con->recv_buf));
    if (n < 0) {<!-- -->
      switch (SSL_get_error(con->ssl, n)) {<!-- -->
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE:
        continue;
      default:
        return ERROR;
      }
    }
    con->recv_n += n;
    http_parser_execute( &con->parser, &con->settings, con->recv_buf,
                        con->recv_n);
  }
  return OK;
}

status ssl_write(connection *con) {<!-- -->
  int n = 0;
  while (n < strlen(con->send_uf)) {<!-- -->
    int send_n =
        SSL_write(con->ssl, con->send_uf + n, strlen(con->send_uf) - n);
    if (send_n < 0) {<!-- -->
      switch (SSL_get_error(con->ssl, send_n)) {<!-- -->
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE:
        continue;
      default:
        return ERROR;
      }
    }
    n + = send_n;
  }
  return OK;
}

status ssl_close(connection *c) {<!-- -->
  SSL_shutdown(c->ssl);
  SSL_clear(c->ssl);
  return OK;
}

size_t ssl_readable(connection *c) {<!-- --> return SSL_pending(c->ssl); }

Other

  • yhirose/cpp-httplib: A C++ header-only HTTP/HTTPS server and client library
  • libcurl: the multiprotocol file transfer library