如何创建一个提升ssl iostream?

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.

祝好运!