如何创建 boost ssl iostream?
我正在为使用 boost tcp::iostream(充当 HTTP 服务器)进行输入和输出的代码添加 HTTPS 支持.
I'm adding HTTPS support to code that does input and output using boost tcp::iostream (acting as an HTTP server).
我找到了使用 boost::asio::read/boost::asio::write 进行 SSL 输入/输出的示例(并且有一个工作的玩具 HTTPS 服务器),但没有一个使用 iostreams 和 <<>> 操作员.如何将 ssl::stream 转换为 iostream?
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?
工作代码:
#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 << "
"
<< "Connection: close
"
<< "Content-Length: " << strMsg.size() << "
"
<< "Content-Type: application/json
"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT
"
<< "Server: json-rpc/1.0
"
<< "
"
<< 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
")));
// I really want to write:
// iostream_object << HTTPReply(200, "Okely-Dokely
") << std::flush;
}
}
}
似乎 ssl::stream_service 将是答案,但这是一个死胡同.
It seems like the ssl::stream_service would be the answer, but that is a dead end.
使用 boost::iostreams(如已接受的答案所建议)是正确的方法;这是我最终得到的工作代码:
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 << "
"
<< "Connection: close
"
<< "Content-Length: " << strMsg.size() << "
"
<< "Content-Type: application/json
"
<< "Date: Sat, 09 Jul 2009 12:04:08 GMT
"
<< "Server: json-rpc/1.0
"
<< "
"
<< strMsg;
return s.str();
}
void handle_request(std::iostream& s)
{
s << HTTPReply(200, "Okely-Dokely
") << 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);
}
}
推荐答案
@Guy 的建议(使用 boost::asio::streambuf
) 应该可以工作,而且它可能是最容易实现的.这种方法的主要缺点是,您写入 iostream 的所有内容都将缓冲在内存中,直到最后,当对 boost::asio::write()
的调用将转储立即缓冲到 ssl 流.(我应该注意到,在许多情况下,这种缓冲实际上是可取的,而在您的情况下,它可能根本没有区别,因为您已经说过这是一个低容量的应用程序).
@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 to boost::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).
如果这只是一次性",我可能会使用@Guy 的方法来实现它.
If this is just a "one-off" I would probably implement it using @Guy's approach.
话虽如此 -- 有很多很好的理由让您更愿意拥有一个允许您使用 iostream 调用直接写入您的 ssl_stream
的解决方案.如果您发现是这种情况,那么您需要构建您自己的包装类来扩展 std::streambuf
、覆盖 overflow()
和 sync()
(可能还有其他的,取决于您的需要).
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 extends std::streambuf
, overriding overflow()
, and sync()
(and maybe others depending on your needs).
幸运的是,boost::iostreams
提供了一种相对简单的方法来做到这一点,而无需直接处理 std 类.您只需构建自己的类来实现适当的 设备
合同.在这种情况下,这是 Sink
和 boost::iostreams::sink
类提供了一种方便的方式来获得大部分的方式.一旦你有一个新的 Sink 类封装了写入底层 ssl_stream 的过程,你所要做的就是创建一个 boost::iostreams::stream
模板化到你的新设备类型,和走吧.
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 appropriate Device
contract. In this case that's Sink
, and the boost::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 a boost::iostreams::stream
that is templated to your new device type, and off you go.
它将类似于以下内容(此示例改编自 此处,另见这篇相关的stackoverflow帖子a>):
It will look something like the following (this example is adapted from here, see also this related stackoverflow 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;
};
现在,您的接受循环可能会更改为如下所示:
Now, your accept loop might change to look something like this:
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
") << std::flush;
}
}
该方法将 ssl 流挂接到 iostream 框架中.所以现在你应该能够对上面例子中的 iostream_object
做任何事情,你通常会对任何其他 std::ostream
(如 stdout)做任何事情.您写入的内容将在幕后写入 ssl_stream.Iostreams 有内置缓冲,所以一定程度的缓冲会在内部发生――但这是一件好事――它会缓冲直到它积累了一些合理的数据量,然后它会将它转储到 ssl 流上,并且回到缓冲.最后的 std::flush,应该强制它把缓冲区清空到 ssl_stream.
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 other std::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.
如果您需要更多地控制内部缓冲(或任何其他高级内容),请查看 boost::iostreams
中提供的其他很酷的内容.具体来说,您可以先查看 stream_buffer
.
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 at stream_buffer
.
祝你好运!
相关文章