Как копировать или повторно использовать 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_data​​strong > может быть недействительным после изменения req_buf.