Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ConnectionManager.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Framework_WebServer_ConnectionManager_h_
5#define _Stroika_Framework_WebServer_ConnectionManager_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9#include <list>
10#include <memory>
11
12#include "Stroika/Foundation/Common/Property.h"
13#include "Stroika/Foundation/Containers/Collection.h"
14#include "Stroika/Foundation/Containers/KeyedCollection.h"
15#include "Stroika/Foundation/Containers/Set.h"
19#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
20#include "Stroika/Foundation/IO/Network/Listener.h"
21#include "Stroika/Foundation/IO/Network/SocketAddress.h"
23
24#include "Stroika/Frameworks/WebServer/CORS.h"
25#include "Stroika/Frameworks/WebServer/Connection.h"
26#include "Stroika/Frameworks/WebServer/Request.h"
27#include "Stroika/Frameworks/WebServer/Response.h"
28#include "Stroika/Frameworks/WebServer/Router.h"
29
30/**
31 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
32 */
33
35
36 using namespace Stroika::Foundation;
40 using Containers::Set;
43 using Time::Duration;
45
46 /**
47 * This class is a useful helper for managing a set of connections. You can start it and stop it
48 * (it maintains internal threads). And you can hand it Connections, along with a set of handlers,
49 * and it will monitor the connections, and when any is ready with more input, it will assign the
50 * appropriate handler to handle the request, and produce the response.
51 *
52 * \note The connection manager supports HTTP keep-alives, to keep the connection open.
53 *
54 * \note Default ordering for interceptors:
55 * interceptors += earlyInterceptors;
56 * interceptors += beforeInterceptors;
57 * interceptors += router;
58 * interceptors += afterInterceptors;
59 *
60 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
61 *
62 * TODO:
63 * @todo We could allow updating most of these parameters. Some easily (like ServerHeader). Some would require stopping the
64 * connections, and rebuilding the list of threads etc (cuz must redo bindings). For now, KISS,
65 * and only allow modifications as needed.
66 */
68 public:
69 /**
70 * \brief ConnectionManager::Options specify things like default headers, caching policies, binding flags (not bindings), thread count
71 */
72 struct Options {
73
74 /**
75 * This is the max number of TCP connections the webserver will allow to keep around, before starting
76 * to reject new connections.
77 *
78 * \note NYI tracking and rejecting extra connections - just used as a hint for other values
79 */
80 optional<unsigned int> fMaxConnections;
81
82 /**
83 * This is basically the number of threads to use. It can be automatically inferred from fMaxConnections
84 * but can be specified explicitly.
85 */
86 optional<unsigned int> fMaxConcurrentlyHandledConnections;
87
88 /**
89 * If bindFlags omitted, it defaults to kDefault_BindFlags
90 */
91 optional<Socket::BindFlags> fBindFlags;
92
93 /**
94 * fDefaultResponseHeaders may be used to specify initial{default} values for some HTTP Response headers.
95 * It can be used for specifying any non-standard HTTP headers to add to all responded (like X-Foobar: value).
96 * It can be used to specify a default 'Server' header.
97 * This is equivalent to adding an interceptor early in the interceptor chain, and calling
98 * response.rwHeaders = *fDefaultResponseHeaders;
99 *
100 * Probably not a good idea to specify things like contentLength, etc here. That may produce bad results.
101 * @todo consider listing a stronger set of requirements for what headings can and cannot be set?)
102 *
103 * This is also a good place to add defaults like:
104 * "Content-Type": DataExchange::InternetMediaTypes::kOctetStream (which was done automatically by Stroika before 2.1b10)
105 */
106 optional<Headers> fDefaultResponseHeaders;
107
108 /**
109 * fDefaultGETResponseHeaders - like fDefaultResponseHeaders - may be used to specify initial{default} values for some HTTP headers,
110 * but it is only applied to GET requests (in addition to those applied from fDefaultResponseHeaders which applied to all responses).
111 *
112 * An example of something that makes sense to apply here would be Cache-Control settings (since these are unneeded on other HTTP methods
113 * typically).
114 */
115 optional<Headers> fDefaultGETResponseHeaders;
116
117 /**
118 * Options for how the HTTP Server handles CORS (mostly HTTP OPTIONS requests)
119 */
120 optional<CORSOptions> fCORS;
121
122 /**
123 * \brief sets the initial value for each Response. Harmless except for the slight performance cost (wont always work) - see Response::autoComputeETag
124 *
125 * defaults to kDefault_AutoComputeETagResponse
126 */
128
129 /**
130 * This feature causes sockets to automatically flush their data - and avoid connection reset - when possible.
131 * This makes the closing socket process more costly and slow, so is optional, but is on by default because it makes
132 * communications more reliable.
133 *
134 * Turn this on - especially - if you see connection reset when clients talk to the Stroika web-server (TCP RST sent).
135 *
136 * \note - this defaults to 2 seconds (kDefault_AutomaticTCPDisconnectOnClose)
137 */
139
140 /**
141 * @see Socket::SetLinger () - SO_LINGER
142 */
143 optional<int> fLinger;
144
145 /**
146 * Is the TCP_NODELAY algorithm being used? Generally - this increases latency, for the benefit of better bandwidth utilization.
147 *
148 * See https://brooker.co.za/blog/2024/05/09/nagle.html for hints about if its right for you?
149 */
150 optional<bool> fTCPNoDelay;
151
152 /**
153 * mostly for debugging - so easier to segregate names of threads if you have
154 * multiple thread pools/connection managers
155 */
156 optional<String> fThreadPoolName;
157
158 /**
159 * Statistics are available (from the statistics()) property whether this is true or false. But this
160 * controls whether or not extra statistics are collected and made available.
161 */
163
164 /**
165 * This controls the time used to compute field statistics().fConnections.fConnectionsPiningForTheFjords
166 */
168
169 /**
170 * The number of new TCP connections the kernel will buffer before the application has a chance to accept.
171 *
172 * This can typically be left unset, and defaults to be based on fMaxConnections.
173 *
174 * The default for tcp backlog is a little less than max # of connections. What makes
175 * sense depends on ratio of incoming connections to the lifetime of those calls. If high, make this more than
176 * number of connections. If low, then can be less than max# of connections.
177 *
178 * @see http://man7.org/linux/man-pages/man2/listen.2.html
179 */
180 optional<unsigned int> fTCPBacklog;
181
182 /**
183 * \brief controls whether to use Transfer-Coding: chunked or not
184 *
185 * \see WebServer::Connection::Options::fAutomaticTransferChunkSize
186 * \see WebServer::Response::automaticTransferChunkSize
187 */
189
190 /**
191 * \brief override the set of compression encodings the WebServer supports (default is all the Stroika implementation is built to support)
192 *
193 * \see WebServer::Connection::Options::fSupportedCompressionEncodings
194 */
195 optional<Containers::Set<HTTP::ContentEncoding>> fSupportedCompressionEncodings;
196
197 static constexpr unsigned int kDefault_MaxConnections{25};
198 static constexpr Socket::BindFlags kDefault_BindFlags{};
199 static inline const Headers kDefault_Headers{Iterable<KeyValuePair<String, String>>{{IO::Network::HTTP::HeaderName::kServer, "Stroika/3.0"sv}}};
200 static inline const CORSOptions kDefault_CORS{[] () { return kDefault_CORSOptions; }()};
201 static constexpr bool kDefault_AutoComputeETagResponse{true};
202 static constexpr Duration kDefault_AutomaticTCPDisconnectOnClose{2.0s};
203 static constexpr optional<int> kDefault_Linger{nullopt}; // intentionally optional-valued
204 static constexpr bool kDefault_TCPNoDelay{true}; // https://brooker.co.za/blog/2024/05/09/nagle.html (defaults to NO DELAY - disable Nagle - because we are carefully to only write once when the response is fully read)
205 };
206 static const Options kDefaultOptions;
207
208 public:
209 /**
210 */
211 ConnectionManager (const SocketAddress& bindAddress, const Sequence<Route>& routes, const Options& options = kDefaultOptions);
212 ConnectionManager (const Traversal::Iterable<SocketAddress>& bindAddresses, const Sequence<Route>& routes, const Options& options = kDefaultOptions);
213 ConnectionManager (const ConnectionManager&) = delete;
214#if qStroika_Foundation_Debug_DefaultTracingOn
215 ~ConnectionManager ();
216#else
217 ~ConnectionManager () = default;
218#endif
219
220 public:
221 nonvirtual ConnectionManager& operator= (const ConnectionManager&) = delete;
222
223 private:
225 decltype ([] (const Connection::Stats& t) { return t.fSocketID; })>;
226
227 public:
229
230 public:
231 /**
232 * Get the list of interceptors after the private ConnectionManager interceptors (e.g. router).
233 * @see beforeInterceptors
234 *
235 * @see earlyInterceptors, beforeInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
236 *
237 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
238 */
240
241 public:
242 /**
243 * Get the list of interceptors before the private ConnectionManager interceptors (e.g. router).
244 *
245 * @see earlyInterceptors, afterInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
246 *
247 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
248 */
250
251 public:
252 /**
253 * \brief Return the socket addresses the webserver (connection manager) is listening on (to serve web content).
254 */
256
257 public:
258 /**
259 * Each connection maybe active (currently engaged in the threadpool processing) or inactive (waited on via select/epoll).
260 * To see just summary statistics, use the statistics() property () of this class.
261 */
263
264 public:
265 /**
266 * This defaults to @DefaultFaultInterceptor, but can be set to 'missing' or any other fault handler. Not also - that
267 * all interceptors can engage in fault handling. This is just meant to provide a simple one-stop-shop for how to
268 * handle faults in one place.
269 */
271
272 public:
273 /**
274 * Get/Set the list of interceptors early interceptors. These default to:
275 * earlyInterceptors += ServerHeadersInterceptor_{serverHeader, corsSupportMode};
276 * if (defaultFaultHandler) {
277 * earlyInterceptors += *defaultFaultHandler;
278 * }
279 *
280 * @see beforeInterceptors, afterInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
281 *
282 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
283 */
285
286 public:
287 /**
288 * Returns the 'effective' options after applying defaults, not (generally) the original options.
289 */
291
292 public:
293 /**
294 * Basic statistics about the state of the ConnectionManager (webserver).
295 */
296 struct Statistics {
297 /**
298 */
299 struct ThreadPool : Execution::ThreadPool::Statistics {
300 size_t fThreadEntryCount{};
301
302 /**
303 * See Characters::ToString ()
304 */
305 nonvirtual Characters::String ToString () const;
306 };
307 ThreadPool fThreadPool;
308
309 /**
310 * NOTE - call 'connections' property to get full details... - not summary stats
311 */
313 /**
314 * Number of sockets with TCP connections between the server web server clients.
315 */
317
318 /**
319 * The subset of open connections where there is current activity, such as reading headers,
320 * or processing the body of the request, or sending the response (a thread allocated to doing work for this connection).
321 */
323
324 /**
325 * Each connection that still exists has been open for a given amount of time. Median of those.
326 */
328
329 /**
330 * Each connection will in general serve many requests (due to connection keep-alives). This computes
331 * the median duration of currently open requests. @todo CLARIFY
332 *
333 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
334 */
336
337 /**
338 * Each connection will in general serve many requests (due to connection keep-alives). This computes
339 * the median duration of currently active connection requests.
340 *
341 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
342 */
344
345 /**
346 * Count the number of currently running requests which have taken longer than options.fConnectionPiningForTheFjordsDelay
347 *
348 * This can help to detect deadlocked/or problematically slow web service APIs.
349 *
350 * If this number is non-zero, try the connections () property, and look through for connections with problematic times.
351 *
352 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
353 */
355
356 /**
357 * See Characters::ToString ()
358 */
359 nonvirtual Characters::String ToString () const;
360 };
361
362 /*
363 * \brief statistics about current and recent connections and request durations.
364 *
365 * \see connections() property for more details about currently connected sockets.
366 */
367 ConnectionStatistics fConnections;
368
369 /**
370 * See Characters::ToString ()
371 */
372 nonvirtual Characters::String ToString () const;
373 };
374
375 public:
376 /**
377 * Then this can be used to fetch the current thread pool statistics.
378 *
379 * \note set options.fCollectStatistics = true to use this most effectively - some information maybe missing if this is not set.
380 */
382
383 private:
384 nonvirtual Statistics ComputeStatistics_ () const;
385
386 public:
387 /**
388 * These 'before' and 'after' values are relative to the router, which towards the end of the chain.
389 *
390 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
391 */
393 ePrependsToEarly,
394 ePrepend,
395 eAppend,
396 eAfterBeforeInterceptors,
397 };
398 using InterceptorAddRelativeTo::eAfterBeforeInterceptors;
399 using InterceptorAddRelativeTo::eAppend;
400 using InterceptorAddRelativeTo::ePrepend;
401 using InterceptorAddRelativeTo::ePrependsToEarly;
402
403 public:
404 /**
405 */
406 nonvirtual void AddInterceptor (const Interceptor& i, InterceptorAddRelativeTo relativeTo);
407
408 public:
409 /**
410 */
411 nonvirtual void RemoveInterceptor (const Interceptor& i);
412
413 public:
414 /**
415 */
416 nonvirtual void AbortConnection (const shared_ptr<Connection>& conn);
417
418 private:
419 nonvirtual void DeriveConnectionDefaultOptionsFromEffectiveOptions_ ();
420
421 private:
422 nonvirtual void onConnect_ (const ConnectionOrientedStreamSocket::Ptr& s);
423
424 private:
425 nonvirtual void WaitForReadyConnectionLoop_ ();
426
427 private:
428 // Inactive connections are those we are waiting (select/epoll) for incoming data; these are stored in fInactiveSockSetPoller_
429 nonvirtual Collection<shared_ptr<Connection>> GetInactiveConnections_ () const;
430
431 private:
432 nonvirtual void ReplaceInEarlyInterceptor_ (const optional<Interceptor>& oldValue, const optional<Interceptor>& newValue);
433
434 private:
435 Options fEffectiveOptions_;
436 Traversal::Iterable<SocketAddress> fBindings_; // just to return bindings API
437#if qCompilerAndStdLib_RecuriveTypeOrFunctionDependencyTooComplex_Buggy
438 // BWA not too bad cuz ConnectionManager(const ConnectionManager&)=delete and op= as well.
439 shared_ptr<Connection::Options> fUseDefaultConnectionOptions_BWA_{make_shared<Connection::Options> ()};
440 Connection::Options& fUseDefaultConnectionOptions_{*fUseDefaultConnectionOptions_BWA_};
441#else
442 Connection::Options fUseDefaultConnectionOptions_;
443#endif
448 Execution::Synchronized<optional<Duration>> fAutomaticTCPDisconnectOnClose_;
449 Router fRouter_;
450
451 // Active connections are those actively in the readheaders/readbody, dispatch/handle code
453
454 struct MyWaitForIOReady_Traits_ {
455 using HighLevelType = shared_ptr<Connection>;
456 static inline auto GetSDKPollable (const HighLevelType& t)
457 {
458 return t->socket ().GetNativeSocket ();
459 }
460 };
461 // No need to lock fInactiveSockSetPoller_ since its internally synchronized;
462 Execution::UpdatableWaitForIOReady<shared_ptr<Connection>, MyWaitForIOReady_Traits_> fInactiveSockSetPoller_{};
463
464 /*
465 * SUBTLE DATA MEMBER ORDERING NOTE!
466 * We count on the THREADS that run and manipulate all the above data members are all listed AFTER those data
467 * members in this object. This is just for the convenient ordering that imposes on construction and destruction:
468 * the threads (declared below) are automatically shutdown on destruction before the data they reference (above)
469 *
470 * Same with the listener, as this is basically a thread invoking calls on the above data members.
471 */
472
473 // we may eventually want two thread pools - one for managing bookkeeping/monitoring harvests, and one for actually handling
474 // connections. Or maybe a single thread for the bookkeeping, and the pool for handling ongoing connections?
475 //
476 // But for now - KISS
477 //
478 // Note - for now - we don't even handle 'accepting' connections in the threadpool!!! - just one thread
479 Execution::ThreadPool fActiveConnectionThreads_;
480
481 Execution::Thread::CleanupPtr fWaitForReadyConnectionThread_{Execution::Thread::CleanupPtr::eAbortBeforeWaiting};
482
483 // Note: this must be declared after the threadpool so its shutdown on destruction before the thread pool, and doesn't try to launch
484 // new tasks into an already destroyed threadpool.
485 IO::Network::Listener fListener_;
486 };
487
488 inline const ConnectionManager::Options ConnectionManager::kDefaultOptions;
489
490}
491
492/*
493 ********************************************************************************
494 ***************************** Implementation Details ***************************
495 ********************************************************************************
496 */
497#include "ConnectionManager.inl"
498
499#endif /*_Stroika_Framework_WebServer_ConnectionManager_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
Definition Collection.h:102
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Definition Set.h:105
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Simple wrapper on WaitForIOReady (POSIX select/poll/etc API) - except it allows for the list if polle...
roughly equivalent to Association<String,String>, except that the class is smart about certain keys a...
Definition Headers.h:129
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
Common::ReadOnlyProperty< Statistics > statistics
Common::ReadOnlyProperty< ConnectionStatsCollection > connections
Common::ReadOnlyProperty< Traversal::Iterable< SocketAddress > > bindings
Return the socket addresses the webserver (connection manager) is listening on (to serve web content)...
Common::Property< Sequence< Interceptor > > earlyInterceptors
Common::Property< Sequence< Interceptor > > beforeInterceptors
Common::Property< optional< Interceptor > > defaultErrorHandler
Common::ReadOnlyProperty< const Options & > options
Common::Property< Sequence< Interceptor > > afterInterceptors
ConnectionManager::Options specify things like default headers, caching policies, binding flags (not ...
optional< bool > fAutoComputeETagResponse
sets the initial value for each Response. Harmless except for the slight performance cost (wont alway...
optional< Containers::Set< HTTP::ContentEncoding > > fSupportedCompressionEncodings
override the set of compression encodings the WebServer supports (default is all the Stroika implemen...
optional< size_t > fAutomaticTransferChunkSize
controls whether to use Transfer-Coding: chunked or not