Как копировать или повторно использовать boost:: asio:: streambuf?
Я использую HTTP-прокси-сервер с некоторой бизнес-логикой для повышения asio.
В пункте (1) boost:: asio:: streambuf response _ содержит заголовки HTTP и часть тела http.
После синтаксического анализа http_response:: parse буфера boost:: asio:: streambuf response _ пуст.
В (2) я проверяю всю бизнес-логику и тело чтения, если в заголовках заголовок Content-Length.
Затем, если данные response_ соответствуют конкретным условиям, я хочу отправить исходный буфер response _ в другой сокет (3).
Проблема заключается в том, что после разбора буфера пуст.
Есть ли способ скопировать boost:: asio:: streambuf для повторного использования данных?
void http_response::parse(boost::asio::streambuf& buffer)
{
std::istream response_stream(&buffer);
response_stream >> version_;
response_stream >> status_code_;
response_stream >> status_message_;
std::string key;
std::string value;
std::string header;
std::getline(response_stream, header);
while (std::getline(response_stream, header) && header != "\r") {
header.resize(header.size() - 1);
std::size_t found = header.find(':');
if (found != std::string::npos) {
key = header.substr(0, found);
value = header.substr(found + 2);
headers_[key] = value;
}
}
}
bool go(const std::string& hostname, const std::string& path,
const std::string& server, int port,
boost::asio::io_service::strand& strand,
boost::asio::yield_context& yield)
{
...
http_response response;
boost::asio::streambuf response_;
// async read http header from socket
std::clog << "<- " << sequence_ << " schedule async_read_until head" << std::endl;
boost::asio::async_read_until(socket_, response_, "\r\n\r\n", yield[err]);
check_error_and_timeout(err, timeout_);
// 1. response_.size() == 512 here
response.parse(response_);
// 2. response_.size() == 0 empty here
// using headers for business logic check
...
// read http body if Content-Length > 0
const std::string str_content_length = response.get_header("Content-Length", "");
const size_t content_length = std::stoi(str_content_length);
if(!str_content_length.empty() && content_length > response_.size())
{
std::clog << "<- " << sequence_ << " schedule async_read body" << std::endl;
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(content_length - response_.size()),
yield[err]);
check_error_and_timeout(err, timeout_);
}
// 3. after read all header and body write all data to server sock
boost::asio::async_write(server_socket_, response_, yield[err]);
}
Ответы
Ответ 1
Можно использовать boost::asio::buffer_copy()
для копирования содержимого буферов Asio. Это может быть удобно, если, например, хочется скопировать содержимое одного streambuf
в другое streambuf
.
boost::asio::streambuf source, target;
...
std::size_t bytes_copied = buffer_copy(
target.prepare(source.size()), // target output sequence
source.data()); // source input sequence
// Explicitly move target output sequence to its input sequence.
target.commit(bytes_copied);
Аналогичный подход можно использовать для копирования из streambuf
в любой тип, для которого Asio поддерживает изменяемые буферы. Например, копирование содержимого в std::vector<char>
:
boost::asio::streambuf source;
...
std::vector<char> target(source.size());
buffer_copy(boost::asio::buffer(target), source.data());
Одним из примечательных исключений является то, что Asio не поддерживает возврат изменяемого буфера для std::string
. Тем не менее, все же можно выполнить копирование в std::string
с помощью итераторов:
boost::asio::streambuf source;
...
std::string target{
buffers_begin(source.data()),
buffers_end(source.data())
};
Вот пример демонстрирующий копирование содержимого из boost::asio::streambuf
в другие типы:
#include <iostream>
#include <string>
#include <vector>
#include <boost/asio.hpp>
int main()
{
const std::string expected = "This is a demo";
// Populate source input sequence.
boost::asio::streambuf source;
std::ostream ostream(&source);
ostream << expected;
// streambuf example
{
boost::asio::streambuf target;
target.commit(buffer_copy(
target.prepare(source.size()), // target output sequence
source.data())); // source input sequence
// Verify the contents are equal.
assert(std::equal(
buffers_begin(target.data()),
buffers_end(target.data()),
begin(expected)
));
}
// std::vector example
{
std::vector<char> target(source.size());
buffer_copy(boost::asio::buffer(target), source.data());
// Verify the contents are equal.
assert(std::equal(begin(target), end(target), begin(expected)));
}
// std::string example
{
// Copy directly into std::string. Asio does not support
// returning a mutable buffer for std::string.
std::string target{
buffers_begin(source.data()),
buffers_end(source.data())
};
// Verify the contents are equal.
assert(std::equal(begin(target), end(target), begin(expected)));
}
}
Ответ 2
Во-первых, вы не можете скопировать streambuf - он удалил конструктор копии.
Я должен предложить использовать собственный буфер с помощью asio:: buffer и проанализировать его. Но с этим вы не сможете использовать async_read_until. Кроме того, вы можете сделать копию потребления в свой собственный буфер, а затем отправить его, если необходимо.
Lifehack: вы можете получать данные, не потребляя их с помощью buffer_cast, но обратите внимание на его небезопасный путь, поскольку он не документирован и может сломаться (но он работал у меня много раз и много повышающих обновлений):
// req_buf is a streambuf
const char *req_data = boost::asio::buffer_cast<const char *>( req_buf.data() );
auto sz = req_buf.size();
Запомните еще раз: этот актер работает, потому что streambuf реализует некоторые понятия, но не документируется напрямую. Кроме того, помните, что req_datastrong > может быть недействительным после изменения req_buf.