Как создать буст ssl iostream? - PullRequest
24 голосов
/ 08 сентября 2010

Я добавляю поддержку HTTPS к коду, который выполняет ввод и вывод, используя boost tcp :: iostream (действующий как HTTP-сервер).

Я нашел примеры (и работающий игрушечный HTTPS-сервер), которые выполняют ввод / вывод SSL с использованием boost :: asio :: read / boost :: asio :: write, но ни один, в котором используются 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);
    }
}

Ответы [ 3 ]

15 голосов
/ 12 сентября 2010
Предложение

@ 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, который настроен на ваш новый тип устройства, и все готово.

Itбудет выглядеть примерно так (этот пример адаптирован из здесь , см. также это сообщение о соответствующем стеке ):

//---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 голосов
/ 11 сентября 2010

Я думаю, что вы хотите использовать потоковые буферы (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 для streambuf

Еще одно замечание: я думаю, вы должны проверить учебники / примеры asio. Когда вы это сделаете, вы, вероятно, захотите изменить свой код так, чтобы он работал асинхронно, а не как синхронный пример, который вы демонстрируете выше.

1 голос
/ 07 октября 2015

ssl :: stream может быть упакован с boost :: iostreams / двунаправленным, чтобы имитировать поведение, подобное 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";
    }
}
...