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