2013-04-16 3 views
5

Also von der boost HTTP Server 3 example arbeiten, möchte ich ändern Verbindung :: handle_read zu unterstützen Senden eines Körpers zusammen mit der Nachricht. Die Methode dafür ist mir jedoch nicht ersichtlich. Ich möchte wie etwas schreiben:Wie kann ich eine Datei mit dem 'chunked' HTTP Transfer-Protokoll in boost :: asio in einen Socket schreiben?

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

wo body_stream_ ist ein asio::windows::stream_handle.

Aber dieser Ansatz behandelt das http-Chunking überhaupt nicht (das bedeutet, dass die Größe des Chunks vor jedem Chunk gesendet wird). Was ist der beste Weg, dieses Problem anzugehen? Schreibe ich meinen eigenen Wrapper für einen ifstream, der den Anforderungen eines boost const buffer entspricht? Oder versuchen Sie, den Effekt von async_write mit mehreren Aufrufen an async_write_some in einer Schleife zu simulieren? Ich sollte eine Anforderung der Lösung erwähnen ist, dass ich nie die gesamte Datei im Speicher zu einem bestimmten Zeitpunkt habe - nur ein oder ein paar Brocken.

Sehr neu zu ASIO und Steckdosen, jeder Rat wird geschätzt!

Antwort

11

Es mag einfacher sein, die asynchrone Programmierung als eine Kette von Funktionen anstatt als Schleife zu visualisieren. Wenn ich die Ketten auseinanderreiße, finde ich es hilfreich, die Operationen in zwei Teile zu zerlegen (Initiierung und Vervollständigung) und dann die möglichen Anrufwege zu illustrieren.Hier ist ein Beispiel Abbildung, die asynchron einige Daten aus body_stream_ liest, dann schreibt es aus der Steckdose über 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(); ---------------------------------------' 
} 

Wie oben dargestellt, das Chunking über eine asynchrone Kette erfolgt, wo Daten von body_stream_ gelesen wird, vorbereitet zum Schreiben basierend auf der HTTP Chunked Transfer Encoding-Spezifikation, dann in den Socket geschrieben. Wenn body_stream_ noch Daten enthält, tritt eine weitere Iteration auf.


Ich habe keine Windows-Umgebung habe auf testen, aber hier ist ein einfaches vollständiges Beispiel auf Linux, dass Chunks Daten 10 Byte zu einem Zeitpunkt.

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

Ich habe eine kleine HTML-Datei, die über aufgeteilte Codierung, 10 Bytes zu einer Zeit serviert wird:

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

Lauf Server

:

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

Der Ausgang des Servers:

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...

Die wiresha rk Ausgangs zeigt keine-fehlerhafte Daten:

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

Gute Beschreibung! Es ist sehr ähnlich zu dem, was ich am Ende hatte, obwohl ich in meinem Fall die gesamte HTTP-Nachricht brauche, bevor ich antworten kann, da die Antwort von der Nachricht abhängt. – Rollie

0

Das Beispiel ist sehr einfach, nur um Ihnen zu zeigen, wie HTTP-Anfrage einfach zu handhaben. Chunked Transfer Encoding ist keine Unterstützung für dieses Beispiel. einige Vorschlag für Sie:

  • zu erfahren, was das Encoding Chunked Transfer ist, können Sie es in RFC2616 gefunden, wird im Abschnitt 3.6.
  • etwas vor dem Senden tun: setzen HTTP-Header, um anzuzeigen, dass die Reapons Nachricht mit Chunked Transfer Encoding; codieren Sie Ihre Daten mit Chunked Transfer Encoding.

die Logik wird wie folgt sein:

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

wenn Sie Ihr Programm testen, können Sie Fiddler2 verwenden http Nachricht zu überwachen.

+0

verstehe ich die Chunk-Transfer-Codierung und haben eine funktionierende Version implementiert - das Problem, das ich habe ist, dass die ‚get_headers()‘ Logik, muss ich die gesamte Datei laden in den Speicher, bevor es an async_send weitergeleitet wird. Ich möchte während des Sendens lesen, sodass ich nie die gesamte große Datei in den Speicher laden muss. Außerdem würde das obige nicht funktionieren, denn wenn ich richtig gelesen habe, kann man nicht synchron async_writes aufrufen; stattdessen würden Sie es von der Funktion handle_write aufrufen. Ich bin auf der Suche nach einer einfachen/eleganten Lösung für das Speicherproblem von jemandem besser vertraut mit Asio – Rollie

0

Also die Lösung, die ich kam, ist 2 Schreibfunktionen zu haben - write_body() und write_complete(). Sobald das Lesen abgeschlossen ist, gehen wir von einem Körper zu senden haben, rufen wir

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

innen write_body, tun wir so etwas wie

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()); 

und sobald wir den Inhalt der Datei fertig zu schreiben, ein letztes Mal schreiben mit :

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

Dies scheint ziemlich gut zu funktionieren, und hat Bedenken hinsichtlich der Speichernutzung behoben. Irgendwelche Kommentare zu dieser Lösung werden geschätzt; hoffentlich hilft das jemand anderem mit einem ähnlichen Problem in der Zukunft.