如何创建 boost ssl iostream?
Posted
技术标签:
【中文标题】如何创建 boost ssl iostream?【英文标题】:How to create a boost ssl iostream? 【发布时间】:2011-04-09 17:57:41 【问题描述】:我正在向使用 boost tcp::iostream(充当 HTTP 服务器)进行输入和输出的代码添加 HTTPS 支持。
我找到了使用 boost::asio::read/boost::asio::write 进行 SSL 输入/输出的示例(并且有一个工作玩具 HTTPS 服务器),但没有一个使用 iostreams 和 > 运营商。如何将 ssl::stream 转换为 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);
【问题讨论】:
如果同步读写方法已经有效,为什么还要使用 iostream? 因为我正在使用 iostreams 向已经使用 HTTP 的代码添加 HTTPS 支持,并且我想尽量减少我更改的代码量。 向我们展示不工作的代码可能对您更有用。 问题不在于让代码工作——它正在干净地集成到现有代码中,而没有完全重写它。 “真实”代码是:github.com/gavinandresen/bitcoin-git/blob/master/rpc.cpp#L1280 这段代码的简洁性太棒了 【参考方案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
,它被模板化为你的新设备类型,然后就可以开始了。
它看起来像下面这样(这个例子改编自here,另见this related *** post):
//---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;
;
现在,您的接受循环可能会变成如下所示:
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 reference for streambuf
另一个注意事项是我认为您应该查看 asio 教程/示例。完成后,您可能希望将代码更改为异步工作,而不是上面显示的同步示例。
【讨论】:
如果我无法让 ssl-enabled-iostream 工作,我可能最终会这样做。 RE 异步:不,服务器已经在单独的线程中运行,并且每分钟不必处理数十个连接,因此同步运行更好。 您也可以尝试 basic_socket_iostream 但我认为它不适用于 SSL(它适用于 tcp、ip::tcp::iostream)。不过,您也许可以调整您的 ssl 流以使用它。 我已经尝试使用现有的basic_socket_iostream
系统,方法是为basic_socket<ssl>
提供专门化(它包装了现有的ssl::context
和ssl::stream<ip::tcp::socket>
)——***.com/a/57341216/1433768。由于basic_socket_streambuf<>::connect_to_endpoints()
中现有的抽象中断,没有完全解决,但看起来非常整洁和有前途。【参考方案3】:
ssl::stream 可以用 boost::iostreams / bidirectional 包装以模仿与 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";
【讨论】:
以上是关于如何创建 boost ssl iostream?的主要内容,如果未能解决你的问题,请参考以下文章
多线程应用程序上的 boost::asio::ssl 访问冲突
boost::asio::ssl::context 可以在多个 SSL 流之间共享吗?