如何创建 boost ssl iostream?
我正在向使用 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);
}
}
I'm adding HTTPS support to code that does input and output using boost tcp::iostream (acting as an HTTP server).
I've found examples (and have a working toy HTTPS server) that do SSL input/output using boost::asio::read/boost::asio::write, but none that use iostreams and the << >> operators. How do I turn an ssl::stream into an iostream?
Working code:
#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;
}
}
}
It seems like the ssl::stream_service would be the answer, but that is a dead end.
Using boost::iostreams (as suggested by accepted answer) is the right approach; here's the working code I've ended up with:
#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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
@Guy 的建议(使用
boost::asio::streambuf
)应该有效,并且这可能是最容易实现的。该方法的主要缺点是,写入 iostream 的所有内容都将缓冲在内存中,直到最后,调用boost::asio::write()
将转储 iostream 的全部内容。立即缓冲到 ssl 流上。 (我应该指出,这种缓冲实际上在许多情况下都是可取的,而在您的情况下,它可能根本没有区别,因为您已经说过这是一个低容量的应用程序)。如果这只是“一次性”,我可能会使用@Guy 的方法来实现它。
话虽这么说,但有很多充分的理由表明您可能更愿意拥有一个允许您使用 iostream 调用直接写入
ssl_stream
的解决方案。如果您发现情况如此,那么您需要构建自己的包装类来扩展std::streambuf
、重写overflow()
和sync()
(也许还有其他取决于您的需要)。幸运的是,
boost::iostreams
提供了一种相对简单的方法来执行此操作,而不必直接使用 std 类。您只需构建自己的类来实现适当的设备
合同。在本例中,这是Sink
,并且
boost::iostreams::sink
类是作为一种便捷的方式提供的。一旦您有了一个新的 Sink 类来封装写入底层 ssl_stream 的过程,您所要做的就是创建一个针对您的新设备类型进行模板化的boost::iostreams::stream
,并且你走吧。它看起来像下面这样(这个例子改编自 此处,另请参阅此相关的 stackoverflow 帖子) :
现在,您的接受循环可能会更改为如下所示:
该方法将 ssl 流挂接到 iostream 框架中。因此,现在您应该能够对上面示例中的 iostream_object 执行任何操作,就像您通常对任何其他 std::ostream (例如 stdout)执行的操作一样。您写入的内容将在幕后写入 ssl_stream 中。 Iostreams 具有内置缓冲,因此内部会进行一定程度的缓冲 - 但这是一件好事 - 它将缓冲直到积累了一定数量的数据,然后将其转储到 ssl 流上,并且返回缓冲。最后的 std::flush,应该强制它将缓冲区清空到 ssl_stream。
如果您需要对内部缓冲(或任何其他高级内容)进行更多控制,请查看
boost::iostreams
中提供的其他很酷的内容。具体来说,您可以首先查看stream_buffer
。祝你好运!
@Guy's suggestion (using
boost::asio::streambuf
) should work, and it's probably the easiest to implement. The main drawback to that approach is that everything you write to the iostream will be buffered in memory until the end, when the call toboost::asio::write()
will dump the entire contents of the buffer onto the ssl stream at once. (I should note that this kind of buffering can actually be desirable in many cases, and in your case it probably makes no difference at all since you've said it's a low-volume application).If this is just a "one-off" I would probably implement it using @Guy's approach.
That being said -- there are a number of good reasons that you might rather have a solution that allows you to use iostream calls to write directly into your
ssl_stream
. If you find that this is the case, then you'll need to build your own wrapper class that extendsstd::streambuf
, overridingoverflow()
, andsync()
(and maybe others depending on your needs).Fortunately,
boost::iostreams
provides a relatively easy way to do this without having to mess around with the std classes directly. You just build your own class that implements the appropriateDevice
contract. In this case that'sSink
, and theboost::iostreams::sink
class is provided as a convenient way to get most of the way there. Once you have a new Sink class that encapsulates the process of writing to your underlying ssl_stream, all you have to do is create aboost::iostreams::stream
that is templated to your new device type, and off you go.It will look something like the following (this example is adapted from here, see also this related stackoverflow post):
Now, your accept loop might change to look something like this:
That approach hooks the ssl stream into the iostream framework. So now you should be able to do anything to
iostream_object
in the above example, that you would normally do with any otherstd::ostream
(like stdout). And the stuff that you write to it will get written into the ssl_stream behind the scenes. Iostreams has built-in buffering, so some degree of buffering will take place internally -- but this is a good thing -- it will buffer until it has accumulated some reasonable amount of data, then it will dump it on the ssl stream, and go back to buffering. The final std::flush, should force it to empty the buffer out to the ssl_stream.If you need more control over internal buffering (or any other advanced stuff), have a look at the other cool stuff available in
boost::iostreams
. Specifically, you might start by looking atstream_buffer
.Good luck!
我认为你想要做的是使用流缓冲区(asio::streambuf)
然后你可以做类似的事情(下面是即时编写的未经测试的代码):
类似地,你的读取/接收端可以与std结合读入流缓冲区: :istream,这样您就可以使用各种流函数/运算符来处理您的输入。
streambuf 的 Asio 参考
我认为你应该查看 asio 教程/示例。一旦完成,您可能希望将代码更改为异步工作,而不是上面显示的同步示例。
I think what you want to do is use stream buffers (asio::streambuf)
Then you can do something like (untested code written on the fly follows):
Similarly your read/receive side can read into a stream buffer in conjunction with std::istream so you can process your input using various stream functions/operators.
Asio reference for streambuf
Another note is I think you should check out the asio tutorials/examples. Once you do you'll probably want to change your code to work asynchronously rather than the synchronous example you're showing above.
ssl::stream 可以用 boost::iostreams / bidirection 包装,以模仿与 tcp::iostream 类似的行为。在进一步阅读之前刷新输出似乎是不可避免的。
ssl::stream could be wrapped with boost::iostreams / bidirectional to mimic similar behaviours as tcp::iostream. flushing output before further reading seems cannot be avoided.