I am learning TCP Socket Windows programing with Boost/OpenSSL/VS2019. I guess the below code is a typical server example that echoes received data to the client, it worked as expected when tested with client program. But when tested with a browser such as Chrome or IE11(set proxy IP and port 443 in the browsers), the handshake part always failed with message (handshake failed) "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]". I generated Self-signed Certificate using OpenSSL for testing, and used it in the client program in the way like ssl_context.load_verify_file("cert.pem"), the test turned out to be good. Then I installed "cert.pem" in IE(and Chrome), set the proxy IP and port 443, I was expecting that the handshake will pass, but unfortunately it always failed with the above message.
My Questions are,
In order to make browsers work(the handshake part for now), the domain name or IP, or any other identification info in the Self-signed Certificate has to be accurate to the "proxy" server where I am testing on? I generated a dummy certificate for testing, it has nothing to do with the machine I am testing on. The client program(sends message to the server and receives the message sent back from the server) worked OK with it.
I am trying to implement a simple HTTPS relay, I understand there are many things need to do, my HTTP proxy works so far. To incorporate SSL, the first thing I want to ensure the proxy server can accept browsers' connections thru HTTPS/Proxy request, then transport data back and forth. Is there anything specific in the browser for HTTPS proxy request that failed the handshake? Or am I in the wrong way to build HTTPS/SSL relay? Thank you very much.
class session
{
public:
session(boost::asio::io_service& io_service,
boost::asio::ssl::context& context)
: socket_(io_service, context)
{
}
ssl_socket::lowest_layer_type& socket()
{
return socket_.lowest_layer();
}
void start()
{
socket_.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&session::handle_handshake, this,
boost::asio::placeholders::error));
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "handshake good" << std::endl;
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23)
{
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
std::string get_password() const
{
return "test";
}
void start_accept()
{
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
std::cout << "accept good" << std::endl;
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
Tried:
Generated self-signed Certificate files(cert.pem, key.pem, dh2048.pem) using OpenSSL
Build Server and client Program with the above certificates, Server listening on port 443, Client and server is on the same machine. Handshake between client and server went thru, message sent between client and server went thru. Moved the client to a separate PC, it works as well.
Installed the above certificate in IE and Chrome on the same machine.
Connect browsers with the server via HTTPS proxy on port 443, Handshake failed.
Expected:
Handshake between Browsers and "the Proxy" server goes thru without an error.
Actually Resulted:
browser Handshake failed with message "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]"
Your server is not an HTTP server. You don't disclose what kind of proxy you're using but, if you just point e.g. Chrome at your server, it doesn't like being treated that way either.
Here's with the code made self-contained:
I'd suggest starting out with at least a valid HTTP response, e.g.:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 10,
std::string(data_.data(), bytes_transferred)};
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
This would echo minimal valid HTTP responses ignoring the request:
HTTP/1.0 200 OK
foo
bar
HTTP/1.0 200 OK
bar
qux
HTTP/1.0 200 OK
qux
We're ignoring the client's request, and we're not warning about content-length or keepalive. Le'ts improve that by going HTTP/1.1:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
Now we get responses like on openssl s_client -connect localhost:8989 -quiet -verify_quiet <<< "Hello world":
HTTP/1.1 200 OK
Connection: close
Content-Length: 12
Hello world
Does the browser like it better?
Curl doesn't complain:
curl -k https://localhost:8989/my/page
GET /my/page HTTP/1.1
Host: localhost:8989
User-Agent: curl/7.81.0
Accept: */*
My browser browser sends a load of cookies to the localhost domain:
Not shown in the browser are the actual response headers:
HTTP/1.1 200 OK
Connection: close
Content-Length: 1003
In fact, if the request is larger than 1024 bytes, the full request can't even be received before the server blurts out a partial "echo" and disconnects. Let's improve the situation by at least reading the entire request headers:
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
See it In Full On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
class session {
public:
session(asio::io_service& io_service, ssl::context& context)
: socket_(io_service, context) {}
ssl_socket::lowest_layer_type& socket() {
return socket_.lowest_layer();
}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, this,
asio::placeholders::error));
}
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
void handle_write(error_code error) {
if (!error) {
socket_.async_read_some( //
asio::buffer(data_),
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
delete this;
}
}
private:
ssl_socket socket_;
std::string data_;
http::response<http::string_body> res_;
};
class server {
public:
using Ctx = ssl::context;
server(asio::io_service& io_service, uint16_t port)
: io_service_(io_service)
, acceptor_(io_service, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this,
new_session,
asio::placeholders::error));
}
void handle_accept(session* new_session, error_code error) {
if (!error) {
std::cout << "accept good" << std::endl;
new_session->start();
} else {
delete new_session;
}
start_accept();
}
private:
asio::io_service& io_service_;
tcp::acceptor acceptor_;
ssl::context context_;
};
int main() {
asio::io_service ioc;
server s(ioc, 8989);
ioc.run_for(30s);
}
Further Work
In fact, you probably need to read the HTTP request anyways (since the context is browsers and HTTP proxies). So, perhaps use Beast again:
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
Now we can get rid of the keep_alive(false) since we don't clobber our input.
Now, the delete this anti-pattern can be replaced by the enable_shared_from_this pattern.
If we now move the socket creation to the server and avoid passing io_service& references, we can also remove the violation of encapsulation that was socket() and stop depending on io_service which has been deprecated for several Asio versions.
The result is eerily close to the Beast HTTP server examples:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
#include <boost/lexical_cast.hpp> // for the request echo
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
struct session : public std::enable_shared_from_this<session> {
session(tcp::socket s, ssl::context& context)
: socket_(std::move(s), context) {}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, shared_from_this(),
asio::placeholders::error));
}
private:
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
do_receive();
} else {
std::cout << "handshake failed " + error.what() << std::endl;
}
}
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, shared_from_this(),
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void handle_read(error_code error, size_t /*bytes_transferred*/) {
if (!error) {
res_ = {http::status::ok, 11,
boost::lexical_cast<std::string>(req_)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write,
shared_from_this(),
asio::placeholders::error));
}
}
void handle_write(error_code error) {
if (!error) {
do_receive();
}
}
private:
ssl_socket socket_;
beast::flat_buffer buf_;
http::request<http::string_body> req_;
http::response<http::string_body> res_;
};
struct server {
server(asio::any_io_executor ex, uint16_t port)
: acceptor_(ex, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
using Ctx = ssl::context;
tcp::acceptor acceptor_;
Ctx context_;
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
acceptor_.async_accept(
// make_strand(acceptor_.get_executor()), // for multi-threaded servers
[this](error_code ec, tcp::socket s) {
if (!ec) {
std::cout << "accept good" << std::endl;
auto sess = std::make_shared<session>(std::move(s),
context_);
sess->start();
}
start_accept();
});
}
};
int main() {
asio::io_context ioc;
server s(ioc.get_executor(), 8989);
ioc.run_for(30s);
}
UPDATE
Saving information from the comments for the future:
Here's my review of your code, same but 100 lines of code less.
Adding back 50 lines I implemented the minimal CONNECT parsing and connect code: http://coliru.stacked-crooked.com/a/28fbdaf23ab00586 - See it working with
curl -px http://localhost:8989
on my system, for both HTTP and HTTPS targets:
Listing: Full HTTTP CONNECT de,p supporting any protocol (HTTPS included)
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace Tcp {
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using tcp = asio::ip::tcp;
using socket_type = tcp::socket;
using error_code = boost::system::error_code;
template <typename Session> struct Listener {
Listener(asio::any_io_executor ex, uint16_t bind_port)
: acceptor_(ex, tcp::endpoint({}, bind_port)) {}
void do_accept() {
acceptor_.async_accept( //
make_strand(acceptor_.get_executor()), [this](error_code ec, tcp::socket s) {
std::cerr << "accepted " << s.remote_endpoint() << " (" << ec.message() << ")" << std::endl;
if (!ec) {
std::make_shared<Session>(std::move(s))->start();
do_accept();
} else {
std::cerr << "do_accept: " << ec.message() << std::endl;
}
});
}
private:
tcp::acceptor acceptor_;
};
namespace util {
// Relay from from_ to to_ sockets
// Lifetime shared with shared owner
struct half_duplex {
using Owner = std::weak_ptr<void>;
half_duplex(socket_type& f, socket_type& t) : from_(f), to_(t) {}
void start(Owner w) {
owner_ = w;
do_read();
}
// send previously received pending data first
template <typename ConstBufferSequence> void start(Owner w, ConstBufferSequence pending) {
owner_ = w;
async_write(to_, pending, boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
private:
socket_type& from_;
socket_type& to_;
Owner owner_;
std::array<uint8_t, 8192> buf_;
void do_read() {
from_.async_read_some(
asio::buffer(buf_),
boost::bind(&half_duplex::on_read, owner_shared(), ph::error, ph::bytes_transferred));
}
void on_read(error_code ec, size_t xfer) {
if (!ec)
async_write(to_, asio::buffer(buf_, xfer),
boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
void on_written(error_code ec) {
if (!ec)
do_read();
}
std::shared_ptr<half_duplex> owner_shared() {
if (auto o = owner_.lock())
return std::shared_ptr<half_duplex>(o, this); // aliasing constructor
else
throw std::bad_weak_ptr();
}
};
} // namespace util
namespace proxy {
namespace http = boost::beast::http;
struct Session : std::enable_shared_from_this<Session> {
Session(socket_type s) : client_(std::move(s)), server_(s.get_executor()) {}
void start() {
http::async_read(
client_, lead_in_, req_,
boost::bind(&Session::on_connect_request, shared_from_this(), ph::error));
}
private:
asio::streambuf lead_in_;
http::request<http::empty_body> req_;
http::response<http::empty_body> res_;
socket_type client_, server_;
util::half_duplex down_stream_{server_, client_};
util::half_duplex up_stream_{client_, server_};
void on_connect_request(error_code ec) {
if (ec.failed() || req_.method() != http::verb::connect)
return; // TODO error handling
std::cerr << "Connect request: " << req_ << std::endl;
// TODO handle headers?
std::string upstream(req_.target());
auto pos = upstream.find_last_of(":");
auto host = upstream.substr(0, pos);
auto svc = upstream.substr(pos + 1);
if (svc.empty())
svc = "http";
// TODO async resolve?
auto eps = tcp::resolver(server_.get_executor()).resolve(host, svc);
asio::async_connect(server_, eps,
boost::bind(&Session::on_connect, shared_from_this(), ph::error));
}
void on_connect(error_code ec) {
if (ec)
return; // TODO error handling
std::cerr << "Connected to " << server_.remote_endpoint() << std::endl;
res_ = {http::status::ok, req_.version()};
res_.keep_alive(true);
res_.prepare_payload();
http::async_write(client_, res_,
boost::bind(&Session::on_connect_response, shared_from_this(), ph::error));
}
void on_connect_response(error_code ec) {
if (ec)
return; // TODO error handling
up_stream_.start(shared_from_this());
if (lead_in_.size())
down_stream_.start(shared_from_this(), lead_in_.data());
else
down_stream_.start(shared_from_this());
}
};
}
using Proxy = Listener<proxy::Session>;
} // namespace Tcp
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << std::quoted(argv[0]) << " <bind port>\n";
return 1;
}
auto bind_port = static_cast<uint16_t>(::atoi(argv[1]));
try {
boost::asio::io_context ioc;
Tcp::Proxy p(ioc.get_executor(), bind_port);
p.do_accept();
ioc.run();
} catch (std::exception const& e) {
std::cerr << "main: " << e.what() << std::endl;
return 1;
}
}
I want to initiate the TLS connection only if the server supports the secure connection, to achieve this i have introduced the two message type between the server and client.
Client will send SECURECONNREQ msg to server after tcp connection establishment, if the server is configured to support the TLS it send SECURECONNRESP.
On receiving SECURECONNRESP the client will initiate the the TLS handshake by calling boost asynhandshake API but this API is not sending the correct packet(client hello). In the wireshark i could see it is sending the protocol version as TLS1.1 even if it configured for TLS1.2.
Note: SSL context object is prepared with TLS1.2, ciphers and related certs.
It looks like the asynhandshake will not work properly if there is some data exchange happens on the TCP link.
Is there extra step we need take on BOOST asio to make it work?
The secure connection establishment works perfectly fine with below implementation:
Initiating the TLS connection without sending any data on the TCP link.
The client will initiate the TLS after connection is established(no data transfered on this link) by calling asynhandshake, the server will call boost asynhandshake immediately after the connnection accept.
It's hard, or even impossible, to tell without seeing your actual code. But I thought it a nice challenge to write the quintessential PLAIN/TLS server/client in Asio, just to see whether there were any problems I might not have anticipated.
Sadly, it works. You may compare these and decide what you're doing differently.
If anything, this should help you create a MCVE/SSCCE to repro your issue.
Short Intro
The server accepts connections.
The client initiates a ConnectionRequest. Depending on a runtime flag
_support_tls the server responds with the SECURE capability response (or not).
The client and server upgrade to TLS accordingly.
The client and server execute a very simple protocol session that's
templated on the Stream type, because the implementations are independent of
the transport layer security.
Note, for the example I've used the server.pem from the Asio SSL examples.
The demo then executes servers both with and without SECURE capability, and
verifies that the same simple_client implementation works correctly
against both.
Listing
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <iostream>
namespace http = boost::beast::http;
namespace net = boost::asio;
namespace ssl = net::ssl;
using boost::system::error_code;
using net::ip::tcp;
struct ConnectionRequest {
enum { MAGIC = 0x64fc };
boost::endian::big_uint16_t magic = MAGIC;
};
struct ConnectionResponse {
enum Capabilities {
NONE,
SECURE,
};
boost::endian::big_uint16_t capabilities = NONE;
};
using TLSStream = ssl::stream<tcp::socket>;
template <typename> static inline char const* stream_type = "(plain)";
template <> inline char const* stream_type<TLSStream> = "(TLS)";
static void do_shutdown(tcp::socket& s) {
error_code ignored;
s.shutdown(tcp::socket::shutdown_both, ignored);
}
static void do_shutdown(TLSStream& s) {
error_code ignored;
s.shutdown(ignored);
}
template <typename Stream>
struct Session : std::enable_shared_from_this<Session<Stream> > {
Session(Stream&& s) : _stream(std::move(s)) {}
void start() {
std::cout << "start" << type() << ": " << _stream.lowest_layer().remote_endpoint() << std::endl;
http::async_read(_stream, _buf, _req, self_bind(&Session::on_request));
}
private:
Stream _stream;
http::request<http::string_body> _req;
http::response<http::empty_body> _res;
net::streambuf _buf;
void on_request(error_code ec, size_t) {
std::cout << "on_request" << type() << ": " << ec.message() << std::endl;
http::async_write(_stream, _res, self_bind(&Session::on_response));
}
void on_response(error_code ec, size_t) {
std::cout << "on_reponse" << type() << ": " << ec.message() << std::endl;
do_shutdown(_stream);
}
auto type() const { return stream_type<Stream>; }
auto self_bind(auto member) {
return [self=this->shared_from_this(), member](auto&&... args) {
return (*self.*member)(std::forward<decltype(args)>(args)...);
};
}
};
struct Server {
using Ctx = ssl::context;
Server(auto executor, uint16_t port, bool support_tls)
: _acceptor(executor, {{}, port}),
_support_tls(support_tls)
{
if (_support_tls) {
_ctx.set_password_callback(
[](size_t, Ctx::password_purpose) { return "test"; });
_ctx.use_certificate_file("server.pem", Ctx::file_format::pem);
_ctx.use_private_key_file("server.pem", Ctx::file_format::pem);
}
_acceptor.listen();
accept_loop();
}
~Server() {
_acceptor.cancel();
}
private:
void accept_loop() {
_acceptor.async_accept(
make_strand(_acceptor.get_executor()),
[=, this](error_code ec, tcp::socket&& sock) {
if (!ec)
accept_loop();
else
return;
// TODO not async for brevity of sample here
ConnectionRequest req[1];
read(sock, net::buffer(req));
if (req->magic != ConnectionRequest::MAGIC) {
std::cerr << "Invalid ConnectionRequest" << std::endl;
return;
}
ConnectionResponse res[1]{{ConnectionResponse::NONE}};
if (not _support_tls) {
write(sock, net::buffer(res));
std::make_shared<Session<tcp::socket>>(std::move(sock))
->start();
} else {
res->capabilities = ConnectionResponse::SECURE;
write(sock, net::buffer(res));
// and then do the handshake
TLSStream stream(std::move(sock), _ctx);
stream.handshake(TLSStream::handshake_type::server);
std::make_shared<Session<TLSStream>>(std::move(stream))
->start();
}
});
}
tcp::acceptor _acceptor;
Ctx _ctx{Ctx::method::sslv23};
bool _support_tls;
};
// simplistic one-time request/response convo
template <typename Stream> void simple_conversation(Stream& stream) {
http::write(stream, http::request<http::string_body>(
http::verb::get, "/demo/api/v1/ping", 11,
"hello world"));
{
http::response<http::string_body> res;
net::streambuf buf;
http::read(stream, buf, res);
std::cout << "Received: " << res << std::endl;
}
}
void simple_client(auto executor, uint16_t port) {
// client also not async for brevity
tcp::socket sock(executor);
sock.connect({{}, port});
ConnectionRequest req[1];
write(sock, net::buffer(req));
ConnectionResponse res[1];
read(sock, net::buffer(res));
std::cout << "ConnectionResponse: " << res->capabilities << std::endl;
if (res->capabilities != ConnectionResponse::SECURE) {
simple_conversation(sock);
} else {
std::cout << "Server supports TLS, upgrading" << std::endl;
ssl::context ctx{ssl::context::method::sslv23};
TLSStream stream(std::move(sock), ctx);
stream.handshake(TLSStream::handshake_type::client);
simple_conversation(stream);
}
}
int main() {
net::thread_pool ctx;
{
Server plain(ctx.get_executor(), 7878, false);
simple_client(ctx.get_executor(), 7878);
}
{
Server tls(ctx.get_executor(), 7879, true);
simple_client(ctx.get_executor(), 7879);
}
ctx.join();
}
Prints
ConnectionResponse: start(plain): 0
127.0.0.1:37924
on_request(plain): Success
on_reponse(plain): Success
Received: HTTP/1.1 200 OK
ConnectionResponse: 1
Server supports TLS, upgrading
start(TLS): 127.0.0.1:36562
on_request(TLS): Success
on_reponse(TLS): Success
Received: HTTP/1.1 200 OK
While using the WinHttp request, the call is made successfully however depending if the call takes time to populate in the site, then the value will return nothing and report no errors. We are using a cfc file as the returned response for the api. Some calls will work while others will not (usually the ones that take 2+ seconds to return the call and populate the data).
Sadly the api is private and I cannot provide the call (proprietary information). But I am open to all suggestions and will provide the code that calls the data (which does not contain any proprietary data).
string response;
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
BOOL bResults = FALSE;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen(L"WinHTTP Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
// Specify an HTTP server.
if (hSession) {
hConnect = WinHttpConnect(hSession, s2ws(host).c_str(),
INTERNET_DEFAULT_HTTPS_PORT, 0);
if (!WinHttpSetTimeouts(hSession, 600000, 600000, 600000, 600000))
printf("Error %u in WinHttpSetTimeouts.\n", GetLastError());
}
// Create an HTTP request handle.
if (hConnect)
hRequest = WinHttpOpenRequest(hConnect, L"GET", s2ws(call).c_str(),
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_REFRESH | WINHTTP_FLAG_SECURE);
// Send a request.
if (hRequest) {
bResults = WinHttpSendRequest(hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
0, 0);
}
// End the request.
if (bResults) {
bResults = WinHttpReceiveResponse(hRequest, 0);
}
// Keep checking for data until there is nothing left.
if (bResults) {
do {
// Check for available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
printf("Error %u in WinHttpQueryDataAvailable.\n",
GetLastError());
break;
}
if (!dwSize)
break;
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize + 1];
if (!pszOutBuffer) {
printf("Out of memory\n");
break;
}
// Read the data.
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded))
printf("Error %u in WinHttpReadData.\n", GetLastError());
else {
response = response + string(pszOutBuffer);
}
// Free the memory allocated to the buffer.
delete[] pszOutBuffer;
// This condition should never be reached since WinHttpQueryDataAvailable
// reported that there are bits to read.
if (!dwDownloaded)
break;
} while (dwSize > 0);
}
if (response == "") {
wcout << "Value is blank: " << bResults << endl << GetLastError() << endl << response.c_str() << endl;
}
// Report any errors.
if (!bResults)
printf("Error %d has occurred.\n", GetLastError());
// Close any open handles.
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
return response;
Edit: also using the curl lib also produces the same result of returning no data (it will return data on other api's)
Edit 2: I have also tried the solution from this example WinHttpSendRequest fails with ERROR_WINHTTP_SECURE_FAILURE
It also fails to grab the data, so I am unsure why it fails to grab it.
Edit 3: Fiddler Reports
HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 0
Content-Type: text/plain;charset=UTF-8
Server: Microsoft-IIS/8.5
Set-Cookie: CFID=randomCookie;Path=/
Set-Cookie: CFTOKEN=0;Path=/
Return-Format: plain
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET Date: Thu, 06 Jun 2019 18:15:01 GMT
I can't understand the behaviour of the code below.
When defining the symbol BUG, the third print of the variable this is wrong.
I think there is something in the method resolver::async_resolve that breaks the code. I'd like to understand what :-)
Thanks
#include <boost/asio.hpp>
#include <iostream>
using namespace std;
template <typename F>
#ifdef BUG
void Connect( boost::asio::ip::tcp::resolver& resolver, F Connected )
#else
void Connect( boost::asio::ip::tcp::resolver& resolver, const F& Connected )
#endif
{
resolver.async_resolve(
boost::asio::ip::tcp::resolver::query{ "localhost", "8088" },
[&Connected]( const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator i )
{
Connected();
}
);
}
struct Test
{
void Start()
{
cout << "this1 " << hex << this << dec << endl;
auto handler = [this]()
{
cout << "this2 " << hex << this << dec << endl;
boost::asio::ip::tcp::resolver resolver{ ios };
Connect( resolver, [this]()
{
cout << "this3 " << hex << this << dec << std::endl;
}
);
};
handler();
ios.run();
}
boost::asio::io_service ios;
};
int main()
{
Test t;
t.Start();
}
Your bug is not due to passing to Connect by value vs by const reference, it's undefined behaviour due to calling a dangling reference to a lambda.
This is because you're capturing Connnected by reference in the lambda passed to async_resolve.
resolver.async_resolve(
boost::asio::ip::tcp::resolver::query{ "localhost", "8088" },
[&Connected]( const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator i )
{
Connected(); // Connected is captured by reference
}
);
By the time Connected() is called, it's been popped off the stack and destroyed.
void Start()
{
cout << "this1 " << hex << this << dec << endl;
auto handler = [this]()
{
cout << "this2 " << hex << this << dec << endl;
boost::asio::ip::tcp::resolver resolver{ ios };
Connect( resolver, [this]()
{
cout << "this3 " << hex << this << dec << std::endl;
}
);
};
handler(); // after this function returns Connected will be destructed
ios.run(); // the thread is blocked in ios.run until the resolve returns
}
The call to handler() creates the "Connected" lambda on the stack and passes it to Connect, which in turn creates a lambda which captures Connected by reference, and starts an asynchronous operation.
handler() then returns, popping "Connected" off the stack, destructing it.
ios.run() prevents Test::Start() from returning as it waits for async_resolve to return.
async_resolve completes, and calls its lambda, which in return calls Connected(), which has been destroyed.
You can solve this by capturing Connected by-value
void Connect( boost::asio::ip::tcp::resolver& resolver, F Connected )
{
resolver.async_resolve(
boost::asio::ip::tcp::resolver::query{ "localhost", "8088" },
[Connected]( const boost::system::error_code& ec, boost::asio::ip::tcp::resolver::iterator i )
{
Connected();
}
);
}
The following code of the client:
typedef boost::array<char, 10> header_packet;
header_packet header;
boost::system::error_code error;
...
/** send header */
boost::asio::write(
_socket,
boost::asio::buffer(header, header.size()),
boost::asio::transfer_all(),
error
);
/** send body */
boost::asio::write(
_socket,
boost::asio::buffer(buffer, buffer.length()),
boost::asio::transfer_all(),
error
);
of the server:
struct header {
boost::uint32_t header_length;
boost::uint32_t id;
boost::uint32_t body_length;
};
static header unpack_header(const header_packet& data) {
header hdr;
sscanf(data.data(), "%02d%04d%04d", &hdr.header_length, &hdr.id, &hdr.body_length);
return hdr;
}
void connection::start() {
boost::asio::async_read(
_socket,
boost::asio::buffer(_header, _header.size()),
boost::bind(
&connection::read_header_handler,
shared_from_this(),
boost::asio::placeholders::error
)
);
}
/***************************************************************************/
void connection::read_header_handler(const boost::system::error_code& e) {
if ( !e ) {
std::cout << "readed header: " << _header.c_array() << std::endl;
std::cout << constants::unpack_header(_header);
boost::asio::async_read(
_socket,
boost::asio::buffer(_body, constants::unpack_header(_header).body_length),
boost::bind(
&connection::read_body_handler,
shared_from_this(),
boost::asio::placeholders::error
)
);
} else {
/** report error */
std::cout << "read header finished with error: " << e.message() << std::endl;
}
}
/***************************************************************************/
void connection::read_body_handler(const boost::system::error_code& e) {
if ( !e ) {
std::cout << "readed body: " << _body.c_array() << std::endl;
start();
} else {
/** report error */
std::cout << "read body finished with error: " << e.message() << std::endl;
}
}
On the server side the method read_header_handler() is called, but the method read_body_handler() is never called. Though the client has written down the data in a socket.
The header is readed and decoded successfully.
What's the error?
problem is solved.
error was in my code when sending the result of serialization in asio::write(). so the server could not read anything.