所以在boost HTTP Server 3 example之后,我想修改connection :: handle_read以支持发送正文和消息。但是,这样做的方法对我来说并不明显。我想写一些类似的东西:
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)));
}
其中body_stream_
是asio::windows::stream_handle
。
但是这种方法根本不处理http分块(所有这意味着在每个块之前发送块的大小)。解决这个问题的最佳方法是什么?我是否为符合boost const buffer要求的ifstream编写自己的包装器?或者尝试模拟async_write
在循环中多次调用async_write_some
时的效果?我应该提一下解决方案的要求是我在任何给定时间都没有将整个文件存储在内存中 - 只有一个或几个块。
非常新的ASIO和套接字,任何建议都表示赞赏!
答案 0 :(得分:11)
将异步编程可视化为一系列函数而不是循环可能更容易。当拆开链条时,我发现将操作分成两部分(启动和完成)是有帮助的,然后说明潜在的呼叫路径。下面是一个示例图示,它从body_stream_
异步读取一些数据,然后通过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(); ---------------------------------------'
}
如上所述,分块是通过异步链完成的,其中数据从body_stream_
读取,准备基于HTTP Chunked Transfer Encoding规范进行写入,然后写入套接字。如果body_stream_
仍有数据,则会发生另一次迭代。
我没有可以测试的Windows环境,但这是一个基本的完整示例,在Linux上一次分块数据10个字节。
#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();
}
我有一个小的html文件,将通过分块编码提供,一次10个字节:
<html>
<body>
Test transfering html over chunked encoding.
</body>
</html>
正在运行服务器:
$ mkfifo example_pipe
$ sudo ./a.out &
[1] 28963
<open browser and connected to port 80>
$ cat html > example_pipe
服务器的输出:
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...
wireshark输出显示无畸形数据:
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 ..
答案 1 :(得分:0)
示例非常简单,只是为了向您展示如何简单地处理HTTP请求.Chunked Transfer Encoding不支持此示例。 一些建议:
逻辑将是这样的:
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);
测试程序时,可以使用Fiddler2来监控http消息。
答案 2 :(得分:0)
所以我提出的解决方案是拥有2个写函数 - write_body()
和write_complete()
。一旦完成阅读,假设我们有一个要发送的正文,我们就打电话给
body_fp_.open(bodyFile);
async_write(get_headers(), write_body)
在write_body中,我们执行类似
的操作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());
一旦我们完成文件内容的编写,请写下最后一次:
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)));
这看起来效果很好,并减轻了对内存使用的担忧。对此解决方案的任何意见表示赞赏;希望这将有助于将来遇到类似问题的其他人。