2010-09-08 9 views
23

Sto aggiungendo il supporto HTTPS al codice che immette input e output utilizzando boost tcp :: iostream (che funge da server HTTP).Come creare un iostream ssl boost?

Ho trovato esempi (e un server HTTPS giocattolo funzionante) che esegue l'input/output SSL utilizzando boost :: asio :: read/boost :: asio :: write, ma nessuno che utilizza iostreams e lo < < >> operatori. Come faccio a trasformare un flusso ssl :: in un iostream?

codice di lavoro:

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

Sembra che lo ssl :: stream_service sarebbe la risposta, ma che è un vicolo cieco.

Utilizzando boost :: iostreams (come suggerito dalla risposta accettata) è l'approccio giusto; ecco il codice di lavoro che ho finito con:

#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); 
    } 
} 
+0

perché si desidera utilizzare un iostream se i metodi di lettura e scrittura sincroni funzionano già? –

+0

Perché sto aggiungendo il supporto HTTPS al codice che parla già HTTP usando iostreams e voglio minimizzare la quantità di codice che cambio. – gavinandresen

+0

Probabilmente sarebbe più utile per te mostrarci il codice che è _NOT_ funzionante. – joshperry

risposta

14

@Guy suggerimento (utilizzando boost::asio::streambuf) dovrebbe funzionare, ed è probabilmente il più semplice da implementare. Lo svantaggio principale di questo approccio è che tutto ciò che scrivi sull'iostream verrà memorizzato nella memoria fino alla fine, quando la chiamata a scaricherà l'intero contenuto del buffer sullo streaming ssl in una sola volta. (Dovrei notare che questo tipo di buffering può effettivamente essere desiderabile in molti casi, e nel tuo caso probabilmente non fa alcuna differenza dal momento che hai detto che è un'applicazione a basso volume).

Se questo è solo un "one-off", probabilmente lo implementerei usando l'approccio di @ Guy.

Detto questo, ci sono una serie di buoni motivi per cui potresti preferire una soluzione che ti permetta di usare le chiamate iostream per scrivere direttamente nel tuo ssl_stream. Se si scopre che questo è il caso, è necessario creare la propria classe wrapper che si estende std::streambuf, ignorando overflow() e sync() (e forse altri in base alle proprie esigenze).

Fortunatamente, boost::iostreams fornisce un modo relativamente semplice per eseguire questa operazione senza dover fare confusione con le classi std direttamente. È sufficiente creare la propria classe che implementa il contratto Device appropriato. In questo caso è Sink e la classe boost::iostreams::sink viene fornita come un modo conveniente per ottenere la maggior parte del tragitto. Una volta che hai una nuova classe Sink che incapsula il processo di scrittura sul tuo ssl_stream sottostante, tutto ciò che devi fare è creare un boost::iostreams::stream che sia basato sul nuovo tipo di dispositivo e che tu vada.

Si avrà un aspetto simile al seguente (questo esempio è adattato da here, puoi anche this related 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; 
}; 

Ora, il ciclo di accettare potrebbe cambiare a guardare qualcosa di simile:

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

Questo approccio aggancia lo stream ssl alla struttura iostream. Quindi ora dovresti essere in grado di fare qualsiasi cosa su iostream_object nell'esempio precedente, che normalmente faresti con qualsiasi altro std::ostream (come stdout). E le cose che gli scriverai verranno scritte nel ssl_stream dietro le quinte. Iostreams ha un buffer incorporato, quindi un certo livello di buffering avverrà internamente - ma questa è una buona cosa - bufferizzerà fino a quando non avrà accumulato una quantità ragionevole di dati, quindi lo scaricherà sullo stream ssl, e torna al buffering. L'ultimo std :: flush, dovrebbe obbligarlo a svuotare il buffer verso ssl_stream.

Se hai bisogno di più controllo sul buffering interno (o su qualsiasi altra roba avanzata), dai uno sguardo alle altre fantastiche cose disponibili in boost::iostreams. In particolare, è possibile iniziare guardando stream_buffer.

Buona fortuna!

2

Penso che ciò che si vuole fare è buffer di uso di flusso (ASIO :: streambuf)

Poi si può fare qualcosa di simile (codice non testato scritto su al volo segue):

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

Allo stesso modo la vostra lettura/ricezione lato può leggere in un buffer flusso in combinazione con std :: istream in modo da poter elaborare il vostro input utilizzando varie funzioni di flusso/operatori.

Asio reference for streambuf

Un'altra nota è penso che si dovrebbe verificare le ASIO Tutorials/esempi. Una volta fatto, probabilmente vorrai cambiare il tuo codice per lavorare in modo asincrono piuttosto che nell'esempio sincrono che stai mostrando sopra.

+0

Potrei finire per farlo se non riesco a far funzionare un iostream abilitato per ssl. RE asincrono: no, il server è già in esecuzione in un thread separato e non deve gestire nemmeno decine di connessioni al minuto, quindi è meglio eseguire in modo sincrono. – gavinandresen

+0

Puoi anche provare basic_socket_iostream ma non penso che funzioni con SSL (lo fa con tcp, ip :: tcp :: iostream). Tuttavia potresti essere in grado di adattare il tuo flusso SSL per farcela. –

1

ssl :: stream può essere avvolto con boost :: iostreams/bidirezionale per simulare comportamenti simili a tcp :: iostream. l'uscita di flussaggio prima che ulteriori letture non possano essere evitate.

#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"; 
    } 
} 
Problemi correlati