Comment créer un boost ssl iostream?

j'ajoute le support HTTPS au code qui fait des entrées et sorties en utilisant boost tcp::iostream (agissant comme un serveur HTTP).

j'ai trouvé des exemples (et j'ai un serveur HTTPS qui fonctionne) qui font des entrées/sorties SSL en utilisant boost::asio::read/boost::asio::write, mais aucun qui utilise iostream et les opérateurs<>. Comment transformer un flux ssl::en iostream?

code de travail:

#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 << "rn"
      << "Connection: closern"
      << "Content-Length: " << strMsg.size() << "rn"
      << "Content-Type: application/jsonrn"
      << "Date: Sat, 09 Jul 2009 12:04:08 GMTrn"
      << "Server: json-rpc/1.0rn"
      << "rn"
      << 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-Dokelyn")));
            // I really want to write:
            // iostream_object << HTTPReply(200, "Okely-Dokelyn") << std::flush;
        }
    }
}

il semble que le ssl::stream_service serait la réponse, mais c'est une impasse.

utiliser boost:: iostream (comme suggéré par réponse acceptée) est la bonne approche; voici le code de travail que j'ai fini par:

#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 << "rn"
      << "Connection: closern"
      << "Content-Length: " << strMsg.size() << "rn"
      << "Content-Type: application/jsonrn"
      << "Date: Sat, 09 Jul 2009 12:04:08 GMTrn"
      << "Server: json-rpc/1.0rn"
      << "rn"
      << strMsg;
    return s.str();
}


void handle_request(std::iostream& s)
{
    s << HTTPReply(200, "Okely-Dokelyn") << 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);
    }
}
24
demandé sur gavinandresen 2010-09-08 17:20:12

3 réponses

@Type la suggestion (à l'aide de boost::asio::streambuf ), et c'est probablement le plus simple à mettre en œuvre. Le principal inconvénient de cette approche est que tout ce que vous écrivez à l'iostream sera tamponné en mémoire jusqu'à la fin, lorsque l'appel à boost::asio::write() déchargera la totalité du contenu du tampon sur le flux ssl à la fois. (Je devrais noter que ce genre de buffering peut effectivement être souhaitable dans de nombreux cas, et dans votre cas, il ne fait probablement pas différence du tout puisque vous avez dit que c'est une application de faible volume).

S'il s'agit d'un" one-off " Je l'implémenterais probablement en utilisant l'approche de @Guy.

cela étant dit -- il y a un certain nombre de bonnes raisons pour lesquelles vous pourriez préférer une solution qui vous permet d'utiliser les appels iostream pour écrire directement dans votre ssl_stream . Si vous trouvez que c'est le cas, alors vous aurez besoin pour construire votre propre classe wrapper qui s'étend std::streambuf , substituant overflow() , et sync() (et peut-être d'autres selon vos besoins).

heureusement, boost::iostreams fournit un moyen relativement facile de le faire sans avoir à jouer avec les classes std directement. Vous venez de construire votre propre classe qui met en œuvre le Device contrat. Dans ce cas, c'est Sink , et la classe boost::iostreams::sink est fournie comme un moyen pratique d'y arriver la plupart du temps. Une fois que vous avez une nouvelle classe Sink qui encapsule le processus d'écriture à votre ssl_stream sous-jacent, tout ce que vous avez à faire est de créer un boost::iostreams::stream qui est Templé à votre nouveau type de périphérique, et vous partez.

Il ressemblera à quelque chose comme ce qui suit (cet exemple est adapté de ici , voir aussi cette 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;
};

maintenant, votre boucle d'acceptation pourrait changer pour ressembler à quelque chose comme ceci:

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;
    }
}

qui enferme le flux ssl dans le cadre iostream. Donc maintenant vous devriez être capable de faire n'importe quoi pour iostream_object dans l'exemple ci-dessus, que vous feriez normalement avec n'importe quel autre std::ostream (comme stdout). Et les choses que vous y écrivez seront écrites dans les coulisses de ssl_stream. Iostream a intégré un tampon, donc un certain degré de tampon prendre place en interne -- mais c'est une bonne chose -- il va amortir jusqu'à ce qu'il ait accumulé une certaine quantité raisonnable de données, puis il va le décharger sur le flux ssl, et revenir à la mise en tampon. La dernière std:: flush, devrait forcer à vider le tampon vers le ssl_stream.

si vous avez besoin de plus de contrôle sur le buffering interne (ou tout autre truc avancé), jetez un oeil à l'autre truc cool disponible dans boost::iostreams . Plus précisément, vous pourriez commencez par regarder stream_buffer .

bonne chance!

15
répondu Lee 2017-05-23 12:10:19

je pense que ce que vous voulez faire est d'utiliser le volet des tampons (asio::streambuf)

alors vous pouvez faire quelque chose comme (code non testé écrit sur le vol suit):

boost::asio::streambuf msg;
std::ostream msg_stream(&msg);
msg_stream << "hello world";
msg_stream.flush();
boost::asio::write(stream, msg);

de même votre côté lecture/réception peut lire dans un tampon de flux en conjonction avec std::istream afin que vous puissiez traiter votre entrée en utilisant diverses fonctions/opérateurs de flux.

référence Asio pour streambuf

une autre note est que je pense que vous devriez vérifier les tutoriels/exemples asio. Une fois que vous l'aurez fait, vous voudrez probablement modifier votre code pour fonctionner de manière asynchrone plutôt que l'exemple synchrone que vous montrez ci-dessus.

2
répondu Guy Sirton 2010-09-11 19:55:03

ssl::stream pourrait être enveloppé avec boost::iostreams / bidirectionnel pour imiter des comportements similaires comme tcp::iostream. le rinçage de sortie avant une autre lecture semble ne pas pouvoir être évité.

#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";
    }
}
1
répondu wkliang 2015-10-06 21:58:28