我正在编写一些连接到REST端点的HTTP代码。我在公司网络中工作,与外部世界的连接必须通过HTTP代理,而端点在HTTPS上。
现在,我已经成功地使用Pythonrequests
和curl
进行这些调用,但是在c++中进行相同的调用是一个痛苦的。
我正在尝试使用下面的代码(连接到示例Postmen端点)通过代理执行握手,该代码改编自以下示例:
- https://gist.github.com/derofim/c2562832a58c607f0728aba868a5a515
- Boost Asio https同步调用-错误代码400错误请求
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/http/empty_body.hpp>
#include <boost/beast/http/fields.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>
#include<boost/certify/https_verification.hpp>
#include <iostream>
using tcp = boost::asio::ip::tcp;
namespace http = boost::beast::http;
// https://postman-echo.com/post see https://docs.postman-echo.com/?version=latest
static const std::string
server_endpoint = "/post",
hostname = "postman-echo.com",
target = "https://postman-echo.com:443",
port_no = "443",
authorization_token =
"Auth: "
"c3RhdGljIGNvbnN0IHN0ZDo6c3RyaW5nIGF1dGhvcml6YXRpb"
"25fdG9rZW4gPSAiQXV0aDogIj"
"sK",
client_name = "User-Agent: demo program 0.01",
req_str = R"(name=blabla&password=bloblo)";
int main() {
boost::asio::io_service io_service;
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
// Establish TCP base socket to Proxy
tcp::socket base_sock{io_service};
tcp::resolver resolver(io_service);
{
// Connect socket via proxy
const boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp> proxy_hosts = resolver.resolve("PROXY_ADDR", "PROXY_PORT");
if (proxy_hosts.empty()) {
throw std::range_error("Proxy cannot be resolved.");
}
std::cout << "Proxy IP address: " << proxy_hosts->endpoint().address() << std::endl;
//boost::asio::ip::tcp::socket::lowest_layer_type& socket = base_sock.lowest_layer();
base_sock.connect(proxy_hosts->endpoint());
}
{
// Converts request to transparent TCP/IP tunnel. To facilitate TLS communications
// through HTTP proxy.
http::request<http::string_body> req1{http::verb::connect, target, 11};
req1.set(http::field::host, target);
http::write(base_sock, req1);
boost::beast::flat_buffer buffer;
http::response<http::empty_body> res;
http::parser</* isRequest */ false, http::empty_body> http_parser(res);
/** Set the skip parse option. */
http_parser.skip(true); // see https://stackoverflow.com/a/49837467/10904212
http::read_header(base_sock, buffer, http_parser);
// Write the message to standard out
std::cout << "target_host response: " << res << std::endl;
}
ctx.set_verify_mode(boost::asio::ssl::verify_peer);
//ctx.load_verify_file("/** ssl/cacert from python requests library */");
//ctx.add_verify_path("/** CA certificates from Curl via strace https://serverfault.com/questions/485597/default-ca-cert-bundle-location */");
//ctx.load_verify_file("/** CA certs from curl via calling endpoints with --verbose */");
//ctx.set_default_verify_paths();
boost::certify::enable_native_https_server_verification(ctx); // from lib https://github.com/djarek/certify
ctx.set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket&> ssl_socket(base_sock,
ctx);
{
//ssl_socket.handshake(boost::asio::ssl::stream_base::client);
ssl_socket.handshake(boost::asio::ssl::stream<tcp::socket&>::client);
/** Make REST calls here */
}
}
在握手步骤中运行上述代码失败,并出现"流截断";错误。
到目前为止,我的尝试是:
- 验证使用的证书是否正确,其中我已经从Python, Curl和certificate中提供了证书。这还没有解决问题。
- 检查Python和Curl的协议步骤跟踪,但是由于boost::asio的错误消息有限,我无法在c++中验证这一点;而
gdb
的踪迹很难追踪。 - 大量的stackoverflow挖掘,不幸的是我无法拼凑一个工作原型。
鉴于我编写网络代码的经验有限,但是,错误很可能是在我的逻辑某处,而不是不正确的环境。
想知道是否有人可以给任何指针的地方可能是不正确的代码?
经过更多的实验,我注意到问题是在target
变量。设置target = "postmen-echo.com:443";
-删除https
前缀,并传入适当的CA证书(例如来自Python或Curl)可以解决此问题。
我在TLS握手期间得到了相同的错误消息。在我的案例中,它是与sni相关的(https://en.wikipedia.org/wiki/Server_Name_Indication)。我找到了这个有用的答案:https://stackoverflow.com/a/71081152/6536883
SSL_set_tlsext_host_name(socket.native_handle(), "...");