Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions include/boost/http/concept/buffer_sink.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/http
//

#ifndef BOOST_HTTP_CONCEPT_BUFFER_SINK_HPP
#define BOOST_HTTP_CONCEPT_BUFFER_SINK_HPP

#include <boost/capy/detail/config.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/concept/decomposes_to.hpp>
#include <boost/capy/concept/io_awaitable.hpp>
#include <system_error>

#include <concepts>
#include <cstddef>
#include <span>

namespace boost {
namespace http {

/** Concept for types that consume buffer data using callee-owned buffers.

A type satisfies `BufferSink` if it provides a synchronous `prepare`
member function that fills a caller-provided span with mutable buffer
descriptors pointing to the sink's internal storage, and asynchronous
`commit` and `commit_eof` member functions to finalize written data.

This concept models the "callee owns buffers" pattern where the sink
provides writable memory and the caller writes directly into it,
enabling zero-copy data transfer. Compare with @ref WriteSink which
uses the "caller owns buffers" pattern.

@tparam T The sink type.

@par Syntactic Requirements

@li `T` must provide a synchronous `prepare` member function accepting
a `std::span<capy::mutable_buffer>` and returning a span of filled buffers
@li `T` must provide `commit(n)` returning an @ref capy::IoAwaitable that
decomposes to `(error_code)`
@li `T` must provide `commit_eof(n)` returning an @ref capy::IoAwaitable
that decomposes to `(error_code)`

@par Semantic Requirements

The `prepare` operation provides writable buffer space:

@li Returns a span of buffer descriptors that were filled
@li The returned buffers point to the sink's internal storage
@li If the returned span is empty, the sink has no available space;
caller should call `commit` to flush data and try again

The `commit` operation finalizes written data:

@li Commits `n` bytes written to the most recent `prepare` buffers
@li May trigger underlying I/O (flush to socket, compression, etc.)
@li On success: `ec` is `false`
@li On error: `ec` is `true`

The `commit_eof` operation commits final data and signals end-of-stream:

@li Commits `n` bytes written to the most recent `prepare` buffers
and finalizes the sink
@li After success, no further operations are permitted
@li On success: `ec` is `false`, sink is finalized
@li On error: `ec` is `true`

@par Buffer Lifetime

Buffers returned by `prepare` remain valid until the next call to
`prepare`, `commit`, `commit_eof`, or until the sink is destroyed.

@par Conforming Signatures

@code
std::span<capy::mutable_buffer> prepare( std::span<capy::mutable_buffer> dest );

capy::IoAwaitable auto commit( std::size_t n );
capy::IoAwaitable auto commit_eof( std::size_t n );
@endcode

@par Example

@code
template<BufferSource Source, BufferSink Sink>
io_task<std::size_t> transfer( Source& source, Sink& sink )
{
capy::const_buffer src_arr[16];
capy::mutable_buffer dst_arr[16];
std::size_t total = 0;

for(;;)
{
auto [ec1, src_bufs] = co_await source.pull( src_arr );
if( ec1 == cond::eof )
{
auto [eof_ec] = co_await sink.commit_eof( 0 );
co_return {eof_ec, total};
}
if( ec1 )
co_return {ec1, total};

auto dst_bufs = sink.prepare( dst_arr );
std::size_t n = buffer_copy( dst_bufs, src_bufs );

auto [ec2] = co_await sink.commit( n );
if( ec2 )
co_return {ec2, total};

total += n;
}
}
@endcode

@see BufferSource, WriteSink, capy::IoAwaitable, capy::awaitable_decomposes_to
*/
template<typename T>
concept BufferSink =
requires(T& sink, std::span<capy::mutable_buffer> dest, std::size_t n)
{
// Synchronous: get writable buffers from sink's internal storage
{ sink.prepare(dest) } -> std::same_as<std::span<capy::mutable_buffer>>;

// Async: commit n bytes written
{ sink.commit(n) } -> capy::IoAwaitable;
requires capy::awaitable_decomposes_to<
decltype(sink.commit(n)),
std::error_code>;

// Async: commit n final bytes and signal end of data
{ sink.commit_eof(n) } -> capy::IoAwaitable;
requires capy::awaitable_decomposes_to<
decltype(sink.commit_eof(n)),
std::error_code>;
};

} // namespace http
} // namespace boost

