Skip to content
Merged

work #261

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
13 changes: 0 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,6 @@ jobs:
ubsan: true
build-type: "RelWithDebInfo"

- compiler: "gcc"
version: "12"
cxxstd: "20"
latest-cxxstd: "20"
cxx: "g++-12"
cc: "gcc-12"
runs-on: "ubuntu-latest"
container: "ubuntu:22.04"
b2-toolset: "gcc"
name: "GCC 12: C++20"
shared: true
build-type: "Release"

- compiler: "gcc"
version: "13"
cxxstd: "20"
Expand Down
2 changes: 2 additions & 0 deletions doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* Server
** xref:server/router.adoc[Router]
** xref:server/route-patterns.adoc[Route Patterns]
** xref:server/serve-static.adoc[Serving Static Files]
** xref:server/serve-index.adoc[Directory Listings]
** xref:server/bcrypt.adoc[BCrypt Password Hashing]
// ** xref:server/middleware.adoc[Middleware]
// ** xref:server/errors.adoc[Error Handling]
Expand Down
121 changes: 29 additions & 92 deletions doc/modules/ROOT/pages/design_requirements/serializer.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,135 +27,72 @@ handle_request(
{
res.set_start_line(status::not_found, req.version());
res.set_keep_alive(req.keep_alive());
sr.start(res);
sr.set_message(res);
sr.start();
return {};
}
----


=== Source Body
=== WriteSink Body

A `source`-like body allows for algorithms that can generate body contents directly in the `serializer` 's internal buffer in one or multiple steps, for example for sending contents of a file. The `serializer` takes ownership of the `source` object and is responsible for driving the algorithm and offering a `MutableBufferSequence` (by calling the related virtual interfaces on the `source` object).
When the caller already has the body data in memory, the WriteSink interface writes caller-owned buffers through the serializer to the stream. The sink handles framing (chunked encoding, compression) automatically.

[source,cpp]
----
system::result<void>
capy::task<>
handle_request(
serializer& sr,
response& res,
request_view req)
{
res.set_start_line(status::ok, req.version());
res.set_keep_alive(req.keep_alive());
res.set_chunked(true);

http::file file;
system::error_code ec;
file.open("./index.html", file_mode::scan, ec);
if (ec.failed())
return ec;

sr.start<file_body>(res, std::move(file));
return {};
}
----


=== ConstBufferSequence Body

The `serializer` can use body contents passed as a `ConstBufferSequence` without copying it into its internal buffer.

[source,cpp]
----
system::result<void>
handle_request(
serializer& sr,
response& res,
request_view req)
request_view req,
capy::WriteStream auto& socket)
{
res.set_start_line(status::not_found, req.version());
res.set_keep_alive(req.keep_alive());

// Assume caller has an stable reference to static_pages
sr.start(res, buffers::make_buffer(static_pages.not_found));
return {};
}
----


=== ConstBufferSequence Body with Ownership Transfer

This is useful when the caller wants to create a `ConstBufferSequence` from an object which lives on the caller's stack or it is inconvenient for the caller to keep the object alive until the send operation is complete.

[source,cpp]
----
system::result<void>
handle_request(
serializer& sr,
response& res,
request_view req)
{
res.set_start_line(status::ok, req.version());
res.set_keep_alive(req.keep_alive());

std::string body{ "<!doctype html><title>HOWDY!</title>" };

sr.start(res, [body = std::move(body)]{return buffers::make_buffer(body);});
return {};
// Assume caller has a stable reference to static_pages
sr.set_message(res);
auto sink = sr.sink_for(socket);
co_await sink.write_eof(
capy::make_buffer(static_pages.not_found));
}
----


=== Streaming Body Contents

Sometimes it is desirable to read the body contents asynchronously, such as when reading from a socket, file, or a pipe. In such scenarios, it is possible to borrow buffers from the `serializer` and use them for the asynchronous read operation. As a result, the contents would be read directly into the `serializer` 's internal buffer without needing to make an extra copy.
=== BufferSink Body (Zero-Copy Streaming)

