如何创建 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&lt;ssl&gt; 提供专门化(它包装了现有的ssl::contextssl::stream&lt;ip::tcp::socket&gt;)——***.com/a/57341216/1433768。由于basic_socket_streambuf&lt;&gt;::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库,如何获取相邻节点?

多线程应用程序上的 boost::asio::ssl 访问冲突

boost::asio::ssl::context 可以在多个 SSL 流之间共享吗?

如何优雅地关闭 boost asio ssl 客户端?

在 Boost 中编译 iostreams 库时更改 boost_zlib DLL 名称?

如何在 Eclipse 中使用 MinGW 编译 boost 线程?