2013-04-16 10 views
5

Quindi, lavorando su boost HTTP Server 3 example, desidero modificare la connessione :: handle_read per supportare l'invio di un corpo insieme al messaggio. Tuttavia, il metodo per farlo non è evidente per me. Voglio scrivere qualcosa di simile:Come posso scrivere un file su un socket usando il Protocollo di trasferimento HTTP "chunked" in boost :: asio?

void connection::handle_read(const boost::system::error_code& e, 
    std::size_t bytes_transferred) 
{ 
    ... 
if (result) 
    {  
     boost::asio::async_write(socket_, reply.to_buffers(), 
      strand_.wrap(
      boost::bind(&connection::write_body, shared_from_this(), 
       boost::asio::placeholders::error))); 
    } 
} 

void connection::write_body(const boost::system::error_code& e) 
{ 
    boost::asio::async_write(socket_, body_stream_, 
     strand_.wrap(
     boost::bind(&connection::handle_write, shared_from_this(), 
     boost::asio::placeholders::error))); 
} 

dove body_stream_ è un asio::windows::stream_handle.

Ma questo approccio non gestisce affatto il chunking http (tutto ciò significa che la dimensione del blocco viene inviata prima di ogni blocco). Qual è il modo migliore per affrontare questo problema? Scrivo il mio wrapper per un ifstream che rispetti i requisiti di un boost const buffer? Oppure provare a simulare l'effetto di async_write con più chiamate a async_write_some in un ciclo? Dovrei menzionare un requisito della soluzione è che non ho mai l'intero file in memoria in un dato momento - solo uno o pochi blocchi.

Molto nuovo per ASIO e prese, qualsiasi consiglio è apprezzato!

risposta

11

Potrebbe essere più semplice visualizzare la programmazione asincrona come una catena di funzioni anziché il ciclo. Quando si rompono le catene, trovo che sia utile interrompere le operazioni in due parti (iniziazione e completamento), quindi illustrare i potenziali percorsi di chiamata.Ecco un esempio illustrazione che legge asincrono alcuni dati da body_stream_, quindi scrive fuori presa via HTTP Chunked Transfer Encoding:

void connection::start() 
{ 
    socket.async_receive_from(..., &handle_read); --. 
}             | 
    .----------------------------------------------' 
    |  .-----------------------------------------. 
    V  V           | 
void connection::handle_read(...)     | 
{             | 
    if (result)          | 
    {             | 
    body_stream_.assign(open(...))     | 
                | 
    write_header(); --------------------------------|-----. 
    }             |  | 
    else if (!result)         |  | 
    boost::asio::async_write(..., &handle_write); --|--. | 
    else            | | | 
    socket_.async_read_some(..., &handle_read); ----' | | 
}              | | 
    .---------------------------------------------------' | 
    |              | 
    V              | 
void connection::handle_write()       | 
{}               | 
    .------------------------------------------------------' 
    | 
    V 
void connection::write_header() 
{ 
    // Start chunked transfer coding. Write http headers: 
    // HTTP/1.1. 200 OK\r\n 
    // Transfer-Encoding: chunked\r\n 
    // Content-Type: text/plain\r\n 
    // \r\n 
    boost::asio::async_write(socket_, ..., 
    &handle_write_header); --. 
} .-------------------------' 
    | 
    V 
void connection::handle_write_header(...) 
{ 
    if (error) return; 

    read_chunk(); --. 
} .-------------' 
    |  .--------------------------------------------. 
    V  V           | 
void connection::read_chunk()       | 
{              | 
    boost::asio::async_read(body_stream_, ...,   | 
    &handle_read_chunk); --.       | 
} .-----------------------'       | 
    |             | 
    V             | 
void connection::handle_read_chunk(...)     | 
{              | 
    bool eof = error == boost::asio::error::eof;   | 
                 | 
    // On non-eof error, return early.     | 
    if (error && !eof) return;       | 
                 | 
    write_chunk(bytes_transferred, eof); --.    | 
} .-------------------------------------'    | 
    |             | 
    V             | 
void connection::write_chunk(...)      | 
{              | 
    // Construct chunk based on rfc2616 section 3.6.1  | 
    // If eof has been reached, then append last-chunk. | 
    boost::asio::async_write(socket_, ...,    | 
    &handle_write_chunk); --.       | 
} .------------------------'       | 
    |             | 
    V             | 