The following snippet demonstrates a usage example (using synchronous read/write APIs to keep the code simple):
Sometimes it is desirable to read the body contents asynchronously, such as when reading from a socket, file, or a pipe. The BufferSink interface lets the caller write directly into the serializer's internal buffer, avoiding an extra copy.

[source,cpp]
----
template<class ReadStream, class WriteStream>
void relay_body_contents(
capy::task<>
relay_body_contents(
serializer& sr,
response& res,
request_view req,
ReadStream& src,
WriteStream& client_session)
capy::ReadStream auto& src,
capy::WriteStream auto& client_session)
{
res.set_start_line(status::ok, req.version());
res.set_keep_alive(req.keep_alive());
res.set_chunked(true);

sr.start_stream(res);

auto write_some = [&]
{
auto bs = sr.prepare().value();
system::error_code ec;
auto length = client_session.write_some(bs, ec);
// if (ec.failed()) handle the error...
sr.consume(length);
};
sr.set_message(res);
auto sink = sr.sink_for(client_session);

for (;;)
{
auto bs = sr.stream_prepare();
system::error_code ec;
auto length = src.read_some(bs, ec);
sr.stream_commit(length);
capy::mutable_buffer arr[16];
auto bufs = sink.prepare(arr);

if (ec == asio::error::eof)
auto [ec, n] = co_await src.read_some(bufs);
if (ec == capy::error::eof)
{
co_await sink.commit_eof(n);
break;
}

// if (ec.failed()) handle the error...

write_some();
co_await sink.commit(n);
}

// Closing stream to signal serializer end of body contents
sr.stream_close();

while (!sr.is_done())
write_some();
}
----
----
6 changes: 0 additions & 6 deletions doc/modules/ROOT/pages/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,6 @@ cpp:boost::http::tchars[tchars]

| **Types (1/2)**

cpp:boost::http::basic_router[basic_router]

cpp:boost::http::byte_range[byte_range]

cpp:boost::http::cors[cors]
Expand All @@ -173,16 +171,12 @@ cpp:boost::http::dotfiles_policy[dotfiles_policy]

cpp:boost::http::etag_options[etag_options]

cpp:boost::http::flat_router[flat_router]

cpp:boost::http::range_result[range_result]

cpp:boost::http::range_result_type[range_result_type]

cpp:boost::http::route_params[route_params]

cpp:boost::http::route_params_base[route_params_base]

cpp:boost::http::route_result[route_result]

cpp:boost::http::route_task[route_task]
Expand Down
21 changes: 6 additions & 15 deletions doc/modules/ROOT/pages/router.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,10 @@ int main()
return route::send;
});

// Flatten for dispatch
flat_router fr(std::move(r));

// Dispatch a request
route_params params;
// ... populate params from parsed request ...
auto result = co_await fr.dispatch(method::get, url, params);
auto result = co_await r.dispatch(method::get, url, params);
}
----

Expand Down Expand Up @@ -354,23 +351,20 @@ matches `show_stats`.

== Dispatching Requests

Convert the router to a `flat_router` for efficient dispatch:
Dispatch requests directly on the router:

[source,cpp]
----
// Build routes
router<route_params> r;
// ... add routes ...

// Flatten for dispatch (do this once)
flat_router fr(std::move(r));

// Dispatch requests
route_params p;
p.url = parsed_url;
p.req = parsed_request;

auto result = co_await fr.dispatch(
auto result = co_await r.dispatch(
p.req.method(),
p.url,
p);
Expand All @@ -393,16 +387,16 @@ case route::close:
}
----

The `flat_router` pre-processes routes into a structure optimized for
dispatch performance. Create it once after all routes are registered.
The router internally flattens routes for efficient dispatch. The first
call to `dispatch()` finalizes the routing table automatically.

== The route_params Object

The standard `route_params` type contains everything handlers need:

[source,cpp]
----
struct route_params : route_params_base
class route_params
{
urls::url_view url; // Parsed request target
http::request req; // Request headers
Expand Down Expand Up @@ -538,9 +532,6 @@ int main()
return route::send;
});

// Flatten and dispatch
flat_router fr(std::move(r));

// ... integrate with your I/O layer ...
}
----
Expand Down
Loading
Loading