Repository navigation
Expand file tree
/
Copy pathresponse_definition.hpp
More file actions
430 lines (382 loc) · 21.5 KB
/
Copy pathresponse_definition.hpp
File metadata and controls
430 lines (382 loc) · 21.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
/*
This file is part of libhttpserver
Copyright (C) 2011-2026 Sebastiano Merlino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the file
LICENSE in the distribution; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
// Immutable reusable response definitions and per-send overlays
// (architecture §3.2, DR-V3-005, PRD-V3N-REQ-026/028/029/030).
//
// A response_definition is a replayable response value: one status,
// one base field set, and one body source, frozen at factory time and
// never mutated afterwards. Any number of sends — sequential or
// concurrent — share the same immutable state; every send allocates
// its own cursor (serialization offset, file handle, producer) inside
// its own coroutine frame, so sends cannot interfere (DR-V3-005:
// "each send owns serialization state, body cursor, cancellation,
// overlay").
//
// Body sources (REQ-026/028) and their vocabulary — pull chunks,
// producers, factories, the source-kind taxonomy, and the per-send
// cursor — live in response_sources.hpp (TASK-113 split):
// owned_bytes — the library holds a deep copy of the body;
// reopen_file — the library stores a path and opens one fresh
// handle per send;
// factory — the application supplies a callable returning one
// fresh producer per send;
// borrowed — the application keeps the memory and hands over a
// span plus the body_lease pinning it alive;
// owned_file — an open std::FILE* transferred to the library and
// streamed once from its position at transfer;
// owned_pipe — a pipe endpoint transferred to the library and
// streamed once to its own EOF (unknown length).
//
// One send = send_definition(exchange, definition, overlay): it
// validates the overlay and the merged framing BEFORE committing the
// head (a conflicting or malformed input fails typed with the exchange
// untouched), merges the overlay's headers after the definition's base
// fields in order, streams the body through exchange::writer() with
// backpressure, and finishes with the overlay's trailers. The send
// never mutates the definition (borrowed) or the caller's overlay
// (taken by value into the send's own frame).
//
// Ownership / lifetime matrix (REQ-028). The Replay column is
// REQ-029's concurrent-reuse contract, deliberately narrowed by the
// one-shot kinds (architecture §3.2, DR-V3-005):
//
// Source | Library owns | Replay | Application guarantees
// -----------+-----------------------+------------+-----------------------
// owned_bytes| deep copy of the | any number | nothing after the
// | body, moved in at | of sends | factory returns
// | factory time | |
// reopen_file| the path; one fresh | any number | the path resolves at
// | handle per send | of sends | each send's prepare; a
// | | | file that shrinks below
// | | | the declared or pinned
// | | | size fails that send
// | | | typed
// factory | the callable | any number | the callable is safe to
// | (move-only) | of sends | invoke concurrently
// | | | (once per in-flight
// | | | send); each producer
// | | | serves one send; a
// | | | producer's data spans
// | | | stay valid until its
// | | | next pull
// borrowed | the span and the | any number | the keeper keeps
// | lease (nothing is | of sends | [data, data + size)
// | copied) | | unchanged and alive
// | | | while the definition
// | | | lives; the lease spans
// | | | every send
// owned_file | the handle until the | ONE send | the handle is seekable
// | exactly-once close | | with a remaining size a
// | (the claiming send's | | long can express; an
// | cursor, or the unsent | | application keeping the
// | holder, releases it) | | handle open wraps the
// | | | readable side in a
// | | | factory source instead
// owned_pipe | the endpoint until | ONE send | the stream reaches EOF
// | the exactly-once | | by itself, or supplies
// | close | | a Content-Length that
// | | | bounds the read
// response_overlay| its own field sets (deep | a plain value; the send
// | value type) | frame holds its own copy
// send_definition | nothing of either input | the definition outlives
// | | the task (borrowed by
// | | reference)
//
// The one-shot narrowing: a one-shot definition admits exactly ONE
// send attempt past validation. The claim is taken before the probe
// and is not refundable, so a second send — or a racing loser —
// fails invalid_state before writing anything, and a failed probe
// spends the definition. A leased borrowed body keeps REQ-029's full
// contract: reading a span is non-destructive.
#ifndef SRC_HTTPSERVER_RESPONSE_DEFINITION_HPP_
#define SRC_HTTPSERVER_RESPONSE_DEFINITION_HPP_
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <httpserver/concurrency/task.hpp>
#include <httpserver/exchange.hpp>
#include <httpserver/http/fields.hpp>
#include <httpserver/http/method.hpp>
#include <httpserver/http/outcome.hpp>
#include <httpserver/http/status.hpp>
#include <httpserver/response_sources.hpp>
namespace httpserver {
class response_definition;
struct response_overlay;
namespace detail {
// True iff any byte of v is a control character forbidden in a field
// value: every CTL (0x00-0x1F, 0x7F) except HTAB, which is legal
// interior OWS. Mirrors the engine-side rule
// (detail/http1_head_parser.hpp contains_field_value_ctl), re-expressed
// here because a public header may not include a private one.
bool field_value_has_ctl(std::string_view v) noexcept;
// Wire-safety of one field set: every name a token (RFC 9110 tchar),
// every value free of forbidden control bytes. Shared by the
// definition factories (base fields) and the send path (overlay
// fields).
http::outcome check_fields_wire_safe(const http::fields& f);
// Framing self-consistency of one field set: at most one
// Content-Length occurrence, never a Transfer-Encoding beside a
// Content-Length (the same conflicts the engine's response mode
// rejects), and a numeric Content-Length when one is present (a
// framing lie fails validation, not the wire).
http::outcome check_fields_framing(const http::fields& f);
// Pins Content-Length to `size` when the field set carries neither
// framing field (an explicit Content-Length or Transfer-Encoding passes
// through verbatim; "0" is pinned like any other size).
void pin_content_length(http::fields& f, std::uint64_t size);
// Everything a send does before committing the head (defined after
// send_report below): the one function reaching the definition's
// frozen impl, so the cursor stays decoupled from the class.
http::outcome prepare_send(const response_definition& def,
const response_overlay& overlay,
http::fields& merged,
send_cursor& cursor);
} // namespace detail
// A replayable immutable response value (DR-V3-005). Constructed once
// through the validating factories; afterwards no API mutates it —
// copies share the frozen state (a shared immutable block), moves hand
// it over. Sends read it concurrently and never write it.
class response_definition {
public:
// The source taxonomy lives in response_sources.hpp; the alias
// keeps the TASK-112 spelling (response_definition::source_kind).
using source_kind = response_source_kind;
// Body held by value; Content-Length pinned to the body size when
// the fields carry neither framing field (including "0").
static http::outcome owned_bytes(const http::status& s, http::fields f,
std::vector<std::byte> body,
response_definition& out);
// Body re-read from `path` on every send: nothing is opened at
// construction, so a definition outlives any one file's lifetime;
// each send observes the file at its own prepare and pins
// Content-Length per send.
static http::outcome reopen_file(const http::status& s, http::fields f,
std::string path,
response_definition& out);
// Body produced per send by a fresh producer from `make`.
static http::outcome factory(const http::status& s, http::fields f,
body_factory make,
response_definition& out);
// Body borrowed from application memory under an explicit lease:
// the library stores only the view and the lease, never a copy.
// The keeper keeps [body.data(), body.data() + body.size())
// unchanged and alive while the definition lives (that
// immutability is what makes a borrowed body replayable);
// releasing the last definition releases the keeper. An invalid
// lease is rejected before a definition exists — sharing a
// borrowed body without a valid lease has no safe send (REQ-028).
static http::outcome borrowed(const http::status& s, http::fields f,
std::span<const std::byte> body,
body_lease lease,
response_definition& out);
// Body streamed from an open std::FILE* TRANSFERRED to the
// library and closed with std::fclose (see the owned_close_fn
// overload for a custom release).
static http::outcome owned_file(const http::status& s, http::fields f,
std::FILE* handle,
response_definition& out) {
return owned_file(s, std::move(f), handle,
owned_close_fn(&detail::fclose_owned), out);
}
// Body streamed from an open std::FILE* transferred to the
// library (REQ-028): ONE-SHOT — one handle holds one seek
// position, so exactly one send may consume it; a second send
// fails invalid_state before writing anything. The body is the
// handle's remaining bytes at transfer (position→EOF), probed at
// the send's prepare (an unseekable handle fails there, typed,
// and spends the definition). Content-Length pins per send to the
// probed size unless the fields declare one — a declared length
// becomes the read bound, and a body short of it fails that send
// typed. The library owns the handle from here on and closes it
// exactly once: after the send completes, fails, or is cancelled,
// or when an unsent definition is destroyed. An application that
// must keep its handle open wraps the readable side in a factory
// source instead. `long`-based offsets bound the probed size to
// LONG_MAX on LLP64 platforms; larger transfers belong to
// reopen_file (std::streamoff).
static http::outcome owned_file(const http::status& s, http::fields f,
std::FILE* handle,
owned_close_fn close,
response_definition& out);
// Body streamed from an open pipe endpoint TRANSFERRED to the
// library and closed with std::fclose (see the owned_close_fn
// overload for a custom release).
static http::outcome owned_pipe(const http::status& s, http::fields f,
std::FILE* handle,
response_definition& out) {
return owned_pipe(s, std::move(f), handle,
owned_close_fn(&detail::fclose_owned), out);
}
// Body streamed from an open pipe endpoint (any std::FILE* with no
// usable seek position) transferred to the library (REQ-028):
// ONE-SHOT and NON-REPLAYABLE (architecture §3.2) — the bytes can
// be read exactly once, so exactly one send may consume the
// definition; a second send, however it races the winner, fails
// invalid_state before writing anything. Nothing is probed at the
// send's prepare (a pipe is never seeked). The length is unknown
// unless the fields declare a Content-Length: without one no
// length is pinned and the framing stays the transport's (chunked
// on HTTP/1.1, close-delimited on HTTP/1.0); with one the declared
// length bounds the read, and a body short of it fails that send
// typed. The library owns the endpoint from here on and closes it
// exactly once: after the send completes, fails, or is cancelled,
// or when an unsent definition is destroyed.
static http::outcome owned_pipe(const http::status& s, http::fields f,
std::FILE* handle,
owned_close_fn close,
response_definition& out);
// The empty, not-yet-valid definition; valid() reports false.
response_definition() noexcept = default;
response_definition(const response_definition&) noexcept = default;
response_definition& operator=(const response_definition&) noexcept
= default;
response_definition(response_definition&&) noexcept = default;
response_definition& operator=(response_definition&&) noexcept
= default;
// Meaningful only when valid().
source_kind kind() const noexcept {
return impl_ ? impl_->body->kind : source_kind::owned_bytes;
}
bool valid() const noexcept { return impl_ != nullptr; }
// The frozen status and base fields. For an invalid definition
// these return shared empty instances. The fields never include
// overlay content: a send merges its overlay into a local copy.
const http::status& status() const noexcept;
const http::fields& fields() const noexcept;
private:
// The one send-side reach into the frozen state: prepare_send
// hands the cursor the body block. Consumers never see the impl.
friend http::outcome detail::prepare_send(const response_definition&,
const response_overlay&,
http::fields&,
detail::send_cursor&);
// The frozen state. Built locally by a factory as a `build`,
// validated in full, and only then shared immutably
// (`shared_ptr<const impl>` makes the immutability structural: no
// API can reach a mutable reference). The body rides as its own
// shared immutable block so a live send's cursor pins it
// independently of this object.
struct impl {
http::status status;
http::fields fields;
std::shared_ptr<const detail::response_body_source> body;
};
// A factory's pending, still-private assembly: like impl but the
// body is a plain value. validate/commit are its only consumers;
// a failed build is discarded without ever being shared.
struct build {
http::status status;
http::fields fields;
detail::response_body_source body;
};
// Full validation of the assembled state; nothing is mutated on
// failure (the caller never touches `out` before this returns ok).
static http::outcome validate(const build& state);
// Validates `state` and, only on success, freezes it into `out`:
// the body block becomes its own shared immutable piece, so the
// definition and any in-flight cursor alias one frozen source.
static http::outcome commit(build&& state, response_definition& out);
std::shared_ptr<const impl> impl_;
};
// Request-specific additions for ONE send (DR-V3-005, REQ-030). Owns
// its ordered header and trailer occurrences as a plain deep value;
// the definition it decorates is never mutated. Headers append AFTER
// the definition's base fields, in entries() order (repeated names
// included); trailers ride the final framing of that send only. The
// engine's finish-time rules still apply: a trailer on a body the
// protocol frames without a trailer section fails there, typed.
struct response_overlay {
http::fields headers;
http::fields trailers;
};
// Typed result of one send_definition() completion. ok: the whole body
// was streamed and the final framing (with the overlay's trailers)
// was accepted; body_bytes counts the payload bytes handed to the
// writer. Every failure carries its typed outcome.
struct send_report {
http::outcome status;
std::size_t body_bytes = 0;
};
namespace detail {
// Merged wire fields of one send: the definition's base fields with
// every overlay header occurrence appended in entries() order, so the
// committed sequence is base-then-overlay with the overlay order
// preserved (REQ-030). A local copy per send — the definition is
// never touched.
http::fields merge_send_fields(const response_definition& def,
const response_overlay& overlay);
// Trailer names that may never ride a trailer section: the framing
// fields and the connection's routing name belong to the head only.
http::outcome check_trailer_names(const http::fields& trailers);
// Pre-commit validation of one overlay: wire-safe headers, wire-safe
// trailers, and no framing or routing names among the trailers.
http::outcome check_overlay(const response_overlay& overlay);
// Everything a send does BEFORE committing the head: gates on the
// definition, validates the overlay and the merged framing, merges the
// wire fields, and takes the send's cursor snapshot. A failure
// here leaves the exchange untouched (DR-V3-005: conflicting
// singleton/framing fields fail validation before headers commit).
http::outcome prepare_send(const response_definition& def,
const response_overlay& overlay,
http::fields& merged,
send_cursor& cursor);
// Streams the cursor's body into the exchange's writer, one pull at a
// time; backpressure is the writer's (a parked write IS it).
task<http::outcome> stream_body(exchange& x, send_cursor& cursor,
std::size_t& body_bytes);
} // namespace detail
// Sends one definition through an open exchange with one overlay
// (architecture §3.1's exchange::respond(const response_definition&,
// response_overlay), as a free function wrapping the exchange the
// same way make_sync_route does — the exchange itself stays
// engine-facing and unmodified).
//
// The send validates the overlay and the merged framing, commits the
// head with start_response (base fields then overlay headers, in
// order), streams the body through writer() with backpressure, and
// finishes with the overlay's trailers. A validation, prepare, or
// head-commit failure returns typed with the exchange untouched; a
// failure after the committed head returns typed with the exchange
// terminal (the engine owns the connection's fate; the send never
// calls abort() itself).
//
// The definition is borrowed — it must outlive the task; it is never
// mutated. The overlay is taken BY VALUE: the send frame owns its
// copy, so a temporary overlay is exactly as safe as a named one (a
// reference parameter bound to a default temporary would dangle
// across the send's suspensions).
//
// Failure vocabulary: invalid_argument — malformed fields, framing
// conflicts, an unopenable file, an unseekable owned_file handle, an
// empty producer or chunk; invalid_state — the exchange or writer
// gates (e.g. a second terminal decision), a spent one-shot source,
// or a handle read failure mid-body; connection_closed / cancelled —
// a disconnect before or during the send; protocol_error — a file,
// transferred, or borrowed body short of its pinned or declared
// Content-Length.
task<send_report> send_definition(exchange& x,
const response_definition& def,
response_overlay overlay = {});
} // namespace httpserver
#endif // SRC_HTTPSERVER_RESPONSE_DEFINITION_HPP_