void connection::handle_write_chunk(...)    | 
{              | 
    // If an error occured or no more data is available, | 
    // then return early.         | 
    if (error || eof) return;        | 
                 | 
    // Read more data from body_stream_.     | 
    read_chunk(); ---------------------------------------' 
} 

Come illustrato sopra, il chunking avviene tramite una catena asincrono, dove i dati vengono letti dalla body_stream_, preparato per la scrittura basata sulla specifica HTTP Chunked Transfer Encoding, quindi scritta sul socket. Se body_stream_ contiene ancora dati, si verifica un'altra iterazione.


Non ho un ambiente Windows per testare, ma ecco un esempio completo di base su Linux che blocchi di dati 10 byte alla volta.

#include <iostream> 
#include <sstream> 
#include <string> 
#include <vector> 

#include <boost/array.hpp> 
#include <boost/asio.hpp> 
#include <boost/bind.hpp> 

using boost::asio::ip::tcp; 
namespace posix = boost::asio::posix; 

// Constant strings. 
const std::string http_chunk_header = 
    "HTTP/1.1 200 OK\r\n" 
    "Transfer-Encoding: chunked\r\n" 
    "Content-Type: text/html\r\n" 
    "\r\n"; 
const char crlf[]  = { '\r', '\n' }; 
const char last_chunk[] = { '0', '\r', '\n' }; 

std::string to_hex_string(std::size_t value) 
{ 
    std::ostringstream stream; 
    stream << std::hex << value; 
    return stream.str(); 
} 

class chunk_connection 
{ 
public: 

    chunk_connection(
     boost::asio::io_service& io_service, 
     const std::string& pipe_name) 
    : socket_(io_service), 
     body_stream_(io_service), 
     pipe_name_(pipe_name) 
    {} 

    /// Get the socket associated with the connection 
    tcp::socket& socket() { return socket_; } 

    /// Start asynchronous http chunk coding. 
    void start(const boost::system::error_code& error) 
    { 
    // On error, return early. 
    if (error) 
    { 
     close(); 
     return; 
    } 

    std::cout << "Opening pipe." << std::endl; 
    int pipe = open(pipe_name_.c_str(), O_RDONLY); 
    if (-1 == pipe) 
    { 
     std::cout << "Failed to open pipe." << std::endl; 
     close(); 
     return; 
    } 

    // Assign native descriptor to Asio's stream_descriptor. 
    body_stream_.assign(pipe); 

    // Start writing the header. 
    write_header(); 
    } 

private: 

    // Write http header. 
    void write_header() 
    {  
    std::cout << "Writing http header." << std::endl; 

    // Start chunked transfer coding. Write http headers: 
    // HTTP/1.1. 200 OK\r\n 
    // Transfer-Encoding: chunked\r\n 
    // Content-Type: text/plain\r\n 
    // \r\n 
    boost::asio::async_write(socket_, 
     boost::asio::buffer(http_chunk_header), 
     boost::bind(&chunk_connection::handle_write_header, this, 
     boost::asio::placeholders::error)); 
    } 

    /// Handle writing of http header. 
    void handle_write_header(const boost::system::error_code& error) 
    { 
    // On error, return early. 
    if (error) 
    { 
     close(); 
     return; 
    } 

    read_chunk(); 
    } 

    // Read a file chunk. 
    void read_chunk() 
    { 
    std::cout << "Reading from body_stream_..."; 
    std::cout.flush(); 

    // Read body_stream_ into chunk_data_ buffer. 
    boost::asio::async_read(body_stream_, 
     boost::asio::buffer(chunk_data_), 
     boost::bind(&chunk_connection::handle_read_chunk, this, 
     boost::asio::placeholders::error, 
     boost::asio::placeholders::bytes_transferred)); 
    } 

    // Handle reading a file chunk. 
    void handle_read_chunk(const boost::system::error_code& error, 
         std::size_t bytes_transferred) 
    { 
    bool eof = error == boost::asio::error::eof; 

    // On non-eof error, return early. 
    if (error && !eof) 
    { 
     close(); 
     return; 
    } 

    std::cout << bytes_transferred << " bytes read." << std::endl; 
    write_chunk(bytes_transferred, eof); 
    } 

