Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Frameworks/WebServer/Connection.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <algorithm>
7#include <cstdlib>
8
9#include "Stroika/Foundation/Characters/FloatConversion.h"
11#include "Stroika/Foundation/Characters/String2Int.h"
13#include "Stroika/Foundation/Containers/Common.h"
14#include "Stroika/Foundation/DataExchange/BadFormatException.h"
19#include "Stroika/Foundation/Execution/Throw.h"
22#include "Stroika/Foundation/IO/Network/HTTP/ClientErrorException.h"
23#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
25#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
28
29#include "Connection.h"
30
31using std::byte;
32
33using namespace Stroika::Foundation;
36using namespace Stroika::Foundation::Execution;
37using namespace Stroika::Foundation::Memory;
38using namespace Stroika::Foundation::Traversal;
39using namespace Stroika::Foundation::Time;
40
41using namespace Stroika::Frameworks;
42using namespace Stroika::Frameworks::WebServer;
43
47
48// Comment this in to turn on aggressive noisy DbgTrace in this module
49// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
50
51/*
52 ********************************************************************************
53 ******************** WebServer::Connection::MyMessage_ *************************
54 ********************************************************************************
55 */
56Connection::MyMessage_::MyMessage_ (const ConnectionOrientedStreamSocket::Ptr& socket, const Streams::InputOutputStream::Ptr<byte>& socketStream,
57 const Headers& defaultResponseHeaders, const optional<bool> autoComputeETagResponse)
58 : Message{Request{socketStream}, Response{socket, socketStream, defaultResponseHeaders}, socket.GetPeerAddress ()}
59 , fMsgHeaderInTextStream{HTTP::MessageStartTextInputStreamBinaryAdapter::New (rwRequest ().GetInputStream ())}
60{
61 if (autoComputeETagResponse) {
62 this->rwResponse ().autoComputeETag = *autoComputeETagResponse;
63 }
64}
65
66Connection::MyMessage_::ReadHeadersResult Connection::MyMessage_::ReadHeaders (
67#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
68 const function<void (const String&)>& logMsg
69#endif
70)
71{
72#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
73 logMsg ("Starting ReadHeaders_"sv);
74#endif
75
76 /*
77 * Preflight the request and make sure all the bytes of the header are available. Don't read more than needed.
78 */
79 if (not fMsgHeaderInTextStream.AssureHeaderSectionAvailable ()) {
80#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
81 logMsg ("got fMsgHeaderInTextStream.AssureHeaderSectionAvailable INCOMPLETE"sv);
82#endif
83 return ReadHeadersResult::eIncompleteButMoreMayBeAvailable;
84 }
85
86 /*
87 * At this stage, blocking calls are fully safe - because we've assured above we've seeked to the start of a CRLFCRLF terminated region (or premature EOF)
88 */
89 Request& updatableRequest = this->rwRequest ();
90 {
91 // Read METHOD URL line
92 String line = fMsgHeaderInTextStream.ReadLine ();
93 if (line.length () == 0) {
94#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
95 logMsg ("got EOF from src stream reading headers(incomplete)"sv);
96#endif
97 return ReadHeadersResult::eIncompleteDeadEnd; // could throw here, but this is common enough we don't want the noise in the logs.
98 }
99 Sequence<String> tokens{line.Tokenize ({' '})};
100 if (tokens.size () < 3) {
101 DbgTrace ("tokens={}, line='{}', fMsgHeaderInTextStream={}"_f, tokens, line, fMsgHeaderInTextStream.ToString ());
102 Throw (ClientErrorException{"Bad METHOD Request HTTP line ({})"_f(line)});
103 }
104 updatableRequest.httpMethod = tokens[0];
105 updatableRequest.httpVersion = tokens[2];
106 if (tokens[1].empty ()) {
107 // should check if GET/PUT/DELETE etc...
108 DbgTrace ("tokens={}, line='{}'"_f, tokens, line);
109 Throw (ClientErrorException{"Bad HTTP Request line - missing host-relative URL"sv});
110 }
111 updatableRequest.url = URI::ParseRelative (tokens[1]);
112 if (updatableRequest.httpMethod ().empty ()) {
113 // should check if GET/PUT/DELETE etc...
114 DbgTrace ("tokens={}, line='{}'"_f, tokens, line);
115 static const auto kException_ = ClientErrorException{"Bad METHOD in Request HTTP line"sv};
116 Throw (kException_);
117 }
118 }
119 while (true) {
120 static const String kCRLF_{"\r\n"sv};
121 String line = fMsgHeaderInTextStream.ReadLine ();
122 if (line == kCRLF_ or line.empty ()) {
123 break; // done
124 }
125
126 // add subsequent items to the header map
127 size_t i = line.find (':');
128 if (i == string::npos) {
129 DbgTrace ("line={}"_f, line);
130 Throw (ClientErrorException{"Bad HTTP Request missing colon in headers"sv});
131 }
132 else {
133 String hdr = line.SubString (0, i).Trim ();
134 String value = line.SubString (i + 1).Trim ();
135 updatableRequest.rwHeaders ().Add (hdr, value);
136 }
137 }
138#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
139 logMsg ("ReadHeaders completed normally"sv);
140#endif
141 return ReadHeadersResult::eCompleteGood;
142}
143
144/*
145 ********************************************************************************
146 ************************* WebServer::Connection::Stats *************************
147 ********************************************************************************
148 */
150{
151 StringBuilder sb;
152 sb << "{";
153 sb << "socket: " << fSocketID;
154 sb << ", createdAt: " << fCreatedAt;
155 if (fActive) {
156 if (*fActive) {
157 sb << ", active ";
158 }
159 else {
160 sb << ", inactive ";
161 }
162 }
163#if qStroika_Framework_WebServer_Connection_TrackExtraStats
164 if (fMostRecentMessage) {
165 sb << ", mostRecentMessage: " << *fMostRecentMessage;
166 }
167 if (fHandlingThread) {
168 if (fActive == true) {
169 sb << ", handlingThread: " << fHandlingThread;
170 }
171 else {
172 sb << ", previousThread: " << fHandlingThread;
173 }
174 }
175#endif
176 sb << "}";
177 return sb;
178}
179
180/*
181 ********************************************************************************
182 ***************************** WebServer::Connection ****************************
183 ********************************************************************************
184 */
185Connection::Connection (const ConnectionOrientedStreamSocket::Ptr& s, const InterceptorChain& interceptorChain, const Headers& defaultResponseHeaders,
186 const optional<Headers>& defaultGETResponseHeaders, const optional<bool> autoComputeETagResponse)
187 : Connection{s, Options{.fInterceptorChain = interceptorChain,
188 .fDefaultResponseHeaders = defaultResponseHeaders,
189 .fDefaultGETResponseHeaders = defaultGETResponseHeaders,
190 .fAutoComputeETagResponse = autoComputeETagResponse}}
191{
192}
193
194Connection::Connection (const ConnectionOrientedStreamSocket::Ptr& s, const Options& options)
195 : socket{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> ConnectionOrientedStreamSocket::Ptr {
196 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::socket);
197 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
198 return thisObj->fSocket_;
199 }}
200 , request{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> const Request& {
201 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::request);
202 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
203 return thisObj->fMessage_->request ();
204 }}
205 , response{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> const Response& {
206 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::response);
207 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
208 return thisObj->fMessage_->response ();
209 }}
210 , rwResponse{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Response& {
211 Connection* thisObj = const_cast<Connection*> (qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::rwResponse));
212 AssertExternallySynchronizedMutex::WriteContext declareContext{*thisObj};
213 return thisObj->fMessage_->rwResponse ();
214 }}
215 , stats{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Stats {
216 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::stats);
217 // NO - INTERNALLY SYNCRHONIZED!!! AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
218 auto uniqueID = thisObj->fSocket_.GetNativeSocket (); // safe because fSocket_ is a const Ptr, and GetNativeSocket () is a const method, so never modified and can be safely used without syncrhonization
219 TimePointSeconds createdAt{thisObj->fConnectionStartedAt_}; // also similar logic - const
220#if qStroika_Framework_WebServer_Connection_TrackExtraStats
221 // all this mishigas is due to atomic<optional<...>> stuff not working - sigh - apparently still not allowed in C++23 (26?)
222 DurationSeconds::rep b1 = thisObj->fStartHandleMessage_.load ();
223 DurationSeconds::rep e1 = thisObj->fCompletedHandleMessage_.load ();
224 thread::id tid1 = thisObj->fHandlingThread_.load ();
225 optional<TimePointSeconds> b = b1 != kAtomicTimeSentinel_ ? TimePointSeconds{DurationSeconds{b1}} : optional<TimePointSeconds>{};
226 optional<TimePointSeconds> e = e1 != kAtomicTimeSentinel_ ? TimePointSeconds{DurationSeconds{e1}} : optional<TimePointSeconds>{};
227 optional<thread::id> tid = tid1 == thread::id{} ? optional<thread::id>{} : tid1;
228#endif
229 Stats stats{.fSocketID = uniqueID,
230 .fCreatedAt = createdAt,
231#if qStroika_Framework_WebServer_Connection_TrackExtraStats
232 .fMostRecentMessage = (b or e) ? Range<TimePointSeconds>{b, e} : optional<Range<TimePointSeconds>>{},
233 .fHandlingThread = tid
234#endif
235 };
236 return stats;
237 }}
239 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> optional<HTTP::KeepAlive> {
240 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::remainingConnectionLimits);
241 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
242 return thisObj->fRemaining_;
243 },
244 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, const auto& remainingConnectionLimits) {
245 Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::remainingConnectionLimits);
246 AssertExternallySynchronizedMutex::WriteContext declareContext{*thisObj};
247 thisObj->fRemaining_ = remainingConnectionLimits;
248 }}
249 , fInterceptorChain_{options.fInterceptorChain}
250 , fDefaultResponseHeaders_{options.fDefaultResponseHeaders}
251 , fDefaultGETResponseHeaders_{options.fDefaultGETResponseHeaders}
252 , fAutoComputeETagResponse_{options.fAutoComputeETagResponse}
253 , fSupportedCompressionEncodings_{options.fSupportedCompressionEncodings}
254 , fSocket_{s}
255 , fConnectionStartedAt_{Time::GetTickCount ()}
256{
257 Require (s != nullptr);
258#if USE_NOISY_TRACE_IN_THIS_MODULE_
259 DbgTrace ("Created connection for socket {}"_f, s);
260#endif
261 fSocketStream_ = SocketStream::New (fSocket_);
262#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
263 {
264 String socketName = "{}-{}"_f((long)DateTime::Now ().As<time_t> (), (int)s.GetNativeSocket ());
265 fSocketStream_ = Streams::LoggingInputOutputStream<byte>::New (
266 fSocketStream_,
267 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}-input-trace.txt"_f(socketName)),
268 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}output-trace.txt"_f(socketName)));
269 fLogConnectionState_ = Streams::TextToBinary::Writer::New (
270 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}-highlevel-trace.txt"_f(socketName)),
271 Streams::TextToBinary::Writer::Format::eUTF8WithoutBOM);
272 }
273#endif
274}
275
276Connection::~Connection ()
277{
278#if USE_NOISY_TRACE_IN_THIS_MODULE_
279 DbgTrace ("Destroying connection for socket {}, message={}"_f, fSocket_, static_cast<const void*> (fMessage_.get ()));
280#endif
281 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
282#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
283 WriteLogConnectionMsg_ (L"DestroyingConnection");
284#endif
285 if (fMessage_ != nullptr) {
286 if (not fMessage_->response ().responseCompleted ()) {
287 IgnoreExceptionsForCall (fMessage_->rwResponse ().Abort ());
288 }
289 Require (fMessage_->response ().responseCompleted ());
290 }
291 /*
292 * When the connection is completed, make sure the socket is closed so that the calling client knows
293 * as quickly as possible. Probably not generally necessary since when the last reference to the socket
294 * goes away, it will also be closed, but that might take a little longer as its held in some object
295 * that hasn't gone away yet.
296 */
297 AssertNotNull (fSocket_);
298 try {
299 fSocket_.Close ();
300 }
301 catch (...) {
302 DbgTrace ("Exception ignored closing socket: {}"_f, current_exception ());
303 }
304}
305
306Connection::ReadAndProcessResult Connection::ReadAndProcessMessage () noexcept
307{
308 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
309 try {
310#if USE_NOISY_TRACE_IN_THIS_MODULE_
311 Debug::TraceContextBumper ctx{"Connection::ReadAndProcessMessage", "this->socket={}"_f, fSocket_};
312#endif
313 fMessage_ = make_unique<MyMessage_> (fSocket_, fSocketStream_, fDefaultResponseHeaders_, fAutoComputeETagResponse_);
314#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
315 fMessage_->SetAssertExternallySynchronizedMutexContext (GetSharedContext ());
316#endif
317
318 auto readHeaders = [&] () -> optional<ReadAndProcessResult> {
319 switch (fMessage_->ReadHeaders (
320#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
321 [this] (const String& i) -> void { WriteLogConnectionMsg_ (i); }
322#endif
323 )) {
324 case MyMessage_::eIncompleteDeadEnd: {
325 DbgTrace ("ReadHeaders failed - typically because the client closed the connection before we could handle it (e.g. "
326 "in web browser hitting refresh button fast)."_f);
327 return eClose; // don't keep-alive - so this closes connection
328 } break;
329 case MyMessage_::eIncompleteButMoreMayBeAvailable: {
330 DbgTrace ("ReadHeaders failed - incomplete header (most likely a DOS attack)."_f);
331 return ReadAndProcessResult::eTryAgainLater;
332 } break;
333 case MyMessage_::eCompleteGood: {
334 // fall through and actually process the request
335 return nullopt;
336 } break;
337 default:
339 return nullopt;
340 }
341 };
342 // First read the HTTP request line, and the headers (and abort this ReadAndProcessMessage attempt if not ready)
343 if (auto r = readHeaders ()) {
344 return *r;
345 }
346
347 // if we get this far, we always complete processing the message
348#if qStroika_Foundation_Debug_AssertionsChecked
349 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept { Ensure (fMessage_->response ().responseCompleted ()); });
350#endif
351
352#if qStroika_Framework_WebServer_Connection_TrackExtraStats
353 fStartHandleMessage_ = Time::GetTickCount ().time_since_epoch ().count ();
354 fCompletedHandleMessage_ = kAtomicTimeSentinel_;
355 fHandlingThread_ = std::this_thread::get_id ();
356 [[maybe_unused]] auto&& cleanup2 =
357 Finally ([&] () noexcept { fCompletedHandleMessage_ = Time::GetTickCount ().time_since_epoch ().count (); });
358#endif
359
360 auto applyDefaultsToResponseHeaders = [&] () -> void {
361 if (fDefaultGETResponseHeaders_ and fMessage_->request ().httpMethod () == HTTP::Methods::kGet) {
362 fMessage_->rwResponse ().rwHeaders () += *fDefaultGETResponseHeaders_;
363 }
364 // https://tools.ietf.org/html/rfc7231#section-7.1.1.2 : ...An origin server MUST send a Date header field in all other cases
365 fMessage_->rwResponse ().rwHeaders ().date = DateTime::Now ();
366
367 // @todo can short-circuit the acceptEncoding logic if not bodyHasEntity...(but careful about checking that cuz no content yet
368 // so may need to revisit the bodyHasEntity logic) - just look at METHOD of request and http-status - oh - that cannot check
369 // yet/until done... so maybe need other check like bodyCannotHaveEntity - stuff can check before filled out response?
370 if (optional<HTTP::ContentEncodings> acceptEncoding = fMessage_->request ().headers ().acceptEncoding) {
371 optional<HTTP::ContentEncodings> oBodyEncoding = fMessage_->rwResponse ().bodyEncoding ();
372 auto addCT = [this, &oBodyEncoding] (HTTP::ContentEncoding contentEncoding2Add) {
373 fMessage_->rwResponse ().bodyEncoding = [&] () {
374 if (oBodyEncoding) {
375 auto bc = *oBodyEncoding;
376 bc += contentEncoding2Add;
377 return bc;
378 }
379 else {
380 return HTTP::ContentEncodings{contentEncoding2Add};
381 }
382 }();
383 };
384 bool needBodyEncoding = not oBodyEncoding.has_value ();
385 // prefer deflate over gzip cuz smaller header and otherwise same
386 auto maybeAddIt = [&] (HTTP::ContentEncoding ce) {
387 if (needBodyEncoding and acceptEncoding->Contains (ce) and
388 (fSupportedCompressionEncodings_ == nullopt or fSupportedCompressionEncodings_->Contains (ce))) {
389 addCT (ce);
390 needBodyEncoding = false;
391 }
392 };
393 if constexpr (DataExchange::Compression::Deflate::kSupported) {
395 }
396 if constexpr (DataExchange::Compression::GZip::kSupported) {
397 maybeAddIt (HTTP::ContentEncoding::kGZip);
398 }
399 // @todo add zstd, and others? zstd best probably...
400 }
401
402 if (auto requestedINoneMatch = this->request ().headers ().ifNoneMatch ()) {
403 if (this->response ().autoComputeETag ()) {
404 this->rwResponse ().automaticTransferChunkSize =
405 Response::kNoChunkedTransfer; // cannot start response xfer til we've computed etag (meaning seen all the body bytes)
406 }
407 }
408 };
409 applyDefaultsToResponseHeaders ();
410
411 /*
412 * Now bookkeeping and handling of keepalive headers
413 */
414 auto applyKeepAliveLogic = [&] () -> bool {
415 bool thisMessageKeepAlive = fMessage_->request ().keepAliveRequested;
416 if (thisMessageKeepAlive) {
417
418 // Check for keepalive headers, and handle/merge them appropriately
419 // only meaningful HTTP 1.1 and earlier and only if Connection: keep-alive
420 if (auto keepAliveValue = fMessage_->request ().headers ().keepAlive ()) {
421 this->remainingConnectionLimits = KeepAlive::Merge (this->remainingConnectionLimits (), *keepAliveValue);
422 }
423 // if missing, no limits
424 if (auto oRemaining = remainingConnectionLimits ()) {
425 if (oRemaining->fMessages) {
426 if (oRemaining->fMessages == 0u) {
427 thisMessageKeepAlive = false;
428 }
429 else {
430 oRemaining->fMessages = *oRemaining->fMessages - 1u;
431 }
432 }
433 if (oRemaining->fTimeout) {
434 if (fConnectionStartedAt_ + *oRemaining->fTimeout < Time::GetTickCount ()) {
435 thisMessageKeepAlive = false;
436 }
437 }
438 }
439 }
440 // From https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
441 // HTTP/1.1 applications that do not support persistent connections MUST include the "close" connection option in every message.
442 this->rwResponse ().rwHeaders ().connection = thisMessageKeepAlive ? Headers::eKeepAlive : Headers::eClose;
443 return thisMessageKeepAlive;
444 };
445 bool thisMessageKeepAlive = applyKeepAliveLogic ();
446
447 /**
448 * Delegate to interceptor chain. This is the principle EXTENSION point for the Stroika Framework webserver. This is where you modify
449 * the response somehow or other (typically through routes).
450 */
451 auto invokeInterceptorChain = [&] () {
452#if USE_NOISY_TRACE_IN_THIS_MODULE_
453 DbgTrace ("Handing request {} to interceptor chain"_f, request ().ToString ());
454#endif
455#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
456 WriteLogConnectionMsg_ ("Handing request {} to interceptor chain"_f(request ()));
457#endif
458 try {
459 fInterceptorChain_.HandleMessage (*fMessage_);
460 }
461 catch (...) {
462#if USE_NOISY_TRACE_IN_THIS_MODULE_
463 DbgTrace ("Interceptor-Chain caught exception handling message: {}"_f, current_exception ());
464#endif
465#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
466 WriteLogConnectionMsg_ ("Interceptor-Chain caught exception handling message: {}"_f(current_exception ()));
467#endif
468 }
469 };
470 invokeInterceptorChain ();
471
472 auto assureRequestFullyRead = [&] () {
473 if (thisMessageKeepAlive) {
474 // be sure we advance the read pointer over the message body,
475 // lest we start reading part of the previous message as the next message
476
477 // @todo must fix this for support of Transfer-Encoding, but from:
478 //
479 /*
480 * https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
481 * The rules for when a message-body is allowed in a message differ for requests and responses.
482 *
483 * The presence of a message-body in a request is signaled by the inclusion of a Content-Length
484 * or Transfer-Encoding header field in the request's message-headers/
485 */
486 if (request ().headers ().contentLength ()) {
487#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
488 WriteLogConnectionMsg_ (L"msg is keepalive, and have content length, so making sure we read all of request body");
489#endif
490#if USE_NOISY_TRACE_IN_THIS_MODULE_
491 DbgTrace ("Assuring all data read; REQ={}"_f, request ().ToString ());
492#endif
493 // @todo - this can be more efficient in the rare case we ignore the body - but that's rare enough to not matter much
494 (void)fMessage_->rwRequest ().GetBody ();
495 }
496 }
497 };
498 assureRequestFullyRead ();
499
500 /*
501 * By this point, the response has been fully built, and so we can potentially redo the response as a 304-not-modified, by
502 * comparing the ETag with the ifNoneMatch header.
503 */
504 auto completeResponse = [&] () {
505 if (not this->response ().responseStatusSent () and HTTP::IsOK (this->response ().status)) {
506 if (auto requestedINoneMatch = this->request ().headers ().ifNoneMatch ()) {
507 if (auto actualETag = this->response ().headers ().ETag ()) {
508 bool ctm = this->response ().chunkedTransferMode ();
509 if (ctm) {
510 DbgTrace ("Warning - disregarding ifNoneMatch request (though it matched) - cuz in chunked transfer mode"_f);
511 }
512 if (requestedINoneMatch->fETags.Contains (*actualETag) and not ctm) {
513 DbgTrace ("Updating OK response to NotModified (due to ETag match)"_f);
514 this->rwResponse ().status = HTTP::StatusCodes::kNotModified; // this assignment automatically prevents sending data
515 }
516 }
517 }
518 }
519 if (not this->rwResponse ().End ()) {
520 thisMessageKeepAlive = false;
521 }
522#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
523 WriteLogConnectionMsg_ (L"Did GetResponse ().End ()");
524#endif
525 };
526 completeResponse ();
527
528 return thisMessageKeepAlive ? eTryAgainLater : eClose;
529 }
530 catch (...) {
531 DbgTrace ("ReadAndProcessMessage Exception caught ({}), so returning ReadAndProcessResult::eClose"_f, current_exception ());
532 this->rwResponse ().Abort ();
533 return Connection::ReadAndProcessResult::eClose;
534 }
535}
536
537#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
538void Connection::WriteLogConnectionMsg_ (const String& msg) const
539{
540 String useMsg = DateTime::Now ().Format () + " -- "sv + msg.Trim ();
541 fLogConnectionState_.WriteLn (useMsg);
542}
543#endif
544
545String Connection::ToString (bool abbreviatedOutput) const
546{
547 AssertExternallySynchronizedMutex::ReadContext declareContext{*this};
548 StringBuilder sb;
549 sb << "{"sv;
550 sb << "Socket: "sv << fSocket_;
551 if (not abbreviatedOutput) {
552 sb << ", Message: "sv << fMessage_;
553 sb << ", Remaining: "sv << fRemaining_;
554 }
555 sb << ", Connection-Started-At: "sv << fConnectionStartedAt_;
556 sb << "}"sv;
557 return sb;
558}
#define AssertNotNull(p)
Definition Assertions.h:333
#define AssertNotReached()
Definition Assertions.h:355
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:309
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual size_t length() const noexcept
Definition String.inl:1045
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1592
nonvirtual Containers::Sequence< String > Tokenize() const
Definition String.cpp:1234
nonvirtual size_t find(Character c, size_t startAt=0) const
Definition String.inl:1061
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
ClientErrorException is to capture exceptions caused by a bad (e.g ill-formed) request.
roughly equivalent to Association<String,String>, except that the class is smart about certain keys a...
Definition Headers.h:129
Common::Property< String > httpMethod
typically HTTP::Methods::kGet
nonvirtual PlatformNativeHandle GetNativeSocket() const
Definition Socket.inl:52
static URI ParseRelative(const String &rawRelativeURL)
Definition URI.cpp:150
InputOutputStream is single stream object that acts much as a InputStream::Ptr and an OutputStream::P...
A Connection object represents the state (and socket) for an ongoing, active, HTTP Connection,...
Common::Property< optional< HTTP::KeepAlive > > remainingConnectionLimits
const Common::ReadOnlyProperty< Stats > stats
retrieve stats about this connection, like threads used, start/end times. NB: INTERNALLY SYNCRONIZED
const Common::ReadOnlyProperty< ConnectionOrientedStreamSocket::Ptr > socket
nonvirtual ReadAndProcessResult ReadAndProcessMessage() noexcept
const Common::ReadOnlyProperty< const Request & > request
const Common::ReadOnlyProperty< const Response & > response
nonvirtual String ToString(bool abbreviatedOutput=true) const
nonvirtual void HandleMessage(Message &m) const
this represents a HTTP request object for the WebServer module
CONTAINER::value_type * End(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the end of the co...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
constexpr bool IsOK(Status s)
several status codes considered OK, so check if it is among them
Definition Status.inl:12
Content coding values indicate an encoding transformation that has been or can be applied to an entit...
ConnectionManager::Options specify things like default headers, caching policies, binding flags (not ...