#endif
121 changes: 121 additions & 0 deletions include/boost/http/concept/buffer_source.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/cppalliance/http
//

#ifndef BOOST_HTTP_CONCEPT_BUFFER_SOURCE_HPP
#define BOOST_HTTP_CONCEPT_BUFFER_SOURCE_HPP

#include <boost/capy/detail/config.hpp>
#include <boost/capy/buffers.hpp>
#include <boost/capy/cond.hpp>
#include <boost/capy/concept/decomposes_to.hpp>
#include <boost/capy/concept/io_awaitable.hpp>

#include <cstddef>
#include <span>
#include <system_error>

namespace boost {
namespace http {

/** Concept for types that produce buffer data asynchronously.

A type satisfies `BufferSource` if it provides a `pull` member function
that fills a caller-provided span of buffer descriptors and
await-returns `(error_code, std::span<capy::const_buffer>)`, plus a `consume` member function
to indicate how many bytes were used.

Use this concept when you need to produce data asynchronously for
transfer to a sink, such as streaming HTTP request bodies or reading
file contents for transmission.

@tparam T The source type.

@par Syntactic Requirements

@li `T` must provide a `pull` member function accepting a
`std::span<capy::const_buffer>` for output
@li The return type must satisfy @ref capy::IoAwaitable
@li The awaitable must decompose to `(error_code,std::span<capy::const_buffer>)`
via structured bindings
@li `T` must provide a `consume` member function accepting a byte count

@par Semantic Requirements

The `pull` operation fills the provided buffer span with data starting
from the current unconsumed position. On return, exactly one of the
following is true:

@li **Data available**: `!ec` and `bufs.size() > 0`.
The returned span contains buffer descriptors.
@li **Source exhausted**: `ec == cond::eof` and `bufs.empty()`.
No more data is available; the transfer is complete.
@li **Error**: `ec` is `true` and `ec != cond::eof`.
An error occurred.

Calling `pull` multiple times without intervening `consume` returns
the same unconsumed data. The `consume` operation advances the read
position by the specified number of bytes. The next `pull` returns
data starting after the consumed bytes.

@par Buffer Lifetime

The memory referenced by the returned buffer descriptors must remain
valid until the next call to `pull`, `consume`, or until the source
is destroyed.

@par Conforming Signatures

@code
some_io_awaitable<io_result<std::span<capy::const_buffer>>>
pull( std::span<capy::const_buffer> dest );

void consume( std::size_t n ) noexcept;
@endcode

@par Example

@code
template<BufferSource Source, capy::WriteStream Stream>
io_task<std::size_t> transfer( Source& source, Stream& stream )
{
capy::const_buffer arr[16];
std::size_t total = 0;
for(;;)
{
auto [ec, bufs] = co_await source.pull( arr );
if( ec == cond::eof )
co_return {{}, total};
if( ec )
co_return {ec, total};
auto [write_ec, n] = co_await stream.write_some( bufs );
if( write_ec )
co_return {write_ec, total};
source.consume( n );
total += n;
}
}
@endcode

@see capy::IoAwaitable, WriteSink, capy::awaitable_decomposes_to
*/
template<typename T>
concept BufferSource =
requires(T& src, std::span<capy::const_buffer> dest, std::size_t n)
{
{ src.pull(dest) } -> capy::IoAwaitable;
requires capy::awaitable_decomposes_to<
decltype(src.pull(dest)),
std::error_code, std::span<capy::const_buffer>>;
src.consume(n);
};

} // namespace http
} // namespace boost

#endif
Loading
Loading