    // Prepare chunk and write to socket. 
    void write_chunk(std::size_t bytes_transferred, bool eof) 
    { 
    std::vector<boost::asio::const_buffer> buffers; 

    // If data was read, create a chunk-body. 
    if (bytes_transferred) 
    { 
     // Convert bytes transferred count to a hex string. 
     chunk_size_ = to_hex_string(bytes_transferred); 

     // Construct chunk based on rfc2616 section 3.6.1 
     buffers.push_back(boost::asio::buffer(chunk_size_)); 
     buffers.push_back(boost::asio::buffer(crlf)); 
     buffers.push_back(boost::asio::buffer(chunk_data_, bytes_transferred)); 
     buffers.push_back(boost::asio::buffer(crlf)); 
    } 

    // If eof, append last-chunk to outbound data. 
    if (eof) 
    { 
     buffers.push_back(boost::asio::buffer(last_chunk)); 
     buffers.push_back(boost::asio::buffer(crlf)); 
    } 

    std::cout << "Writing chunk..." << std::endl; 

    // Write to chunk to socket. 
    boost::asio::async_write(socket_, buffers, 
     boost::bind(&chunk_connection::handle_write_chunk, this, 
     boost::asio::placeholders::error, 
     eof)); 
    } 

    // Handle writing a chunk. 
    void handle_write_chunk(const boost::system::error_code& error, 
          bool eof) 
    { 
    // If eof or error, then shutdown socket and return. 
    if (eof || error) 
    { 
     // Initiate graceful connection closure. 
     boost::system::error_code ignored_ec; 
     socket_.shutdown(tcp::socket::shutdown_both, ignored_ec); 
     close(); 
     return; 
    } 

    // Otherwise, body_stream_ still has data. 
    read_chunk(); 
    } 

    // Close the socket and body_stream. 
    void close() 
    { 
    boost::system::error_code ignored_ec; 
    socket_.close(ignored_ec); 
    body_stream_.close(ignored_ec); 
    } 

private: 

    // Socket for the connection. 
    tcp::socket socket_; 

    // Stream file being chunked. 
    posix::stream_descriptor body_stream_; 

    // Buffer to read part of the file into. 
    boost::array<char, 10> chunk_data_; 

    // Buffer holds hex encoded value of chunk_data_'s valid size. 
    std::string chunk_size_; 

    // Name of pipe. 
    std::string pipe_name_; 
}; 

int main() 
{ 
    boost::asio::io_service io_service; 

    // Listen to port 80. 
    tcp::acceptor acceptor_(io_service, tcp::endpoint(tcp::v4(), 80)); 

    // Asynchronous accept connection. 
    chunk_connection connection(io_service, "example_pipe"); 
    acceptor_.async_accept(connection.socket(), 
    boost::bind(&chunk_connection::start, &connection, 
     boost::asio::placeholders::error)); 

    // Run the service. 
    io_service.run(); 
} 

ho un piccolo file html che verrà servito su codifica Chunked, 10 byte alla volta:

<html> 
<body> 
    Test transfering html over chunked encoding. 
</body> 
</html> 

Esecuzione server:

$ mkfifo example_pipe 
$ sudo ./a.out & 
[1] 28963 
<open browser and connected to port 80> 
$ cat html > example_pipe 

L'uscita del server:

Opening pipe. 
Writing http header. 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...10 bytes read. 
Writing chunk... 
Reading from body_stream_...7 bytes read. 
Writing chunk...

The wiresha Uscita rk non mostra-malformati dati:

