Как создать повышение ssl iostream?
Я добавляю поддержку HTTPS коду, который вводит и выводит, используя boost tcp:: iostream (действуя как HTTP-сервер).
Я нашел примеры (и у вас есть рабочий игровой сервер HTTPS), которые выполняют ввод/вывод SSL с помощью boost:: asio:: read/boost:: asio:: write, но не используют iostreams и < < < < < < < < < <; → операторов. Как превратить поток ssl:: в iostream?
Рабочий код:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/foreach.hpp>
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
using namespace boost;
using boost::asio::ip::tcp;
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream;
string HTTPReply(int nStatus, const string& strMsg)
{
string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
ostringstream s;
s << "HTTP/1.1 " << nStatus << " " << strStatus << "\r\n"
<< "Connection: close\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT\r\n"
<< "Server: json-rpc/1.0\r\n"
<< "\r\n"
<< strMsg;
return s.str();
}
int main()
{
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally
boost::asio::io_service io_service;
tcp::endpoint endpoint(boost::asio::ip::address_v4::loopback(), 1111);
tcp::acceptor acceptor(io_service, endpoint);
boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23);
context.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2);
context.use_certificate_chain_file("server.cert");
context.use_private_key_file("server.pem", boost::asio::ssl::context::pem);
for(;;)
{
// Accept connection
ssl_stream stream(io_service, context);
tcp::endpoint peer_endpoint;
acceptor.accept(stream.lowest_layer(), peer_endpoint);
boost::system::error_code ec;
stream.handshake(boost::asio::ssl::stream_base::server, ec);
if (!ec) {
boost::asio::write(stream, boost::asio::buffer(HTTPReply(200, "Okely-Dokely\n")));
// I really want to write:
// iostream_object << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
}
}
Кажется, что ssl:: stream_service будет ответом, но это тупик.
Использование boost:: iostreams (как предложено принятым ответом) - правильный подход; здесь рабочий код, в котором я закончил:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/iostreams/concepts.hpp>
#include <boost/iostreams/stream.hpp>
#include <sstream>
#include <string>
#include <iostream>
using namespace boost::asio;
typedef ssl::stream<ip::tcp::socket> ssl_stream;
//
// IOStream device that speaks SSL but can also speak non-SSL
//
class ssl_iostream_device : public boost::iostreams::device<boost::iostreams::bidirectional> {
public:
ssl_iostream_device(ssl_stream &_stream, bool _use_ssl ) : stream(_stream)
{
use_ssl = _use_ssl;
need_handshake = _use_ssl;
}
void handshake(ssl::stream_base::handshake_type role)
{
if (!need_handshake) return;
need_handshake = false;
stream.handshake(role);
}
std::streamsize read(char* s, std::streamsize n)
{
handshake(ssl::stream_base::server); // HTTPS servers read first
if (use_ssl) return stream.read_some(boost::asio::buffer(s, n));
return stream.next_layer().read_some(boost::asio::buffer(s, n));
}
std::streamsize write(const char* s, std::streamsize n)
{
handshake(ssl::stream_base::client); // HTTPS clients write first
if (use_ssl) return boost::asio::write(stream, boost::asio::buffer(s, n));
return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n));
}
private:
bool need_handshake;
bool use_ssl;
ssl_stream& stream;
};
std::string HTTPReply(int nStatus, const std::string& strMsg)
{
std::string strStatus;
if (nStatus == 200) strStatus = "OK";
else if (nStatus == 400) strStatus = "Bad Request";
else if (nStatus == 404) strStatus = "Not Found";
else if (nStatus == 500) strStatus = "Internal Server Error";
std::ostringstream s;
s << "HTTP/1.1 " << nStatus << " " << strStatus << "\r\n"
<< "Connection: close\r\n"
<< "Content-Length: " << strMsg.size() << "\r\n"
<< "Content-Type: application/json\r\n"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT\r\n"
<< "Server: json-rpc/1.0\r\n"
<< "\r\n"
<< strMsg;
return s.str();
}
void handle_request(std::iostream& s)
{
s << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
int main(int argc, char* argv[])
{
bool use_ssl = (argc <= 1);
// Bind to loopback 127.0.0.1 so the socket can only be accessed locally
io_service io_service;
ip::tcp::endpoint endpoint(ip::address_v4::loopback(), 1111);
ip::tcp::acceptor acceptor(io_service, endpoint);
ssl::context context(io_service, ssl::context::sslv23);
context.set_options(
ssl::context::default_workarounds
| ssl::context::no_sslv2);
context.use_certificate_chain_file("server.cert");
context.use_private_key_file("server.pem", ssl::context::pem);
for(;;)
{
ip::tcp::endpoint peer_endpoint;
ssl_stream _ssl_stream(io_service, context);
ssl_iostream_device d(_ssl_stream, use_ssl);
boost::iostreams::stream<ssl_iostream_device> ssl_iostream(d);
// Accept connection
acceptor.accept(_ssl_stream.lowest_layer(), peer_endpoint);
std::string method;
std::string path;
ssl_iostream >> method >> path;
handle_request(ssl_iostream);
}
}
Ответы
Ответ 1
@Guy предложение (используя boost::asio::streambuf
) должно работать, и это, вероятно, проще всего реализовать. Основным недостатком этого подхода является то, что все, что вы пишете в iostream, буферизуется в памяти до конца, когда вызов boost::asio::write()
будет выгружать все содержимое буфера в поток ssl сразу. (Я должен отметить, что такая буферизация может быть во многих случаях желательна, и в вашем случае это, вероятно, не имеет никакого значения, поскольку вы сказали это приложение с небольшим объемом).
Если это просто "одноразовый", я бы, вероятно, использовал его с помощью подхода @Guy.
При этом есть ряд веских причин, по которым у вас может быть решение, позволяющее вам использовать вызовы iostream для прямой записи в ваш ssl_stream
. Если вы обнаружите, что это так, тогда вам нужно создать свой собственный класс-оболочку, который расширяет std::streambuf
, переопределяя overflow()
и sync()
(и, возможно, другие в зависимости от ваших потребностей).
К счастью, boost::iostreams
обеспечивает относительно простой способ сделать это без необходимости беспорядочно вмешиваться в классы std. Вы просто создаете свой собственный класс, который реализует соответствующий Device
контракт. В этом случае Sink
, а класс boost::iostreams::sink
предоставляется как удобный способ получить большую часть пути. Если у вас есть новый класс Sink, который инкапсулирует процесс записи в ваш базовый ssl_stream, все, что вам нужно сделать, это создать boost::iostreams::stream
, который будет шаблонизирован для вашего нового типа устройства и от вас.
Он будет выглядеть примерно следующим образом (этот пример адаптирован из здесь, см. также fooobar.com/questions/222934/...):
//---this should be considered to be "pseudo-code",
//---it has not been tested, and probably won't even compile
//---
#include <boost/iostreams/concepts.hpp>
// other includes omitted for brevity ...
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_stream;
class ssl_iostream_sink : public sink {
public:
ssl_iostream_sink( ssl_stream *theStream )
{
stream = theStream;
}
std::streamsize write(const char* s, std::streamsize n)
{
// Write up to n characters to the underlying
// data sink into the buffer s, returning the
// number of characters written
boost::asio::write(*stream, boost::asio::buffer(s, n));
}
private:
ssl_stream *stream;
};
Теперь ваш цикл accept может измениться, чтобы выглядеть примерно так:
for(;;)
{
// Accept connection
ssl_stream stream(io_service, context);
tcp::endpoint peer_endpoint;
acceptor.accept(stream.lowest_layer(), peer_endpoint);
boost::system::error_code ec;
stream.handshake(boost::asio::ssl::stream_base::server, ec);
if (!ec) {
// wrap the ssl stream with iostream
ssl_iostream_sink my_sink(&stream);
boost::iostream::stream<ssl_iostream_sink> iostream_object(my_sink);
// Now it works the way you want...
iostream_object << HTTPReply(200, "Okely-Dokely\n") << std::flush;
}
}
Этот подход перехватывает поток ssl в инфраструктуру iostream. Итак, теперь вы можете сделать что-либо в iostream_object
в приведенном выше примере, что обычно делаете с любым другим std::ostream
(например, stdout). И материал, который вы ему пишете, будет записан в ssl_stream за кулисами. У Iostreams есть встроенная буферизация, поэтому некоторая степень буферизации будет происходить внутри, но это хорошо - она будет буферизоваться до тех пор, пока она не накопит достаточное количество данных, затем она выгрузит ее в поток ssl и вернитесь к буферизации. Окончательный std:: flush, должен заставить его очистить буфер от ssl_stream.
Если вам нужно больше контролировать внутреннюю буферизацию (или любые другие продвинутые вещи), посмотрите другой классный материал, доступный в boost::iostreams
. В частности, вы можете начать с просмотра stream_buffer
.
Удачи!
Ответ 2
Я думаю, что вы хотите использовать потоковые буферы (asio:: streambuf)
Затем вы можете сделать что-то вроде (непроверенный код, написанный на ходу):
boost::asio::streambuf msg;
std::ostream msg_stream(&msg);
msg_stream << "hello world";
msg_stream.flush();
boost::asio::write(stream, msg);
Аналогично, ваша сторона чтения/приема может считывать в буфер потока совместно с std:: istream, чтобы вы могли обрабатывать ваш вход с помощью различных функций/операторов потока.
Ссылка Asio для streambuf
Еще одно замечание: я думаю, вы должны ознакомиться с учебниками/примерами asio. Как только вы это сделаете, вы, вероятно, захотите изменить свой код на работу асинхронно, а не на синхронный пример, который вы показываете выше.
Ответ 3
ssl:: stream можно обернуть с помощью boost:: iostreams/twoirectional, чтобы имитировать подобное поведение как tcp:: iostream. вымывание выхода перед дальнейшим чтением не может быть предотвращено.
#include <regex>
#include <string>
#include <iostream>
#include <boost/iostreams/stream.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
namespace bios = boost::iostreams;
namespace asio = boost::asio;
namespace ssl = boost::asio::ssl;
using std::string;
using boost::asio::ip::tcp;
using boost::system::system_error;
using boost::system::error_code;
int parse_url(const std::string &s,
std::string& proto, std::string& host, std::string& path)
{
std::smatch m;
bool found = regex_search(s, m, std::regex("^(http[s]?)://([^/]*)(.*)$"));
if (m.size() != 4)
return -1;
proto = m[1].str();
host = m[2].str();
path = m[3].str();
return 0;
}
void get_page(std::iostream& s, const string& host, const string& path)
{
s << "GET " << path << " HTTP/1.0\r\n"
<< "Host: " << host << "\r\n"
<< "Accept: */*\r\n"
<< "Connection: close\r\n\r\n" << std::flush;
std::cout << s.rdbuf() << std::endl;;
}
typedef ssl::stream<tcp::socket> ssl_socket;
class ssl_wrapper : public bios::device<bios::bidirectional>
{
ssl_socket& sock;
public:
typedef char char_type;
ssl_wrapper(ssl_socket& sock) : sock(sock) {}
std::streamsize read(char_type* s, std::streamsize n) {
error_code ec;
auto rc = asio::read(sock, asio::buffer(s,n), ec);
return rc;
}
std::streamsize write(const char_type* s, std::streamsize n) {
return asio::write(sock, asio::buffer(s,n));
}
};
int main(int argc, char* argv[])
{
std::string proto, host, path;
if (argc!= 2 || parse_url(argv[1], proto, host, path)!=0)
return EXIT_FAILURE;
try {
if (proto != "https") {
tcp::iostream s(host, proto);
s.expires_from_now(boost::posix_time::seconds(60));
get_page(s, host, path);
} else {
asio::io_service ios;
tcp::resolver resolver(ios);
tcp::resolver::query query(host, "https");
tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
ssl::context ctx(ssl::context::sslv23);
ctx.set_default_verify_paths();
ssl_socket socket(ios, ctx);
asio::connect(socket.lowest_layer(), endpoint_iterator);
socket.set_verify_mode(ssl::verify_none);
socket.set_verify_callback(ssl::rfc2818_verification(host));
socket.handshake(ssl_socket::client);
bios::stream<ssl_wrapper> ss(socket);
get_page(ss, host, path);
}
} catch (const std::exception& e) {
std::cout << "Exception: " << e.what() << "\n";
}
}