问题
I am using asio standalone and an HTTPS wrapper from Eidheim (Eidheims SimpleHttpsServer) to set up an HTTPS server on Windows with asynchronous rerquest handling and a thread pool. Occassionally the HTTPS server gets raw socket queries though, because I want to replace an older socket server and if the client app is not up to date, they wont send HTTP(s) formatted queries. For HTTP this was no problem, because I could change the Read (from socket) method to use the legacy code for request handling instead, if the incoming query did not have HTTP format.
Now, trying the same on HTTPS ssl socket streams, the server first needs to perform an ssl handshake, before any reading takes place, so I need to read (peek) into the socket before that handshake to verify if it needs pure socket fallback methods or standard HTTPS methods.
But whenever I read the socket before that handshake manually, Bytes are missing on the input stream and could not yet provide those missing bytes to the handshake/reading process.
So I thought it would be easier to leave the bytes on the input stream and instead peek, but I have not yet found a way to peek into asio::ssl::stream. (async_receive and the flag message_peek are supposed to work, but I couldn't find it. The only documentation I found is for boost::beast)
My only angle on this is the overwritten accept function, in which a read is called if the handshake succeeds:
(from https://gitlab.com/eidheim/Simple-Web-Server/-/blob/master/server_https.hpp)
void accept() override {
auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const error_code& ec) {
auto lock = connection->handler_runner->continue_lock();
if (!lock)
return;
if (ec != error::operation_aborted)
this->accept();
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
if (!ec) {
asio::ip::tcp::no_delay option(true);
error_code ec;
session->connection->socket->lowest_layer().set_option(option, ec);
session->connection->set_timeout(config.timeout_request);
// ***** I need to read (peek) before this to decide if a handshake is needed *****
session->connection->socket->async_handshake(asio::ssl::stream_base::server, [this, session](const error_code& ec) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if (!lock)
return;
if (!ec)
this->read(session);
else if (this->on_error)
this->on_error(session->request, ec);
});
}
else if (this->on_error)
this->on_error(session->request, ec);
});
}
Does anyone have some insight how to peek into asio ssl streams (I just need the first Byte actually)? Or does someone know this library and has another idea on how to tackle this? Any other example of mixed (asio) servers (https and raw sockets) which I could look into?
Thanks Natulux
回答1:
Turns out peeking into the socket is discouraged and also hard to accomplish, even more so with asio standalone. The workaround I found works like this:
- Switch from Asio Standalone library to boost::asio, because boost::asio has additional overloads for the asio namespace (at least when comparing the atm newest builds boost 1.72.0 and asio 1.13.0)
- As described in this article (Is it possible to do async_handshake after reading from socket prior using Boost::asio?) read the whole handshake, if you need any reading from the ssl stream in before and pass the read buffer to the async_handshake overload (see first point) as second parameter
For me, it looks like this:
void accept() override {
auto connection = create_connection(*io_service, context);
acceptor->async_accept(connection->socket->lowest_layer(), [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
if(ec != error::operation_aborted)
this->accept();
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
if(!ec) {
asio::ip::tcp::no_delay option(true);
error_code ec;
session->connection->socket->lowest_layer().set_option(option, ec);
//read some bytes, needed before the handshake
const unsigned int bytesToRead = 1;
int size_of_the_data = 100;
std::vector<unsigned char> _raw_buffer(size_of_the_data);
asio::mutable_buffers_1 sslBuffer(asio::buffer(_raw_buffer, size_of_the_data));
//You should make this async!
asio::read(session->connection->socket->next_layer(), boost::asio::buffer(sslBuffer, bytesToRead), asio::transfer_exactly(bytesToRead));
//Get the read data from the buffer in a readable form
unsigned char * firstByte = asio::buffer_cast<unsigned char*>(sslBuffer);
//Use the data somehow (in my case, use the first Byte to see if I need raw socket handling or ssl handshake + https handling)
if (SocketQuery::CheckForSocketQuery(firstByte[0])) {
this->read_socket(session, firstByte[0]);
}
else
{
//read handshake, 4000 Bytes should be way more than any handshake needs (which is something between 200 and 400 bytes usually)
//You should make this async!
std::size_t bytesOfHandshake = session->connection->socket->next_layer().read_some(boost::asio::buffer(sslBuffer + bytesToRead, 4000));
bytesOfHandshake += bytesToRead;
session->connection->set_timeout(config.timeout_request);
//Use overload of async_handshake with buffer as second parameter
//Note that the async callback lambda is expected to take the buffer and buffer size as you see below
session->connection->socket->async_handshake(asio::ssl::stream_base::server, asio::buffer(sslBuffer, bytesOfHandshake), [this, sslBuffer, session](const error_code& ecHttps, std::size_t bufferSize) {
session->connection->cancel_timeout();
auto lock = session->connection->handler_runner->continue_lock();
if (!lock)
return;
if (!ecHttps)
{
this->read(session);
}
else if (this->on_error)
{
this->on_error(session->request, ecHttps);
wxLogMessage("server error: " + wxString(ecHttps.message()));
}
else
{
wxLogMessage("server error: " + wxString(ecHttps.message()));
}
});
}
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
来源:https://stackoverflow.com/questions/60932525/peek-asio-https-ssl-stream-without-deleting-from-input-stream