0000 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK. 
0010 0a 54 72 61 6e 73 66 65 72 2d 45 6e 63 6f 64 69 .Transfe r-Encodi 
0020 6e 67 3a 20 63 68 75 6e 6b 65 64 0d 0a 43 6f 6e ng: chun ked..Con 
0030 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f tent-Typ e: text/ 
0040 68 74 6d 6c 0d 0a 0d 0a 61 0d 0a 3c 68 74 6d 6c html.... a..<html 
0050 3e 0a 3c 62 6f 0d 0a 61 0d 0a 64 79 3e 0a 20 20 >.<bo..a ..dy>. 
0060 54 65 73 74 0d 0a 61 0d 0a 20 74 72 61 6e 73 66 Test..a. . transf 
0070 65 72 69 0d 0a 61 0d 0a 6e 67 20 68 74 6d 6c 20 eri..a.. ng html 
0080 6f 76 0d 0a 61 0d 0a 65 72 20 63 68 75 6e 6b 65 ov..a..e r chunke 
0090 64 0d 0a 61 0d 0a 20 65 6e 63 6f 64 69 6e 67 2e d..a.. e ncoding. 
00a0 0d 0a 61 0d 0a 0a 3c 2f 62 6f 64 79 3e 0a 3c 0d ..a...</ body>.<. 
00b0 0a 37 0d 0a 2f 68 74 6d 6c 3e 0a 0d 0a 30 0d 0a .7../htm l>...0.. 
00c0 0d 0a            ..    
+0

Buona descrizione! È molto simile a quello che ho trovato, anche se nel mio caso ho bisogno dell'intero messaggio http prima di poter iniziare a rispondere, poiché la risposta dipende dal messaggio. – Rollie

0

l'esempio è molto semplice, solo per mostrarvi come richiedere semplicemente HTTP request.Chunked Transfer Encoding non è supportato per questo esempio. qualche suggerimento per voi:

  • per imparare che cosa è la codifica di trasferimento Chunked, che si possono trovare in RFC2616, sezione 3.6.
  • fare qualcosa prima di inviare: impostare le intestazioni HTTP per indicare che il messaggio di raccolta utilizzando Chunked Transfer Encoding; codifica i dati con Chunked Transfer Encoding.

la logica sarà simile a questo:

std::string http_head; 
std::string http_body; 

char buff[10000]; 
read_file_to_buff(buff); 
set_http_head_values(http_head); 
encode_chunk_format(buff,http_body); 


boost::asio::async_write(socket_, 
    boost::asio::buffer(http_head.c_str(), http_head.length()), 
    boost::bind(&connection::handle_write, shared_from_this(), 
    boost::asio::placeholders::error); 

boost::asio::async_write(socket_, 
    boost::asio::buffer(http_body.c_str(), http_body.length()), 
    boost::bind(&connection::handle_write, shared_from_this(), 
    boost::asio::placeholders::error); 

quando si prova il vostro programma, è possibile utilizzare per monitorare Fiddler2 messaggio HTTP.

+0

faccio capire la codifica di trasferimento pezzo e hanno implementato una versione di lavoro - il problema che ho è che utilizzando le 'get_headers()' logica, ho bisogno di caricare l'intero file in memoria prima che venga inoltrato a async_send. Quello che voglio fare è leggere durante l'invio, quindi non devo mai caricare la totalità di un grande file in memoria. Inoltre, quanto sopra non funzionerebbe, perché se ho letto correttamente, non è possibile chiamare 2 async_writes in modo sincrono; invece, lo chiameresti dalla funzione handle_write. Sto cercando una soluzione semplice/elegante per il problema della memoria da qualcuno più familiare con asio – Rollie

0

Quindi la soluzione che ho trovato è di avere 2 funzioni di scrittura - write_body() e write_complete(). Una volta che la lettura è fatto, assumendo abbiamo un corpo di inviare, abbiamo chiamare

body_fp_.open(bodyFile); 
async_write(get_headers(), write_body) 

all'interno write_body, facciamo qualcosa di simile

vector<boost::asio::const_buffer> buffers; 
body_fp_.read(buffer_, buffer_.size()) 
buffers.push_back(...size, newlines, etc...); 
buffers.push_back(buffer_, body_fp_.gcount()); 

e una volta terminato di scrivere il contenuto del file, scrivere un tempo finale con :

boost::asio::async_write(socket_, boost::asio::buffer(misc_strings::eob), 
      strand_.wrap(
      boost::bind(&connection::write_complete, shared_from_this(), 
      boost::asio::placeholders::error))); 

Questo sembra funzionare piuttosto bene e ha alleviato i problemi relativi all'utilizzo della memoria. Qualsiasi commento su questa soluzione è apprezzato; speriamo che questo possa aiutare qualcun altro con un problema simile in futuro.

Problemi correlati