gav*_*sen 24 c++ boost openssl iostream boost-asio
我正在使用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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
似乎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);
}
}
Run Code Online (Sandbox Code Playgroud)
Lee*_*Lee 15
@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
模板化你的新设备类型,然后离开.
它看起来像下面这个(这个例子是从这里改编的,另见这个相关的stackoverflow帖子):
//---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;
};
Run Code Online (Sandbox Code Playgroud)
现在,您的接受循环可能会更改为如下所示:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
该方法将ssl流挂钩到iostream框架中.所以现在你应该能够iostream_object
在上面的例子中做任何事情,你通常会做任何其他事情std::ostream
(比如stdout).你写给它的东西将被写入幕后的ssl_stream.Iostreams具有内置缓冲功能,因此内部会发生一定程度的缓冲 - 但这是一件好事 - 它会缓冲直到它累积了一些合理数量的数据,然后它会将它转储到ssl流上,并且回去缓冲.最后的std :: flush,应该强制它将缓冲区清空到ssl_stream.
如果您需要更多地控制内部缓冲(或任何其他高级内容),请查看其他可用的内容boost::iostreams
.具体来说,您可以从查看开始stream_buffer
.
祝好运!