Comment créer un boost ssl iostream?

J’ajoute la prise en charge HTTPS au code qui effectue les entrées et les sorties à l’aide de boost tcp :: iostream (agissant en tant que serveur HTTP).

J’ai trouvé des exemples (et un serveur HTTPS en état de fonctionnement) qui saisissent des entrées / sorties SSL à l’aide de boost :: asio :: read / boost :: asio :: write, mais qui n’utilisent ni iostreams ni les opérateurs <>. Comment transformer un stream ssl :: en un iostream?

Code de travail:

#include  #include  #include  #include  #include  #include  using namespace std; using namespace boost; using boost::asio::ip::tcp; typedef boost::asio::ssl::stream ssl_stream; ssortingng HTTPReply(int nStatus, const ssortingng& strMsg) { ssortingng 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"; ossortingngstream 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; } } } 

Il semble que ssl :: stream_service soit la solution, mais c’est une impasse.

Utiliser boost :: iostreams (comme suggéré par la réponse acceptée) est la bonne approche; voici le code de travail que j’ai fini avec:

 #include  #include  #include  #include  #include  #include  #include  using namespace boost::asio; typedef ssl::stream ssl_stream; // // IOStream device that speaks SSL but can also speak non-SSL // class ssl_iostream_device : public boost::iostreams::device { 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::ssortingng HTTPReply(int nStatus, const std::ssortingng& strMsg) { std::ssortingng 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::ossortingngstream 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(d); // Accept connection acceptor.accept(_ssl_stream.lowest_layer(), peer_endpoint); std::ssortingng method; std::ssortingng path; ssl_iostream >> method >> path; handle_request(ssl_iostream); } } 

La suggestion de @Guy (en utilisant boost::asio::streambuf ) devrait marcher et c’est probablement la plus facile à implémenter. Le principal inconvénient de cette approche est que tout ce que vous écrivez sur iostream sera mis en mémoire tampon jusqu’à la fin, moment où l’appel à boost::asio::write() dumpera le contenu entier de la mémoire tampon en une fois sur le stream ssl. . (Je devrais noter que ce type de mise en mémoire tampon peut être souhaitable dans de nombreux cas, et dans votre cas, cela ne fait probablement aucune différence puisque vous avez dit que c’était une application à faible volume).

S’il ne s’agit que d’un événement ponctuel, je le mettrais probablement en œuvre selon l’approche de @ Guy.

Cela étant dit, il existe un certain nombre de bonnes raisons pour lesquelles vous préféreriez peut-être une solution vous permettant d’utiliser les appels iostream pour écrire directement dans votre ssl_stream . Si vous trouvez que c’est le cas, vous devrez alors créer votre propre classe wrapper qui étend std::streambuf , en surchargeant overflow() et sync() (et peut-être d’autres en fonction de vos besoins).

Heureusement, boost::iostreams fournit un moyen relativement simple de le faire sans avoir à jouer directement avec les classes std. Vous venez de construire votre propre classe qui implémente le contrat de Device approprié. Dans ce cas, il s’agit de Sink , et la classe boost::iostreams::sink est fournie comme un moyen pratique d’en tirer le meilleur parti. Une fois que vous avez une nouvelle classe Sink qui encapsule le processus d’écriture sur votre ssl_stream sous-jacent, il vous suffit de créer un boost::iostreams::stream sur votre nouveau type d’appareil, et le tour est joué.

Cela ressemblera à quelque chose de ce qui suit (cet exemple est adapté ici , voir aussi cet article de stackoverflow associé ):

 //---this should be considered to be "pseudo-code", //---it has not been tested, and probably won't even comstack //--- #include  // other includes omitted for brevity ... typedef boost::asio::ssl::stream 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 à 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 iostream_object(my_sink); // Now it works the way you want... iostream_object << HTTPReply(200, "Okely-Dokely\n") << std::flush; } } 

Cette approche connecte le stream SSL à la structure iostream. Donc maintenant, vous devriez être capable de faire n'importe quoi à iostream_object dans l'exemple ci-dessus, ce que vous feriez normalement avec n'importe quel autre std::ostream (comme stdout). Et les trucs que vous écrivez vont être écrits dans les coulisses de ssl_stream. Iostreams a une mise en mémoire tampon intégrée, donc une certaine quantité de mise en mémoire tampon aura lieu en interne - mais c’est une bonne chose - elle restra en mémoire tampon jusqu’à ce qu’elle ait accumulé une quantité raisonnable de données, puis elle les affichera sur le stream SSL revenir à la mise en mémoire tampon. Le dernier std :: flush devrait le forcer à vider le tampon vers ssl_stream.

Si vous avez besoin de plus de contrôle sur la mise en mémoire tampon interne (ou sur tout autre élément avancé), consultez les autres éléments intéressants disponibles dans boost::iostreams . Plus précisément, vous pouvez commencer par regarder stream_buffer .

Bonne chance!

Je pense que ce que vous voulez faire est d’utiliser des tampons de stream (asio :: streambuf)

Ensuite, vous pouvez faire quelque chose comme (un code non testé écrit à la volée 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 stream en conjonction avec std :: istream afin que vous puissiez traiter votre entrée en utilisant divers opérateurs / fonctions de stream.

Référence Asio pour streambuf

Une autre note est que je pense que vous devriez consulter les tutoriels / exemples asio. Une fois que vous aurez terminé, vous voudrez probablement changer votre code pour qu'il fonctionne de manière asynchrone plutôt que l'exemple synchrone que vous montrez ci-dessus.

ssl :: stream pourrait être encapsulé avec boost :: iostreams / bidirectional pour imiter des comportements similaires à ceux de tcp :: iostream. rincer la sortie avant une lecture ultérieure semble inévitable.

 #include  #include  #include  #include  #include  #include  namespace bios = boost::iostreams; namespace asio = boost::asio; namespace ssl = boost::asio::ssl; using std::ssortingng; using boost::asio::ip::tcp; using boost::system::system_error; using boost::system::error_code; int parse_url(const std::ssortingng &s, std::ssortingng& proto, std::ssortingng& host, std::ssortingng& 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 ssortingng& host, const ssortingng& 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 ssl_socket; class ssl_wrapper : public bios::device { 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::ssortingng 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 ss(socket); get_page(ss, host, path); } } catch (const std::exception& e) { std::cout << "Exception: " << e.what() << "\n"; } }