Serializing
The serializer transforms HTTP messages into bytes for transmission. It handles
chunked encoding, content compression, and the Expect: 100-continue handshake
automatically.
Basic Usage
Serialization follows a push model. You provide a message, then pull output buffers until complete:
// 1. Install serializer service with configuration
capy::polystore ctx;
serializer::config cfg;
install_serializer_service(ctx, cfg);
// 2. Create serializer
serializer sr(ctx);
// 3. Start with a message
response res(status::ok);
res.set(field::content_type, "text/plain");
sr.start(res, "Hello, world!");
// 4. Pull output and write to socket
while (!sr.is_done())
{
auto result = sr.prepare();
if (!result)
throw system::system_error(result.error());
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
Configuration
Serializer behavior is controlled through configuration:
capy::polystore ctx;
serializer::config cfg;
// Content encoding (compression)
cfg.apply_gzip_encoder = true; // Enable gzip compression
cfg.apply_deflate_encoder = false; // Enable deflate compression
cfg.apply_brotli_encoder = false; // Requires separate service
// Compression settings
cfg.zlib_comp_level = 6; // 0-9 (0=none, 9=best)
cfg.zlib_window_bits = 15; // 9-15
cfg.zlib_mem_level = 8; // 1-9
// Brotli settings (if enabled)
cfg.brotli_comp_quality = 5; // 0-11
cfg.brotli_comp_window = 18; // 10-24
// Buffer settings
cfg.payload_buffer = 8192; // Internal buffer size
cfg.max_type_erase = 1024; // Space for source storage
install_serializer_service(ctx, cfg);
Body Sources
The serializer supports several ways to provide message body content.
No Body
For messages without a body (HEAD responses, 204 No Content, etc.):
response res(status::no_content);
sr.start(res); // No body argument
Buffer Sequence Body
Provide the body as in-memory buffers:
response res(status::ok);
res.set(field::content_type, "text/plain");
res.set_content_length(13);
std::string body = "Hello, world!";
sr.start(res, capy::buffer(body));
Multiple buffers are supported:
std::string part1 = "Hello, ";
std::string part2 = "world!";
std::array<capy::const_buffer, 2> buffers = {
capy::buffer(part1),
capy::buffer(part2)
};
sr.start(res, buffers);
Source Body
For large or dynamic bodies, use a source:
// From file
capy::file f("large_file.bin", capy::file_mode::scan);
res.set_payload_size(f.size());
sr.start<file_source>(res, std::move(f));
// Pull output
while (!sr.is_done())
{
auto result = sr.prepare();
if (!result)
throw system::system_error(result.error());
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
Stream Body
For maximum flexibility, push body data incrementally:
response res(status::ok);
res.set(field::content_type, "application/octet-stream");
// No content-length - will use chunked encoding
auto stream = sr.start_stream(res);
// Push body data as it becomes available
while (has_more_data())
{
// Get buffer to write into
auto buf = stream.prepare();
std::size_t n = generate_data(buf);
stream.commit(n);
// Output is available
auto result = sr.prepare();
if (result)
{
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
}
// Signal end of body
stream.close();
// Flush remaining output
while (!sr.is_done())
{
auto result = sr.prepare();
if (result)
{
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
}
Chunked Encoding
The serializer uses chunked transfer encoding automatically when:
-
No
Content-Lengthheader is set -
The body size is unknown at start time
response res(status::ok);
res.set(field::content_type, "text/event-stream");
// No Content-Length - chunked encoding will be used
auto stream = sr.start_stream(res);
// Send chunks as events occur
for (auto& event : events)
{
auto buf = stream.prepare();
auto n = format_event(event, buf);
stream.commit(n);
// Flush to client
auto result = sr.prepare();
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
stream.close();
Content Encoding
When compression is enabled and the client accepts it, the serializer compresses the body automatically:
// Enable in config
serializer::config cfg;
cfg.apply_gzip_encoder = true;
// Check Accept-Encoding from request
if (request_accepts_gzip(req))
{
res.set(field::content_encoding, "gzip");
// Body will be compressed
}
sr.start(res, large_body);
Expect: 100-continue
The serializer handles the 100-continue handshake:
response res(status::ok);
// ... set headers ...
sr.start(res, body_source);
while (!sr.is_done())
{
auto result = sr.prepare();
if (result.error() == error::expect_100_continue)
{
// Client wants confirmation before sending body
// Send 100 Continue response
response cont(status::continue_);
serializer sr100(ctx);
sr100.start(cont);
// ... write sr100 output ...
// Continue with original response
continue;
}
if (!result)
throw system::system_error(result.error());
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
Error Handling
Serializer errors are reported through the result type:
auto result = sr.prepare();
if (!result)
{
auto ec = result.error();
if (ec == error::expect_100_continue)
{
// Not an error - handle 100-continue
}
else if (ec == error::need_data)
{
// Stream body needs more input
}
else
{
// Real error (e.g., source read failure)
std::cerr << "Serialization error: " << ec.message() << "\n";
}
}
Custom Sources
Implement the source interface for custom body generation:
class my_source : public source
{
std::function<std::string()> generator_;
std::string current_;
std::size_t pos_ = 0;
bool done_ = false;
public:
explicit my_source(std::function<std::string()> gen)
: generator_(std::move(gen))
{
}
protected:
results on_read(capy::mutable_buffer b) override
{
results rv;
while (b.size() > 0 && !done_)
{
// Refill current buffer if empty
if (pos_ >= current_.size())
{
current_ = generator_();
pos_ = 0;
if (current_.empty())
{
done_ = true;
rv.finished = true;
break;
}
}
// Copy to output
auto avail = current_.size() - pos_;
auto n = std::min(b.size(), avail);
std::memcpy(b.data(), current_.data() + pos_, n);
pos_ += n;
rv.bytes += n;
b = capy::mutable_buffer(
static_cast<char*>(b.data()) + n,
b.size() - n);
}
return rv;
}
};
Multiple Messages
Reuse the serializer for multiple messages on the same connection:
serializer sr(ctx);
for (auto& request : requests)
{
// Process request, build response
response res = handle(request);
// Serialize response
sr.start(res, response_body);
while (!sr.is_done())
{
auto result = sr.prepare();
if (result)
{
socket.write(*result);
sr.consume(capy::buffer_size(*result));
}
}
// Reset for next message
sr.reset();
}
Next Steps
With parsing and serialization covered, you can now build complete HTTP processing pipelines. For server applications, the router provides request dispatch:
-
Router — dispatch requests to handlers