C++ initiates https request

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.

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> &errors) {
    
    
  qDebug() << "SSL verification errors:";
  for (const auto &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);
    // 要求该证书是有效的
    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([&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.

参考:How to send a https request with boost beastWhat 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() << "\n";
      std::cout << "Body: "
                << boost::beast::buffers_to_string(res.body().data()) << "\n";
      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() << "\n";
      std::cout << "Body: "
                << boost::beast::buffers_to_string(res.body().data()) << "\n";
      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.

参考:Simple C example of doing an HTTP POST and consuming the responseC to perform HTTPS requests with opensslhow to do http & https request with openssl

Because http/https requests are to be supported at the same time, function pointers are used. Structurally referenced: 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为 AF_INET or AF_INET6
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP; // 指定协议为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\n", 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 && (sizeof(con->recv_buf) - con->recv_n > 0)) {
    
    
    // 同步方式,当读取到一个完整的response的时候,停止
    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 && (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

Guess you like

Origin blog.csdn.net/sinat_38816924/article/details/130837151