Stroika Library 3.0d18
 
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 *
162 * Note collecting statistics costs a modest amount of memory and time.
163 */
165
166 /**
167 * This controls the time used to compute field statistics().fConnections.fConnectionsPiningForTheFjords
168 */
170
171 /**
172 * The number of new TCP connections the kernel will buffer before the application has a chance to accept.
173 *
174 * This can typically be left unset, and defaults to be based on fMaxConnections.
175 *
176 * The default for tcp backlog is a little less than max # of connections. What makes
177 * sense depends on ratio of incoming connections to the lifetime of those calls. If high, make this more than
178 * number of connections. If low, then can be less than max# of connections.
179 *
180 * @see http://man7.org/linux/man-pages/man2/listen.2.html
181 */
182 optional<unsigned int> fTCPBacklog;
183
184 /**
185 * \brief controls whether to use Transfer-Coding: chunked or not
186 *
187 * \see WebServer::Connection::Options::fAutomaticTransferChunkSize
188 * \see WebServer::Response::automaticTransferChunkSize
189 */
191
192 /**
193 * \brief override the set of compression encodings the WebServer supports (default is all the Stroika implementation is built to support)
194 *
195 * \see WebServer::Connection::Options::fSupportedCompressionEncodings
196 */
197 optional<Containers::Set<HTTP::ContentEncoding>> fSupportedCompressionEncodings;
198
199 static constexpr unsigned int kDefault_MaxConnections{25};
200 static constexpr Socket::BindFlags kDefault_BindFlags{};
201 static inline const Headers kDefault_Headers{Iterable<KeyValuePair<String, String>>{{IO::Network::HTTP::HeaderName::kServer, "Stroika/3.0"sv}}};
202 static inline const CORSOptions kDefault_CORS{[] () { return kDefault_CORSOptions; }()};
203 static constexpr bool kDefault_AutoComputeETagResponse{true};
204 static constexpr Duration kDefault_AutomaticTCPDisconnectOnClose{2.0s};
205 static constexpr optional<int> kDefault_Linger{nullopt}; // intentionally optional-valued
206 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)
207 };
208 static const Options kDefaultOptions;
209
210 public:
211 /**
212 */
213 ConnectionManager (const SocketAddress& bindAddress, const Sequence<Route>& routes, const Options& options = kDefaultOptions);
214 ConnectionManager (const Traversal::Iterable<SocketAddress>& bindAddresses, const Sequence<Route>& routes, const Options& options = kDefaultOptions);
215 ConnectionManager (const ConnectionManager&) = delete;
216#if qStroika_Foundation_Debug_DefaultTracingOn
217 ~ConnectionManager ();
218#else
219 ~ConnectionManager () = default;
220#endif
221
222 public:
223 nonvirtual ConnectionManager& operator= (const ConnectionManager&) = delete;
224
225 private:
227 decltype ([] (const Connection::Stats& t) { return t.fSocketID; })>;
228
229 public:
231
232 public:
233 /**
234 * Get the list of interceptors after the private ConnectionManager interceptors (e.g. router).
235 * @see beforeInterceptors
236 *
237 * @see earlyInterceptors, beforeInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
238 *
239 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
240 */
242
243 public:
244 /**
245 * Get the list of interceptors before the private ConnectionManager interceptors (e.g. router).
246 *
247 * @see earlyInterceptors, afterInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
248 *
249 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
250 */
252
253 public:
254 /**
255 * \brief Return the socket addresses the webserver (connection manager) is listening on (to serve web content).
256 */
258
259 public:
260 /**
261 * Each connection maybe active (currently engaged in the threadpool processing) or inactive (waited on via select/epoll).
262 * To see just summary statistics, use the statistics() property () of this class.
263 */
265
266 public:
267 /**
268 * This defaults to @DefaultFaultInterceptor, but can be set to 'missing' or any other fault handler. Not also - that
269 * all interceptors can engage in fault handling. This is just meant to provide a simple one-stop-shop for how to
270 * handle faults in one place.
271 */
273
274 public:
275 /**
276 * Get/Set the list of interceptors early interceptors. These default to:
277 * earlyInterceptors += ServerHeadersInterceptor_{serverHeader, corsSupportMode};
278 * if (defaultFaultHandler) {
279 * earlyInterceptors += *defaultFaultHandler;
280 * }
281 *
282 * @see beforeInterceptors, afterInterceptors, AddInterceptor, RemoveInterceptor to maintain the list of interceptors
283 *
284 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
285 */
287
288 public:
289 /**
290 * Returns the 'effective' options after applying defaults, not (generally) the original options.
291 */
293
294 public:
295 /**
296 * Basic statistics about the state of the ConnectionManager (webserver).
297 */
298 struct Statistics {
299 /**
300 */
301 struct ThreadPool : Execution::ThreadPool::Statistics {
302 size_t fThreadEntryCount{};
303
304 /**
305 * See Characters::ToString ()
306 */
307 nonvirtual Characters::String ToString () const;
308 };
309 ThreadPool fThreadPool;
310
311 /**
312 * NOTE - call 'connections' property to get full details... - not summary stats
313 */
315 /**
316 * Number of sockets with TCP connections between the server web server clients.
317 */
319
320 /**
321 * The subset of open connections where there is current activity, such as reading headers,
322 * or processing the body of the request, or sending the response (a thread allocated to doing work for this connection).
323 */
325
326 /**
327 * Each connection that still exists has been open for a given amount of time. Median of those.
328 */
330
331 /**
332 * Each connection will in general serve many requests (due to connection keep-alives). This computes
333 * the median duration of currently open requests. @todo CLARIFY
334 *
335 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
336 */
338
339 /**
340 * Each connection will in general serve many requests (due to connection keep-alives). This computes
341 * the median duration of currently active connection requests.
342 *
343 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
344 */
346
347 /**
348 * Count the number of currently running requests which have taken longer than options.fConnectionPiningForTheFjordsDelay
349 *
350 * This can help to detect deadlocked/or problematically slow web service APIs.
351 *
352 * If this number is non-zero, try the connections () property, and look through for connections with problematic times.
353 *
354 * \note REQUIRES qStroika_Framework_WebServer_Connection_TrackExtraStats (maybe @todo use options.fCollectStats)
355 */
357
358 /**
359 * See Characters::ToString ()
360 */
361 nonvirtual Characters::String ToString () const;
362 };
363
364 /*
365 * \brief statistics about current and recent connections and request durations.
366 *
367 * \see connections() property for more details about currently connected sockets.
368 */
369 ConnectionStatistics fConnections;
370
371 /**
372 * See Characters::ToString ()
373 */
374 nonvirtual Characters::String ToString () const;
375 };
376
377 public:
378 /**
379 * Then this can be used to fetch the current thread pool statistics.
380 *
381 * \note set options.fCollectStatistics = true to use this most effectively - some information maybe missing if this is not set.
382 */
384
385 private:
386 nonvirtual Statistics ComputeStatistics_ () const;
387
388 public:
389 /**
390 * These 'before' and 'after' values are relative to the router, which towards the end of the chain.
391 *
392 * \note ordering is earlyInterceptors => beforeInterceptors => router => afterInterceptors;
393 */
395 ePrependsToEarly,
396 ePrepend,
397 eAppend,
398 eAfterBeforeInterceptors,
399 };
400 using InterceptorAddRelativeTo::eAfterBeforeInterceptors;
401 using InterceptorAddRelativeTo::eAppend;
402 using InterceptorAddRelativeTo::ePrepend;
403 using InterceptorAddRelativeTo::ePrependsToEarly;
404
405 public:
406 /**
407 */
408 nonvirtual void AddInterceptor (const Interceptor& i, InterceptorAddRelativeTo relativeTo);
409
410 public:
411 /**
412 */
413 nonvirtual void RemoveInterceptor (const Interceptor& i);
414
415 public:
416 /**
417 */
418 nonvirtual void AbortConnection (const shared_ptr<Connection>& conn);
419
420 private:
421 nonvirtual void DeriveConnectionDefaultOptionsFromEffectiveOptions_ ();
422
423 private:
424 nonvirtual void onConnect_ (const ConnectionOrientedStreamSocket::Ptr& s);
425
426 private:
427 nonvirtual void WaitForReadyConnectionLoop_ ();
428
429 private:
430 // Inactive connections are those we are waiting (select/epoll) for incoming data; these are stored in fInactiveSockSetPoller_
431 nonvirtual Collection<shared_ptr<Connection>> GetInactiveConnections_ () const;
432
433 private:
434 nonvirtual void ReplaceInEarlyInterceptor_ (const optional<Interceptor>& oldValue, const optional<Interceptor>& newValue);
435
436 private:
437 Options fEffectiveOptions_;
438 Traversal::Iterable<SocketAddress> fBindings_; // just to return bindings API
439#if qCompilerAndStdLib_RecuriveTypeOrFunctionDependencyTooComplex_Buggy
440 // BWA not too bad cuz ConnectionManager(const ConnectionManager&)=delete and op= as well.
441 shared_ptr<Connection::Options> fUseDefaultConnectionOptions_BWA_{make_shared<Connection::Options> ()};
442 Connection::Options& fUseDefaultConnectionOptions_{*fUseDefaultConnectionOptions_BWA_};
443#else
444 Connection::Options fUseDefaultConnectionOptions_;
445#endif
450 Execution::Synchronized<optional<Duration>> fAutomaticTCPDisconnectOnClose_;
451 Router fRouter_;
452
453 // Active connections are those actively in the readheaders/readbody, dispatch/handle code
455
456 struct MyWaitForIOReady_Traits_ {
457 using HighLevelType = shared_ptr<Connection>;
458 static inline auto GetSDKPollable (const HighLevelType& t)
459 {
460 return t->socket ().GetNativeSocket ();
461 }
462 };
463 // No need to lock fInactiveSockSetPoller_ since its internally synchronized;
464 Execution::UpdatableWaitForIOReady<shared_ptr<Connection>, MyWaitForIOReady_Traits_> fInactiveSockSetPoller_{};
465
466 /*
467 * SUBTLE DATA MEMBER ORDERING NOTE!
468 * We count on the THREADS that run and manipulate all the above data members are all listed AFTER those data
469 * members in this object. This is just for the convenient ordering that imposes on construction and destruction:
470 * the threads (declared below) are automatically shutdown on destruction before the data they reference (above)
471 *
472 * Same with the listener, as this is basically a thread invoking calls on the above data members.
473 */
474
475 // we may eventually want two thread pools - one for managing bookkeeping/monitoring harvests, and one for actually handling
476 // connections. Or maybe a single thread for the bookkeeping, and the pool for handling ongoing connections?
477 //
478 // But for now - KISS
479 //
480 // Note - for now - we don't even handle 'accepting' connections in the threadpool!!! - just one thread
481 Execution::ThreadPool fActiveConnectionThreads_;
482
483 Execution::Thread::CleanupPtr fWaitForReadyConnectionThread_{Execution::Thread::CleanupPtr::eAbortBeforeWaiting};
484
485 // Note: this must be declared after the threadpool so its shutdown on destruction before the thread pool, and doesn't try to launch
486 // new tasks into an already destroyed threadpool.
487 IO::Network::Listener fListener_;
488 };
489
490 inline const ConnectionManager::Options ConnectionManager::kDefaultOptions;
491
492}
493
494/*
495 ********************************************************************************
496 ***************************** Implementation Details ***************************
497 ********************************************************************************
498 */
499#include "ConnectionManager.inl"
500
501#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 ...
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.
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