2010-09-08 18 views
23

Dodaję obsługę HTTPS do kodu, który wykonuje dane wejściowe i wyjściowe za pomocą funkcji boost tcp :: iostream (działającej jako serwer HTTP).Jak utworzyć doładowanie ssl iostream?

Znalazłem przykłady (i mam działający serwer HTTPS z zabawkami), które obsługują wejścia/wyjścia SSL za pomocą funkcji boost :: asio :: read/boost :: asio :: write, ale żadne nie używają iostreams i < < >> operatorzy. Jak zmienić strumień ssl :: stream w strumień?

kod robocza:

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

Wydaje się, że SSL :: stream_service byłaby odpowiedź, ale to ślepy zaułek.

Używanie boost :: iostreams (zgodnie z sugestią przyjętej odpowiedzi) jest właściwym podejściem; oto kod roboczych ja skończyło się z:

#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

dlaczego chcesz używać iostream jeśli synchroniczne metody odczytu i zapisu już działają? –

+0

Ponieważ dodam obsługę HTTPS do kodu, który już mówi HTTP za pomocą iostreams, i chcę zminimalizować ilość kodu, który zmienię. – gavinandresen

+0

Prawdopodobnie lepiej byłoby pokazać nam kod, który _NOT_ działa. – joshperry

Odpowiedz

14
Propozycja

@Guy (przy użyciu boost::asio::streambuf) powinna zadziałać i prawdopodobnie jest najłatwiejsza do wdrożenia. Główną wadą tego podejścia jest to, że wszystko, co napiszesz do iostream, będzie buforowane w pamięci do końca, kiedy wywołanie boost::asio::write() spowoduje zrzucenie całej zawartości bufora na strumień ssl na raz. (Powinienem zauważyć, że tego rodzaju buforowanie może być w wielu przypadkach pożądane, aw twoim przypadku prawdopodobnie nie ma żadnej różnicy, ponieważ powiedzieliście, że jest to aplikacja o małej objętości).

Jeśli jest to tylko "jednorazowe", prawdopodobnie zaimplementowałem to za pomocą metody @ Guya.

W związku z tym - istnieje wiele dobrych powodów, dla których wolałbyś mieć rozwiązanie, które pozwala używać wywołań iostream do pisania bezpośrednio do twojego ssl_stream. Jeśli okaże się, że tak jest, musisz zbudować własną klasę otoki, która rozszerza się o std::streambuf, zastępując overflow() i sync() (i być może inne w zależności od Twoich potrzeb).

Na szczęście, boost::iostreams zapewnia stosunkowo łatwy sposób, aby to zrobić bez konieczności bałagan z klas bezpośrednio. Po prostu budujesz własną klasę, która implementuje odpowiednią umowę Device. W tym przypadku jest to Sink, a klasa boost::iostreams::sink jest wygodnym sposobem, aby uzyskać większość drogi. Gdy masz już nową klasę Sink, która zawiera proces zapisywania do bazowego strumienia ssl, wystarczy utworzyć szablon boost::iostreams::stream, który jest szablonem do nowego typu urządzenia, i gotowe.

będzie wyglądać następująco (tym przykładzie jest adaptacją here patrz również 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; 
}; 

Teraz Twój zaakceptować pętla może się zmienić, aby wyglądać tak:

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

To podejście powoduje przechwycenie strumienia ssl do frameworka iostream. Teraz powinieneś być w stanie zrobić wszystko, aby iostream_object w powyższym przykładzie, że normalnie zrobiłbyś z każdym innym std::ostream (jak stdout). A rzeczy, które do niego napiszesz, zostaną zapisane w ssl_stream za kulisami. Iostreams ma wbudowane buforowanie, więc pewien stopień buforowania będzie odbywał się wewnętrznie - ale to dobrze - będzie buforował, dopóki nie zgromadzi jakiejś rozsądnej ilości danych, wtedy zrzuci go w strumieniu ssl, a wróć do buforowania. Ostateczny std :: flush, powinien zmusić go do opróżnienia bufora do strumienia ssl_stream.

Jeśli potrzebujesz większej kontroli nad wewnętrznym buforowaniem (lub innymi zaawansowanymi funkcjami), zobacz inne fajne rzeczy dostępne w boost::iostreams. W szczególności możesz zacząć od spojrzenia na stream_buffer.

Powodzenia!

2

myślę, co chcesz zrobić, to bufory stosować strumienia (asio :: streambuf)

Następnie można zrobić coś takiego (kod nietestowanego pisemnej na mucha następująco):

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

Podobnie Twój odczytu/odbierać strona może odczytać w buforze strumienia w połączeniu z std :: istream więc można przetworzyć za pomocą różnych funkcji wejścia/strumień operatorów.

Asio reference for streambuf

Inna uwaga to myślę, że należy sprawdzić ASIO samouczki/przykłady. Gdy to zrobisz, prawdopodobnie będziesz chciał zmienić swój kod tak, aby działał asynchronicznie, a nie na przykładzie synchronicznym, który pokazano powyżej.

+0

Może się to skończyć, jeśli nie będę w stanie obsłużyć funkcji iostream z włączoną obsługą SSL. RE asynchroniczny: nie, serwer działa już w osobnym wątku i nie musi obsługiwać nawet kilkudziesięciu połączeń na minutę, więc działa synchronicznie lepiej. – gavinandresen

+0

Możesz także wypróbować basic_socket_iostream, ale nie sądzę, że działa z SSL (działa z tcp, ip :: tcp :: iostream). Możesz jednak dostosować swój strumień ssl do pracy z nim. –

1

ssl :: stream może być zawijany z boost :: iostreams/bidirectional w celu naśladowania podobnych zachowań, jak tcp :: iostream. nie można uniknąć wypierania przed dalszym czytaniem.

#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"; 
    } 
} 
Powiązane problemy