Repository navigation
Expand file tree
/
Copy pathoptions.hpp
More file actions
704 lines (633 loc) · 26.2 KB
/
Copy pathoptions.hpp
File metadata and controls
704 lines (633 loc) · 26.2 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
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
/*
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
*/
#ifndef SRC_HTTPSERVER_SERVER_OPTIONS_HPP_
#define SRC_HTTPSERVER_SERVER_OPTIONS_HPP_
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <httpserver/concurrency/executor.hpp>
#include <httpserver/http/outcome.hpp>
#include <httpserver/http/protocol.hpp>
#include <httpserver/server/budgets.hpp>
#include <httpserver/server/hooks.hpp>
#include <httpserver/server/peer_policy.hpp>
namespace httpserver {
namespace server {
// Selection of the sole optional TLS/crypto provider linked into the
// build (DR-V3-002). `none` is the TLS-off configuration; TLS, HTTP/2,
// and HTTP/3 are then unavailable. `system_default` selects the
// provider chosen at build time; its identity is never part of this
// API.
enum class tls_provider : std::uint8_t {
none,
system_default,
};
// Credential profile used with the selected provider. `certificates`
// authenticates this endpoint with a certificate chain; `mutual_tls`
// additionally requires a client chain; `external_psk` uses
// out-of-band shared keys and is limited to HTTP/1 in v3.
enum class tls_profile : std::uint8_t {
none,
certificates,
mutual_tls,
external_psk,
};
// Documented configuration bounds quoted by validation diagnostics.
inline constexpr std::size_t max_workers = 1024;
inline constexpr std::size_t max_listeners = 64;
// 24 hours; the ceiling for every timeout field.
inline constexpr std::chrono::milliseconds max_timeout{86400000};
// Closed set over http::protocol. Defaults to the TLS-off baseline
// {HTTP/1.0, HTTP/1.1}. HTTP/2 and HTTP/3 require a TLS provider, so
// they are opt-in via enable().
class protocol_set {
public:
constexpr protocol_set() noexcept : enabled_(kDefaultMask) { }
constexpr bool contains(http::protocol version) const noexcept {
return (enabled_ & mask(version)) != 0;
}
constexpr void enable(http::protocol version) noexcept {
enabled_ = static_cast<std::uint8_t>(enabled_ | mask(version));
}
constexpr void disable(http::protocol version) noexcept {
enabled_ = static_cast<std::uint8_t>(enabled_ & ~mask(version));
}
private:
static constexpr std::uint8_t kDefaultMask = 0x03; // HTTP/1.0 + HTTP/1.1
static constexpr std::uint8_t mask(http::protocol version) noexcept {
return static_cast<std::uint8_t>(
1u << static_cast<unsigned>(version));
}
std::uint8_t enabled_;
};
// One listening endpoint. address is "" or "*" for any local address,
// or a numeric IPv4/IPv6 literal; names are resolved by the caller, not
// by validation. port 0 requests an ephemeral port. tls selects whether
// the endpoint speaks TLS (which requires a tls_provider).
struct listener_options {
std::string address;
std::uint16_t port = 0;
bool tls = false;
};
// The §3.4 timeout inventory, in milliseconds. Every field must be
// positive and at most max_timeout.
struct timeout_options {
std::chrono::milliseconds handshake{30000};
std::chrono::milliseconds header{30000};
std::chrono::milliseconds body_idle{60000};
std::chrono::milliseconds suspension{300000};
std::chrono::milliseconds write_idle{30000};
std::chrono::milliseconds ws_close{10000};
std::chrono::milliseconds drain{30000};
};
// Worker concurrency. workers == 0 lets the runtime pick the degree of
// parallelism (documented, valid).
struct concurrency_options {
std::size_t workers = 0;
};
// Provider/profile pair. provider == none pairs only with
// profile == none.
struct tls_options {
tls_provider provider = tls_provider::none;
tls_profile profile = tls_profile::none;
};
// TASK-119: the construction-time stance of the server-wide peer
// policy (the v2 default_policy + ip_access_control pair). The lists
// hold pattern spellings exactly as net::parse_pattern accepts them
// (exact literals, trailing-* IPv4 wildcards, CIDR over both
// families); validate() (V12) judges every entry pre-listen and
// listen() seeds the live policy. Runtime mutation afterwards goes
// through native_server::peer_policy() (thread-safe, DR-V3-008).
struct peer_policy_options {
bool enabled = true;
peer_policy_mode mode = peer_policy_mode::accept_all;
std::vector<std::string> deny;
std::vector<std::string> allow;
};
namespace detail {
constexpr bool is_digit(char c) noexcept {
return c >= '0' && c <= '9';
}
// Decimal field of an IPv4 literal: one to three digits, no leading
// zero, value at most 255.
constexpr bool valid_ipv4_field(std::string_view field) noexcept {
if (field.empty() || field.size() > 3) return false;
if (field.size() > 1 && field[0] == '0') return false;
unsigned value = 0;
for (const char c : field) {
if (!is_digit(c)) return false;
value = value * 10u + static_cast<unsigned>(c - '0');
}
return value <= 255u;
}
// Dotted-quad IPv4 literal ("192.0.2.1"): exactly four fields, no
// extra separators.
constexpr bool valid_ipv4(std::string_view v) noexcept {
std::size_t pos = 0;
for (int field = 0; field < 4; ++field) {
const std::size_t dot = v.find('.', pos);
const bool last = field == 3;
if (last != (dot == std::string_view::npos)) return false;
const std::size_t width =
dot == std::string_view::npos ? std::string_view::npos
: dot - pos;
if (!valid_ipv4_field(v.substr(pos, width))) return false;
pos = dot == std::string_view::npos ? v.size() : dot + 1;
}
return pos == v.size();
}
constexpr int hex_value(char c) noexcept {
if (is_digit(c)) return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
// Hextet of an IPv6 literal: one to four hex digits.
constexpr bool valid_ipv6_group(std::string_view group) noexcept {
if (group.empty() || group.size() > 4) return false;
for (const char c : group) {
if (hex_value(c) < 0) return false;
}
return true;
}
// Weight of one ":"-run field: 1 for a hextet, 2 for a trailing
// embedded IPv4, -1 when malformed.
constexpr int ipv6_field_weight(std::string_view field,
bool allow_ipv4_tail) noexcept {
const bool ipv4_tail = allow_ipv4_tail
&& field.find('.') != std::string_view::npos;
if (ipv4_tail) return valid_ipv4(field) ? 2 : -1;
return valid_ipv6_group(field) ? 1 : -1;
}
// Group count of a ":"-separated run of groups; -1 when malformed. An
// empty run (either side of "::") contributes 0. When allow_ipv4_tail
// is set, a trailing dotted-quad field counts as two groups.
constexpr int count_ipv6_groups(std::string_view run,
bool allow_ipv4_tail) noexcept {
if (run.empty()) return 0;
int groups = 0;
std::size_t pos = 0;
for (;;) {
const std::size_t colon = run.find(':', pos);
const std::size_t width =
colon == std::string_view::npos ? std::string_view::npos
: colon - pos;
const std::string_view field = run.substr(pos, width);
const bool tail = allow_ipv4_tail
&& colon == std::string_view::npos;
const int weight = ipv6_field_weight(field, tail);
if (weight < 0) return -1;
groups += weight;
if (colon == std::string_view::npos) break;
pos = colon + 1;
if (pos == run.size()) return -1; // trailing ':' inside the run
}
return groups;
}
// Numeric IPv6 literal per RFC 4291 §2.2, without a zone index: at most
// one "::" compression, at most eight groups, optional trailing
// embedded IPv4.
constexpr bool valid_ipv6(std::string_view v) noexcept {
if (v.empty()) return false;
const std::size_t compressed = v.find("::");
if (compressed != std::string_view::npos
&& v.find("::", compressed + 1) != std::string_view::npos) {
return false; // more than one "::"
}
if (compressed == std::string_view::npos) {
return count_ipv6_groups(v, true) == 8;
}
const int head = count_ipv6_groups(v.substr(0, compressed), false);
const int tail = count_ipv6_groups(v.substr(compressed + 2), true);
if (head < 0 || tail < 0) return false;
return head + tail <= 8;
}
// V2: a listener address is "" or "*" (any local address) or a numeric
// IPv4/IPv6 literal.
constexpr bool valid_listen_address(std::string_view address) noexcept {
return address.empty() || address == "*"
|| valid_ipv4(address) || valid_ipv6(address);
}
// V4: workers == 0 means auto and is always valid.
inline http::outcome check_workers(std::size_t workers) {
if (workers > max_workers) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: workers " + std::to_string(workers)
+ " exceeds the maximum of " + std::to_string(max_workers));
}
return http::outcome::okay();
}
// V5: one timeout field must be positive and within max_timeout.
inline http::outcome check_timeout(std::string_view name,
std::chrono::milliseconds value) {
if (value <= std::chrono::milliseconds::zero()) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: timeout '" + std::string(name)
+ "' must be positive");
}
if (value > max_timeout) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: timeout '" + std::string(name) + "' of "
+ std::to_string(value.count())
+ "ms exceeds the maximum of "
+ std::to_string(max_timeout.count()) + "ms");
}
return http::outcome::okay();
}
// V5: the whole timeout inventory.
inline http::outcome check_timeouts(const timeout_options& timeouts) {
const std::pair<std::string_view, std::chrono::milliseconds> fields[] = {
{"handshake", timeouts.handshake},
{"header", timeouts.header},
{"body_idle", timeouts.body_idle},
{"suspension", timeouts.suspension},
{"write_idle", timeouts.write_idle},
{"ws_close", timeouts.ws_close},
{"drain", timeouts.drain},
};
for (const auto& field : fields) {
if (const http::outcome result =
check_timeout(field.first, field.second);
!result.ok()) {
return result;
}
}
return http::outcome::okay();
}
// V6: every capacity in the server-scope budget must be positive and
// within its documented ceiling.
inline http::outcome check_budget(const budget_limits& limits) {
for (std::size_t i = 0; i < resource_count; ++i) {
const auto kind = static_cast<resource>(i);
const std::size_t capacity = limits.get(kind);
if (capacity == 0 || capacity > max_capacity(kind)) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: budget '" + std::string(to_string(kind))
+ "' capacity must be positive and at most "
+ std::to_string(max_capacity(kind)));
}
}
return http::outcome::okay();
}
constexpr bool same_listener(const listener_options& a,
const listener_options& b) noexcept {
return a.address == b.address && a.port == b.port && a.tls == b.tls;
}
// V2 and V3: every listener address is well-formed and no two listeners
// are identical.
inline http::outcome check_listeners(
const std::vector<listener_options>& listeners) {
for (std::size_t i = 0; i < listeners.size(); ++i) {
if (!valid_listen_address(listeners[i].address)) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: listener " + std::to_string(i)
+ " address '" + listeners[i].address
+ "' is not \"\", \"*\", or a numeric IP literal");
}
for (std::size_t j = 0; j < i; ++j) {
if (same_listener(listeners[i], listeners[j])) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: listener " + std::to_string(i)
+ " duplicates listener " + std::to_string(j));
}
}
}
return http::outcome::okay();
}
constexpr bool multi_version_enabled(const protocol_set& protocols) noexcept {
return protocols.contains(http::protocol::http_2)
|| protocols.contains(http::protocol::http_3);
}
// V7 and V8: with no provider selected, neither HTTP/2 nor HTTP/3 can
// be enabled, no profile may be named, and no listener may request TLS.
inline http::outcome check_tls_off(
const tls_options& tls, const protocol_set& protocols,
const std::vector<listener_options>& listeners) {
if (tls.provider != tls_provider::none) {
return http::outcome::okay();
}
if (multi_version_enabled(protocols)) {
return http::outcome(
http::outcome_code::not_supported,
"server_options: HTTP/2 and HTTP/3 require a TLS provider,"
" which is not selected");
}
if (tls.profile != tls_profile::none) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: a TLS profile requires a TLS provider");
}
for (std::size_t i = 0; i < listeners.size(); ++i) {
if (listeners[i].tls) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: listener " + std::to_string(i)
+ " requests TLS but no TLS provider is selected");
}
}
return http::outcome::okay();
}
// V9: selecting the system provider requires naming a profile.
inline http::outcome check_provider_profile_pair(const tls_options& tls) {
if (tls.provider == tls_provider::system_default
&& tls.profile == tls_profile::none) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: the system_default TLS provider requires a"
" TLS profile");
}
return http::outcome::okay();
}
// V10: the external PSK profile negotiates only over HTTP/1 in v3.
inline http::outcome check_psk_protocols(
const tls_options& tls, const protocol_set& protocols) {
if (tls.profile == tls_profile::external_psk
&& multi_version_enabled(protocols)) {
return http::outcome(
http::outcome_code::not_supported,
"server_options: the external_psk TLS profile is limited to"
" HTTP/1 and cannot serve HTTP/2 or HTTP/3");
}
return http::outcome::okay();
}
// V11: a listener that does not speak TLS itself is reachable only over
// HTTP/1.x, so the protocol set must leave some HTTP/1.x enabled.
inline http::outcome check_plaintext_listeners(
const protocol_set& protocols,
const std::vector<listener_options>& listeners) {
const bool h1_available = protocols.contains(http::protocol::http_1_0)
|| protocols.contains(http::protocol::http_1_1);
if (h1_available) {
return http::outcome::okay();
}
for (std::size_t i = 0; i < listeners.size(); ++i) {
if (!listeners[i].tls) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: listener " + std::to_string(i)
+ " does not use TLS and no HTTP/1.x protocol is"
" enabled");
}
}
return http::outcome::okay();
}
// V12 grammar, header-resident by necessity: server_options must stay
// compilable and linkable without the library (consumer_v3_server is
// the proof), so the check reuses this header's own constexpr literal
// validators over the SAME grammar the live store parses -- exact
// literals, one trailing '*' on a dotted quad, one '/n' within the
// family's bit count, no middle wildcards, no v6 wildcard (CIDR is
// the compression form). listen() turns any residual divergence
// between this judgment and the store's parse into a typed failure
// instead of a silently dropped entry.
// A '/n' suffix: one to three digits, no leading zero unless exactly
// "0", at most @p limit.
constexpr bool valid_prefix_bits(std::string_view digits,
unsigned limit) noexcept {
if (digits.empty() || digits.size() > 3) return false;
if (digits.size() > 1 && digits[0] == '0') return false;
unsigned value = 0;
for (const char c : digits) {
if (!is_digit(c)) return false;
value = value * 10u + static_cast<unsigned>(c - '0');
}
return value <= limit;
}
// "a.b.c.*": three valid dotted-quad fields then the wildcard.
constexpr bool valid_v4_wildcard(std::string_view v) noexcept {
if (!v.ends_with(".*")) return false;
const std::string_view head = v.substr(0, v.size() - 1);
std::size_t pos = 0;
for (int field = 0; field < 3; ++field) {
const std::size_t dot = head.find('.', pos);
if (dot == std::string_view::npos) return false;
if (!valid_ipv4_field(head.substr(pos, dot - pos))) return false;
pos = dot + 1;
}
return pos == head.size();
}
// One pattern spelling per the net vocabulary's grammar.
constexpr bool valid_peer_pattern(std::string_view v) noexcept {
if (v.empty() || (v.find('*') != std::string_view::npos
&& !v.ends_with(".*"))) {
return false;
}
const std::size_t slash = v.find('/');
const std::string_view literal =
slash == std::string_view::npos ? v : v.substr(0, slash);
const bool v6 = literal.find(':') != std::string_view::npos;
if (slash != std::string_view::npos
&& (slash == 0
|| !valid_prefix_bits(v.substr(slash + 1),
v6 ? 128u : 32u))) {
return false;
}
if (!v6 && valid_v4_wildcard(v)) return slash == std::string_view::npos;
// A v4-mapped v6 literal stays an exact pattern in the store; the
// store rejects a mapped CIDR, so does the gate.
if (v6 && slash != std::string_view::npos
&& literal.substr(0, 7) == "::ffff:") {
return false;
}
return v6 ? valid_ipv6(literal) : valid_ipv4(literal);
}
// V12: every peer-policy entry must be a valid pattern spelling.
inline http::outcome check_peer_policy(
const std::vector<std::string>& entries, const char* list) {
for (const std::string& entry : entries) {
if (!valid_peer_pattern(entry)) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: peer policy " + std::string(list)
+ " entry '" + entry + "' is not an address,"
" trailing-wildcard, or CIDR spelling");
}
}
return http::outcome::okay();
}
// V7-V11: provider, profile, protocol, and per-listener TLS flags must
// combine into a servable configuration.
inline http::outcome check_tls_protocol_combination(
const tls_options& tls, const protocol_set& protocols,
const std::vector<listener_options>& listeners) {
if (const http::outcome result = check_tls_off(tls, protocols, listeners);
!result.ok()) {
return result;
}
if (const http::outcome result = check_provider_profile_pair(tls);
!result.ok()) {
return result;
}
if (const http::outcome result = check_psk_protocols(tls, protocols);
!result.ok()) {
return result;
}
if (const http::outcome result =
check_plaintext_listeners(protocols, listeners);
!result.ok()) {
return result;
}
return http::outcome::okay();
}
} // namespace detail
// The single backend-neutral configuration surface for a v3 server
// (PRD-V3N-REQ-014): listeners, concurrency, timeouts, budgets, TLS
// provider/profile, and enabled protocol versions. No backend flags
// appear here (DR-V3-001); the optional provider is chosen at build
// time and selected through tls_provider.
//
// validate() is the pre-listen gate (PRD-V3N-REQ-016): every option
// combination is judged, purely, before a listener accepts any
// connection. Validation performs no I/O, opens no descriptor, reads
// no environment, and is idempotent.
class server_options {
public:
server_options() = default;
// Appends a listener. No validation happens here; add_listener
// defers every judgment to validate().
void add_listener(listener_options listener) {
listeners_.push_back(std::move(listener));
}
std::size_t listener_count() const noexcept { return listeners_.size(); }
// Precondition: i < listener_count().
const listener_options& listener(std::size_t i) const noexcept {
return listeners_[i];
}
concurrency_options& concurrency() noexcept { return concurrency_; }
const concurrency_options& concurrency() const noexcept {
return concurrency_;
}
timeout_options& timeouts() noexcept { return timeouts_; }
const timeout_options& timeouts() const noexcept { return timeouts_; }
tls_options& tls() noexcept { return tls_; }
const tls_options& tls() const noexcept { return tls_; }
protocol_set& protocols() noexcept { return protocols_; }
const protocol_set& protocols() const noexcept { return protocols_; }
budget_limits& budgets() noexcept { return budgets_; }
const budget_limits& budgets() const noexcept { return budgets_; }
// TASK-118 (plan D3): construction-time custom error-page
// factories, the v3 equivalents of the v2 not_found_handler and
// method_not_allowed_handler aliases. Set before the server is
// constructed (factories, not runtime bus seats: the aliases are
// gone in v3). An unset factory means the v2 default page. The 405
// Allow header is appended by the engine on every method-mismatch
// response, custom or default. Copies of server_options share the
// installed factories.
using response_factory =
concurrency::unique_function<hook_response(const http::request_head&)>;
void not_found_response(response_factory factory) {
not_found_response_ =
std::make_shared<const response_factory>(std::move(factory));
}
// Null when unset (the v2 default page serves).
const std::shared_ptr<const response_factory>& not_found_response()
const noexcept {
return not_found_response_;
}
void method_not_allowed_response(response_factory factory) {
method_not_allowed_response_ =
std::make_shared<const response_factory>(std::move(factory));
}
// Null when unset (the v2 default page serves).
const std::shared_ptr<const response_factory>&
method_not_allowed_response() const noexcept {
return method_not_allowed_response_;
}
// Judges the whole configuration. Returns an ok outcome, or exactly
// one typed failure whose diagnostic names the violated rule or
// bound. Pure, idempotent, and callable on a const object.
http::outcome validate() const {
if (listeners_.empty()) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: at least one listener is required before"
" listening");
}
if (listeners_.size() > max_listeners) {
return http::outcome(
http::outcome_code::invalid_argument,
"server_options: listener count "
+ std::to_string(listeners_.size())
+ " exceeds the maximum of "
+ std::to_string(max_listeners));
}
if (const http::outcome result = detail::check_listeners(listeners_);
!result.ok()) {
return result;
}
if (const http::outcome result =
detail::check_workers(concurrency_.workers);
!result.ok()) {
return result;
}
if (const http::outcome result = detail::check_timeouts(timeouts_);
!result.ok()) {
return result;
}
if (const http::outcome result = detail::check_budget(budgets_);
!result.ok()) {
return result;
}
if (const http::outcome result = detail::check_tls_protocol_combination(
tls_, protocols_, listeners_);
!result.ok()) {
return result;
}
if (const http::outcome result =
detail::check_peer_policy(peers_.deny, "deny");
!result.ok()) {
return result;
}
if (const http::outcome result =
detail::check_peer_policy(peers_.allow, "allow");
!result.ok()) {
return result;
}
return http::outcome::okay();
}
// TASK-119: the peer policy stance, seeded into the live policy
// at listen().
peer_policy_options& peer_policy() noexcept { return peers_; }
const peer_policy_options& peer_policy() const noexcept {
return peers_;
}
private:
std::vector<listener_options> listeners_;
concurrency_options concurrency_;
timeout_options timeouts_;
tls_options tls_;
protocol_set protocols_;
budget_limits budgets_;
peer_policy_options peers_;
std::shared_ptr<const response_factory> not_found_response_;
std::shared_ptr<const response_factory> method_not_allowed_response_;
};
} // namespace server
} // namespace httpserver
#endif // SRC_HTTPSERVER_SERVER_OPTIONS_HPP_