2010-09-08 17 views
23

Estoy agregando soporte HTTPS al código que ingresa y emite usando boost tcp :: iostream (actuando como un servidor HTTP).Cómo crear un impulso ssl iostream?

He encontrado ejemplos (y tengo un servidor HTTPS de juguete que funciona) que hacen entrada/salida SSL usando boost :: asio :: read/boost :: asio :: write, pero ninguno que use iostreams y < < >> operadores. ¿Cómo convierto un ssl :: stream en un iostream?

código de trabajo:

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

Parece que el SSL :: stream_service sería la respuesta, pero que es un callejón sin salida.

El uso de boost :: iostreams (como lo sugiere la respuesta aceptada) es el enfoque correcto; Aquí está el código de trabajo he terminado 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

¿Por qué desea utilizar un iostream si los métodos de lectura y escritura síncronos ya funcionan? –

+0

Porque estoy agregando soporte HTTPS al código que ya habla HTTP usando iostreams, y quiero minimizar la cantidad de código que cambio. – gavinandresen

+0

Probablemente sea más útil que nos muestre el código que NO funciona. – joshperry

Respuesta

14

@Guy sugerencia (usando boost::asio::streambuf) debería funcionar, y probablemente sea la más fácil de implementar. El principal inconveniente de este enfoque es que todo lo que escriba en el iostream se almacenará en la memoria hasta el final, cuando la llamada al boost::asio::write() arrojará todo el contenido del búfer a la secuencia ssl de una vez. (Debo señalar que este tipo de almacenamiento en búfer puede ser realmente deseable en muchos casos, y en su caso probablemente no haga ninguna diferencia ya que ha dicho que es una aplicación de bajo volumen).

Si esto es solo una "excepción", probablemente lo implementaría utilizando el enfoque de @ Guy.

Dicho esto, hay una serie de buenas razones por las que quizás prefiera tener una solución que le permita utilizar las llamadas iostream para escribir directamente en su ssl_stream. Si encuentra que este es el caso, tendrá que crear su propia clase contenedora que extienda std::streambuf, anulando overflow() y sync() (y tal vez otros dependiendo de sus necesidades).

Afortunadamente, boost::iostreams proporciona una forma relativamente fácil de hacerlo sin tener que meterse directamente con las clases estándar. Usted acaba de construir su propia clase que implementa el contrato apropiado Device. En este caso, es Sink, y la clase boost::iostreams::sink se proporciona como una manera conveniente de obtener la mayor parte del camino hasta allí. Una vez que tiene una nueva clase de Sink que encapsula el proceso de escritura en su ssl_stream subyacente, todo lo que tiene que hacer es crear un boost::iostreams::stream que está configurado como plantilla para su nuevo tipo de dispositivo, y listo.

Se verá algo como lo siguiente (en este ejemplo es una adaptación de here, consulta 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; 
}; 

Ahora, el bucle aceptar podría cambiar a ser algo como esto:

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

Ese enfoque engancha la secuencia de ssl en el marco iostream. Así que ahora debería poder hacer cualquier cosa al iostream_object en el ejemplo anterior, que normalmente haría con cualquier otra std::ostream (como stdout). Y las cosas que escribes se escribirán en el ssl_stream detrás de las escenas. Iostreams tiene un búfer incorporado, por lo que un cierto grado de almacenamiento en memoria intermedia tendrá lugar internamente, pero esto es bueno, amortiguará hasta que haya acumulado una cantidad razonable de datos, y luego lo vaciará en la secuencia ssl, y volver al almacenamiento en búfer. El std :: flush final, debe obligarlo a vaciar el búfer al ssl_stream.

Si necesita más control sobre el almacenamiento en búfer interno (o cualquier otra cosa avanzada), eche un vistazo a las otras cosas interesantes disponibles en boost::iostreams. Específicamente, puede comenzar mirando stream_buffer.

¡Buena suerte!

2

Creo que lo que quiere hacer es buffers de uso corriente (asio :: streambuf)

A continuación, puede hacer algo como (código no probado escrito en la marcha sigue):

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

del mismo modo que su lectura/lado de recepción puede leer en un búfer de la secuencia en conjunto con std :: istream para que pueda procesar su entrada utilizando diversas funciones secuencia/operadores.

Asio reference for streambuf

Otra nota es que creo que debe salir los tutoriales ASIO/ejemplos. Una vez que lo haga, probablemente desee cambiar el código para que funcione de forma asíncrona en lugar del ejemplo sincrónico que muestra arriba.

+0

Podría terminar haciéndolo si no puedo hacer funcionar un ssl-enabled-iostream. RE asíncrono: no, el servidor ya se ejecuta en un subproceso separado y no tiene que manejar incluso decenas de conexiones por minuto, por lo que ejecutar de forma sincrónica es mejor. – gavinandresen

+0

También puedes probar basic_socket_iostream, pero no creo que funcione con SSL (lo hace con tcp, ip :: tcp :: iostream). Sin embargo, es posible que pueda adaptar su secuencia de ssl para que funcione. –

1

ssl :: stream podría incluirse con boost :: iostreams/bidirectional para imitar comportamientos similares como tcp :: iostream. no se puede evitar la salida de enjuague antes de seguir leyendo.

#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"; 
    } 
} 
Cuestiones relacionadas