Stroika Library 3.0d23
 
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 if (fMsgHeaderInTextStream.IsAtEOF (Streams::eDontBlock) == true) {
85 return ReadHeadersResult::eIncompleteDeadEnd;
86 }
87 return ReadHeadersResult::eIncompleteButMoreMayBeAvailable;
88 }
89
90 /*
91 * 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)
92 */
93 Request& updatableRequest = this->rwRequest ();
94 {
95 // Read METHOD URL line
96 String line = fMsgHeaderInTextStream.ReadLine ();
97 if (line.length () == 0) {
98#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
99 logMsg ("got EOF from src stream reading headers(incomplete)"sv);
100#endif
101 return ReadHeadersResult::eIncompleteDeadEnd; // could throw here, but this is common enough we don't want the noise in the logs.
102 }
103 Sequence<String> tokens{line.Tokenize ({' '})};
104 if (tokens.size () < 3) {
105 DbgTrace ("tokens={}, line='{}', fMsgHeaderInTextStream={}"_f, tokens, line, fMsgHeaderInTextStream.ToString ());
106 Throw (ClientErrorException{"Bad METHOD Request HTTP line ({})"_f(line)});
107 }
108 updatableRequest.httpMethod = tokens[0];
109 updatableRequest.httpVersion = tokens[2];
110 if (tokens[1].empty ()) {
111 // should check if GET/PUT/DELETE etc...
112 DbgTrace ("tokens={}, line='{}'"_f, tokens, line);
113 Throw (ClientErrorException{"Bad HTTP Request line - missing host-relative URL"sv});
114 }
115 updatableRequest.url = URI::ParseRelative (tokens[1]);
116 if (updatableRequest.httpMethod ().empty ()) {
117 // should check if GET/PUT/DELETE etc...
118 DbgTrace ("tokens={}, line='{}'"_f, tokens, line);
119 static const auto kException_ = ClientErrorException{"Bad METHOD in Request HTTP line"sv};
120 Throw (kException_);
121 }
122 }
123 while (true) {
124 static const String kCRLF_{"\r\n"sv};
125 String line = fMsgHeaderInTextStream.ReadLine ();
126 if (line == kCRLF_ or line.empty ()) {
127 break; // done
128 }
129
130 // add subsequent items to the header map
131 size_t i = line.find (':');
132 if (i == string::npos) {
133 DbgTrace ("line={}"_f, line);
134 Throw (ClientErrorException{"Bad HTTP Request missing colon in headers"sv});
135 }
136 else {
137 String hdr = line.SubString (0, i).Trim ();
138 String value = line.SubString (i + 1).Trim ();
139 updatableRequest.rwHeaders ().Add (hdr, value);
140 }
141 }
142#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
143 logMsg ("ReadHeaders completed normally"sv);
144#endif
145 return ReadHeadersResult::eCompleteGood;
146}
147
148/*
149 ********************************************************************************
150 ************************* WebServer::Connection::Stats *************************
151 ********************************************************************************
152 */
154{
155 StringBuilder sb;
156 sb << "{";
157 sb << "socket: " << fSocketID;
158 sb << ", createdAt: " << fCreatedAt;
159 if (fActive) {
160 if (*fActive) {
161 sb << ", active";
162 }
163 else {
164 sb << ", inactive";
165 }
166 }
167#if qStroika_Framework_WebServer_Connection_TrackExtraStats
169 sb << ", connectionMessageNumber: " << fReadAndProcessMessageNumber;
170 }
171 sb << ", state: " << fState;
172 if (fMostRecentMessage) {
173 sb << ", mostRecentMessage: " << *fMostRecentMessage;
174 }
175 if (fHandlingThread) {
176 if (fActive == true) {
177 sb << ", handlingThread: " << fHandlingThread;
178 }
179 else {
180 sb << ", thread: " << fHandlingThread;
181 }
182 }
184 sb << ", " << fRequestWebMethod << " " << fRequestURI;
185 }
186 if (fRemotePeerAddress) {
187 sb << ", from: " << *fRemotePeerAddress;
188 }
189#endif
190 sb << "}";
191 return sb;
192}
193
194/*
195 ********************************************************************************
196 ***************************** WebServer::Connection ****************************
197 ********************************************************************************
198 */
199Connection::Connection (const ConnectionOrientedStreamSocket::Ptr& s, const InterceptorChain& interceptorChain, const Headers& defaultResponseHeaders,
200 const optional<Headers>& defaultGETResponseHeaders, const optional<bool> autoComputeETagResponse)
201 : Connection{s, Options{.fInterceptorChain = interceptorChain,
202 .fDefaultResponseHeaders = defaultResponseHeaders,
203 .fDefaultGETResponseHeaders = defaultGETResponseHeaders,
204 .fAutoComputeETagResponse = autoComputeETagResponse}}
205{
206}
207
208Connection::Connection (const ConnectionOrientedStreamSocket::Ptr& s, const Options& options)
209 : socket{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> ConnectionOrientedStreamSocket::Ptr {
210 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::socket);
211 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
212 return thisObj->fSocket_;
213 }}
214 , request{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> const Request& {
215 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::request);
216 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
217 return thisObj->fMessage_->request ();
218 }}
219 , response{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> const Response& {
220 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::response);
221 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
222 return thisObj->fMessage_->response ();
223 }}
224 , rwResponse{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Response& {
225 Connection* thisObj = const_cast<Connection*> (qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::rwResponse));
226 AssertExternallySynchronizedMutex::WriteContext declareContext{*thisObj};
227 return thisObj->fMessage_->rwResponse ();
228 }}
229 , stats{[qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> Stats {
230 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::stats);
231 // NO - INTERNALLY SYNCHRONIZED!!! AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
232 // typically called from thread OTHER than the one filling in these variables
233 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
234 TimePointSeconds createdAt{thisObj->fConnectionStartedAt_}; // also similar logic - const
235#if qStroika_Framework_WebServer_Connection_TrackExtraStats
236 Stats2Capture_ statsCapturedDuringMessageProcessing = thisObj->fExtraStats_.load ();
237 using State = Stats::State;
238 State state = [thisObj] () {
239 switch (thisObj->fState_) {
240 case State_Flag_::eNew:
241 return State::eNew;
242 case State_Flag_::eReadingHeaders_Started:
243 return State::eReadingHeaders;
244 case State_Flag_::eFinishedReadingHeaders_Success:
245 return State::eProcessingInterceptorChain;
246 case State_Flag_::eFinishedReadingHeaders_Incomplete:
247 return State::ePausedIncompleteHeaders;
248 case State_Flag_::eFinishedReadingHeaders_Failed:
249 return State::eClosing;
250 case State_Flag_::eInterceptorChain_Start:
251 return State::eProcessingInterceptorChain;
252 case State_Flag_::eInterceptorChain_Complete:
253 return State::eFlushing;
254 case State_Flag_::eFlushing_Start:
255 return State::eFlushing;
256 case State_Flag_::eFlushing_Done:
257 if (thisObj->fKeepAlive_) {
258 return State::eReadyForNextMessage;
259 }
260 else {
261 return State::eClosing;
262 }
263 case State_Flag_::eAborting:
264 return State::eClosing;
265 default:
267 return State::eNew; // silence compiler warning
268 }
269 }();
270#endif
271 Stats stats{
272 .fSocketID = uniqueID,
273 .fCreatedAt = createdAt,
274#if qStroika_Framework_WebServer_Connection_TrackExtraStats
275 .fReadAndProcessMessageNumber = thisObj->fReadAndProcessMessageNumber_,
276 .fState = state,
277 .fMostRecentMessage = statsCapturedDuringMessageProcessing.fMessageStart
278 ? Range<TimePointSeconds>{statsCapturedDuringMessageProcessing.fMessageStart,
279 statsCapturedDuringMessageProcessing.fMessageCompleted}
280 : optional<Range<TimePointSeconds>>{},
281 .fHandlingThread = statsCapturedDuringMessageProcessing.fHandlingThread,
282 .fRemotePeerAddress = statsCapturedDuringMessageProcessing.fPeer,
283 .fRequestWebMethod = statsCapturedDuringMessageProcessing.fWebMethod,
284 .fRequestURI = statsCapturedDuringMessageProcessing.fRequestURI,
285#endif
286 };
287 return stats;
288 }}
290 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] const auto* property) -> optional<HTTP::KeepAlive> {
291 const Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::remainingConnectionLimits);
292 AssertExternallySynchronizedMutex::ReadContext declareContext{*thisObj};
293 return thisObj->fRemaining_;
294 },
295 [qStroika_Foundation_Common_Property_ExtraCaptureStuff] ([[maybe_unused]] auto* property, const auto& remainingConnectionLimits) {
296 Connection* thisObj = qStroika_Foundation_Common_Property_OuterObjPtr (property, &Connection::remainingConnectionLimits);
297 AssertExternallySynchronizedMutex::WriteContext declareContext{*thisObj};
298 thisObj->fRemaining_ = remainingConnectionLimits;
299 }}
300 , fInterceptorChain_{options.fInterceptorChain}
301 , fDefaultResponseHeaders_{options.fDefaultResponseHeaders}
302 , fDefaultGETResponseHeaders_{options.fDefaultGETResponseHeaders}
303 , fAutoComputeETagResponse_{options.fAutoComputeETagResponse}
304 , fSupportedCompressionEncodings_{options.fSupportedCompressionEncodings}
305 , fSocket_{s}
306 , fConnectionStartedAt_{Time::GetTickCount ()}
307{
308 Require (s != nullptr);
309#if USE_NOISY_TRACE_IN_THIS_MODULE_
310 DbgTrace ("Created connection for socket {}"_f, s);
311#endif
312 fSocketStream_ = SocketStream::New (fSocket_);
313#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
314 {
315 String socketName = "{}-{}"_f((long)DateTime::Now ().As<time_t> (), (int)s.GetNativeSocket ());
316 fSocketStream_ = Streams::LoggingInputOutputStream<byte>::New (
317 fSocketStream_,
318 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}-input-trace.txt"_f(socketName)),
319 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}output-trace.txt"_f(socketName)));
320 fLogConnectionState_ = Streams::TextToBinary::Writer::New (
321 IO::FileSystem::FileOutputStream::New (IO::FileSystem::WellKnownLocations::GetTemporary () + "socket-{}-highlevel-trace.txt"_f(socketName)),
322 Streams::TextToBinary::Writer::Format::eUTF8WithoutBOM);
323 }
324#endif
325}
326
327Connection::~Connection ()
328{
329#if USE_NOISY_TRACE_IN_THIS_MODULE_
330 DbgTrace ("Destroying connection for socket {}, message={}"_f, fSocket_, static_cast<const void*> (fMessage_.get ()));
331#endif
332 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
333#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
334 WriteLogConnectionMsg_ (L"DestroyingConnection");
335#endif
336 if (fMessage_ != nullptr) {
337 if (not fMessage_->response ().responseCompleted ()) {
338 IgnoreExceptionsForCall (fMessage_->rwResponse ().Abort ());
339 }
340 Require (fMessage_->response ().responseCompleted ());
341 }
342 /*
343 * When the connection is completed, make sure the socket is closed so that the calling client knows
344 * as quickly as possible. Probably not generally necessary since when the last reference to the socket
345 * goes away, it will also be closed, but that might take a little longer as its held in some object
346 * that hasn't gone away yet.
347 */
348 AssertNotNull (fSocket_);
349 try {
350 fSocket_.Close ();
351 }
352 catch (...) {
353 DbgTrace ("Exception ignored closing socket: {}"_f, current_exception ());
354 }
355}
356
357Connection::ReadAndProcessResult Connection::ReadAndProcessMessage () noexcept
358{
359 AssertExternallySynchronizedMutex::WriteContext declareContext{*this};
360 try {
361#if USE_NOISY_TRACE_IN_THIS_MODULE_
362 Debug::TraceContextBumper ctx{"Connection::ReadAndProcessMessage", "this->socket={}"_f, fSocket_};
363#endif
364 fMessage_ = make_unique<MyMessage_> (fSocket_, fSocketStream_, fDefaultResponseHeaders_, fAutoComputeETagResponse_);
365#if qStroika_Foundation_Debug_AssertExternallySynchronizedMutex_Enabled
366 fMessage_->SetAssertExternallySynchronizedMutexContext (GetSharedContext ());
367#endif
368
369 // readHeaders returns nullopt if it completed successfully (usually the case)
370 auto readHeaders = [&] () -> optional<ReadAndProcessResult> {
371#if qStroika_Framework_WebServer_Connection_TrackExtraStats
372 ++fReadAndProcessMessageNumber_;
373 fState_ = State_Flag_::eReadingHeaders_Started;
374 fExtraStats_.store (Stats2Capture_{
375 .fMessageStart = Time::GetTickCount (), .fPeer = fSocket_.GetPeerAddress (), .fHandlingThread = std::this_thread::get_id ()});
376#endif
377 switch (fMessage_->ReadHeaders (
378#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
379 [this] (const String& i) -> void { WriteLogConnectionMsg_ (i); }
380#endif
381 )) {
382 case MyMessage_::eIncompleteDeadEnd: {
383 DbgTrace ("ReadHeaders failed (socket {}) - incomplete data read from client."_f,
384 fSocket_); // sometimes because the client closed the connection before we could handle: e.g. user in web browser hitting refresh button fast
385#if qStroika_Framework_WebServer_Connection_TrackExtraStats
386 fState_ = State_Flag_::eFinishedReadingHeaders_Failed;
387#endif
388 return eClose; // don't keep-alive - so this closes connection
389 } break;
390 case MyMessage_::eIncompleteButMoreMayBeAvailable: {
391 DbgTrace ("ReadHeaders failed - incomplete header (most likely a DOS attack)."_f);
392#if qStroika_Framework_WebServer_Connection_TrackExtraStats
393 fState_ = State_Flag_::eFinishedReadingHeaders_Incomplete;
394#endif
395 return ReadAndProcessResult::eTryAgainLater;
396 } break;
397 case MyMessage_::eCompleteGood: {
398 // fall through and actually process the request
399#if qStroika_Framework_WebServer_Connection_TrackExtraStats
400 fState_ = State_Flag_::eFinishedReadingHeaders_Success;
401#endif
402 return nullopt;
403 } break;
404 default:
406 return nullopt;
407 }
408 };
409 // First read the HTTP request line, and the headers (and abort this ReadAndProcessMessage attempt if not ready)
410 if (auto r = readHeaders ()) {
411 return *r;
412 }
413
414 // if we get this far, we always complete processing the message
415#if qStroika_Foundation_Debug_AssertionsChecked
416 [[maybe_unused]] auto&& cleanup = Finally ([&] () noexcept { Ensure (fMessage_->response ().responseCompleted ()); });
417#endif
418
419#if qStroika_Framework_WebServer_Connection_TrackExtraStats
420 [[maybe_unused]] auto&& cleanup2 =
421 Finally ([&] () noexcept { fExtraStats_.rwget ().rwref ().fMessageCompleted = Time::GetTickCount (); });
422 fExtraStats_.store (Stats2Capture_{.fMessageStart = Time::GetTickCount (),
423 .fPeer = fSocket_.GetPeerAddress (),
424 .fWebMethod = fMessage_->request ().httpMethod (),
425 .fRequestURI = fMessage_->request ().url (),
426 .fHandlingThread = std::this_thread::get_id ()});
427#endif
428
429 auto applyDefaultsToResponseHeaders = [&] () -> void {
430 if (fDefaultGETResponseHeaders_ and fMessage_->request ().httpMethod () == HTTP::Methods::kGet) {
431 fMessage_->rwResponse ().rwHeaders () += *fDefaultGETResponseHeaders_;
432 }
433 // https://tools.ietf.org/html/rfc7231#section-7.1.1.2 : ...An origin server MUST send a Date header field in all other cases
434 fMessage_->rwResponse ().rwHeaders ().date = DateTime::Now ();
435
436 // @todo can short-circuit the acceptEncoding logic if not bodyHasEntity...(but careful about checking that cuz no content yet
437 // so may need to revisit the bodyHasEntity logic) - just look at METHOD of request and http-status - oh - that cannot check
438 // yet/until done... so maybe need other check like bodyCannotHaveEntity - stuff can check before filled out response?
439 if (optional<HTTP::ContentEncodings> acceptEncoding = fMessage_->request ().headers ().acceptEncoding) {
440 optional<HTTP::ContentEncodings> oBodyEncoding = fMessage_->rwResponse ().bodyEncoding ();
441 auto addCT = [this, &oBodyEncoding] (HTTP::ContentEncoding contentEncoding2Add) {
442 fMessage_->rwResponse ().bodyEncoding = [&] () {
443 if (oBodyEncoding) {
444 auto bc = *oBodyEncoding;
445 bc += contentEncoding2Add;
446 return bc;
447 }
448 else {
449 return HTTP::ContentEncodings{contentEncoding2Add};
450 }
451 }();
452 };
453 bool needBodyEncoding = not oBodyEncoding.has_value ();
454 // prefer deflate over gzip cuz smaller header and otherwise same
455 auto maybeAddIt = [&] (HTTP::ContentEncoding ce) {
456 if (needBodyEncoding and acceptEncoding->Contains (ce) and
457 (fSupportedCompressionEncodings_ == nullopt or fSupportedCompressionEncodings_->Contains (ce))) {
458 addCT (ce);
459 needBodyEncoding = false;
460 }
461 };
462 if constexpr (DataExchange::Compression::Deflate::kSupported) {
464 }
465 if constexpr (DataExchange::Compression::GZip::kSupported) {
466 maybeAddIt (HTTP::ContentEncoding::kGZip);
467 }
468 if constexpr (DataExchange::Compression::ZStd::kSupported) {
469 maybeAddIt (HTTP::ContentEncoding::kZStd);
470 }
471 // @todo add zstd, and others? zstd best probably...
472 }
473
474 if (auto requestedINoneMatch = this->request ().headers ().ifNoneMatch ()) {
475 if (this->response ().autoComputeETag ()) {
476 this->rwResponse ().automaticTransferChunkSize =
477 Response::kNoChunkedTransfer; // cannot start response xfer til we've computed etag (meaning seen all the body bytes)
478 }
479 }
480 };
481 applyDefaultsToResponseHeaders ();
482
483 /*
484 * Now bookkeeping and handling of keepalive headers
485 */
486 auto applyKeepAliveLogic = [&] () -> bool {
487 bool thisMessageKeepAlive = fMessage_->request ().keepAliveRequested;
488 if (thisMessageKeepAlive) {
489
490 // Check for keepalive headers, and handle/merge them appropriately
491 // only meaningful HTTP 1.1 and earlier and only if Connection: keep-alive
492 if (auto keepAliveValue = fMessage_->request ().headers ().keepAlive ()) {
493 this->remainingConnectionLimits = KeepAlive::Merge (this->remainingConnectionLimits (), *keepAliveValue);
494 }
495 // if missing, no limits
496 if (auto oRemaining = remainingConnectionLimits ()) {
497 if (oRemaining->fMessages) {
498 if (oRemaining->fMessages == 0u) {
499 thisMessageKeepAlive = false;
500 }
501 else {
502 oRemaining->fMessages = *oRemaining->fMessages - 1u;
503 }
504 }
505 if (oRemaining->fTimeout) {
506 if (fConnectionStartedAt_ + *oRemaining->fTimeout < Time::GetTickCount ()) {
507 thisMessageKeepAlive = false;
508 }
509 }
510 }
511 }
512 // From https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
513 // HTTP/1.1 applications that do not support persistent connections MUST include the "close" connection option in every message.
514 this->rwResponse ().rwHeaders ().connection = thisMessageKeepAlive ? Headers::eKeepAlive : Headers::eClose;
515 return thisMessageKeepAlive;
516 };
517 bool thisMessageKeepAlive = applyKeepAliveLogic ();
518
519 /**
520 * Delegate to interceptor chain. This is the principle EXTENSION point for the Stroika Framework webserver. This is where you modify
521 * the response somehow or other (typically through routes).
522 */
523 auto invokeInterceptorChain = [&] () {
524#if USE_NOISY_TRACE_IN_THIS_MODULE_
525 DbgTrace ("Handing request {} to interceptor chain"_f, request ().ToString ());
526#endif
527#if qStroika_Framework_WebServer_Connection_TrackExtraStats
528 [[maybe_unused]] auto&& cleanupFlags = Finally ([&] () noexcept {
529 Assert (fState_ < State_Flag_::eInterceptorChain_Complete);
530 fState_ = State_Flag_::eInterceptorChain_Complete;
531 });
532 fState_ = State_Flag_::eInterceptorChain_Start;
533#endif
534#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
535 WriteLogConnectionMsg_ ("Handing request {} to interceptor chain"_f(request ()));
536#endif
537 try {
538 fInterceptorChain_.HandleMessage (*fMessage_);
539 }
540 catch (...) {
541#if USE_NOISY_TRACE_IN_THIS_MODULE_
542 DbgTrace ("Interceptor-Chain caught exception handling message: {}"_f, current_exception ());
543#endif
544#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
545 WriteLogConnectionMsg_ ("Interceptor-Chain caught exception handling message: {}"_f(current_exception ()));
546#endif
547 }
548 };
549 invokeInterceptorChain ();
550
551 auto assureRequestFullyRead = [&] () {
552 if (thisMessageKeepAlive) {
553 // be sure we advance the read pointer over the message body,
554 // lest we start reading part of the previous message as the next message
555
556 // @todo must fix this for support of Transfer-Encoding, but from:
557 //
558 /*
559 * https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
560 * The rules for when a message-body is allowed in a message differ for requests and responses.
561 *
562 * The presence of a message-body in a request is signaled by the inclusion of a Content-Length
563 * or Transfer-Encoding header field in the request's message-headers/
564 */
565 if (request ().headers ().contentLength ()) {
566#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
567 WriteLogConnectionMsg_ (L"msg is keepalive, and have content length, so making sure we read all of request body");
568#endif
569#if USE_NOISY_TRACE_IN_THIS_MODULE_
570 DbgTrace ("Assuring all data read; REQ={}"_f, request ().ToString ());
571#endif
572 // @todo - this can be more efficient in the rare case we ignore the body - but that's rare enough to not matter much
573 (void)fMessage_->rwRequest ().GetBody ();
574 }
575 }
576 };
577 assureRequestFullyRead ();
578
579 /*
580 * By this point, the response has been fully built, and so we can potentially redo the response as a 304-not-modified, by
581 * comparing the ETag with the ifNoneMatch header.
582 */
583 [&] () {
584#if qStroika_Framework_WebServer_Connection_TrackExtraStats
585 fState_ = State_Flag_::eFlushing_Start;
586#endif
587 if (not this->response ().responseStatusSent () and HTTP::IsOK (this->response ().status)) {
588 if (auto requestedINoneMatch = this->request ().headers ().ifNoneMatch ()) {
589 if (auto actualETag = this->response ().headers ().ETag ()) {
590 bool ctm = this->response ().chunkedTransferMode ();
591 if (ctm) {
592 DbgTrace ("Warning - disregarding ifNoneMatch request (though it matched) - cuz in chunked transfer mode"_f);
593 }
594 if (requestedINoneMatch->fETags.Contains (*actualETag) and not ctm) {
595 DbgTrace ("Updating OK response to NotModified (due to ETag match)"_f);
596 this->rwResponse ().status = HTTP::StatusCodes::kNotModified; // this assignment automatically prevents sending data
597 }
598 }
599 }
600 }
601 if (not this->rwResponse ().End ()) {
602 thisMessageKeepAlive = false;
603 }
604#if qStroika_Framework_WebServer_Connection_TrackExtraStats
605 fKeepAlive_ = thisMessageKeepAlive;
606 fState_ = State_Flag_::eFlushing_Done;
607#endif
608#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
609 WriteLogConnectionMsg_ (L"Did GetResponse ().End ()");
610#endif
611 }();
612
613 return thisMessageKeepAlive ? eTryAgainLater : eClose;
614 }
615 catch (...) {
616 DbgTrace ("ReadAndProcessMessage Exception caught ({}), so returning ReadAndProcessResult::eClose"_f, current_exception ());
617 this->rwResponse ().Abort ();
618#if qStroika_Framework_WebServer_Connection_TrackExtraStats
619 fState_ = State_Flag_::eAborting;
620#endif
621 return Connection::ReadAndProcessResult::eClose;
622 }
623}
624
625#if qStroika_Framework_WebServer_Connection_DetailedMessagingLog
626void Connection::WriteLogConnectionMsg_ (const String& msg) const
627{
628 String useMsg = DateTime::Now ().Format () + " -- "sv + msg.Trim ();
629 fLogConnectionState_.WriteLn (useMsg);
630}
631#endif
632
633String Connection::ToString (bool abbreviatedOutput) const
634{
635 AssertExternallySynchronizedMutex::ReadContext declareContext{*this};
636 StringBuilder sb;
637 sb << "{"sv;
638 sb << "Socket: "sv << fSocket_;
639 if (not abbreviatedOutput) {
640 sb << ", Message: "sv << fMessage_;
641 sb << ", Remaining: "sv << fRemaining_;
642 }
643 sb << ", Connection-Started-At: "sv << fConnectionStartedAt_;
644 sb << "}"sv;
645 return sb;
646}
#define AssertNotNull(p)
Definition Assertions.h:334
#define AssertNotReached()
Definition Assertions.h:356
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:317
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)
State
Experimental state information - dont count on details or names. Subject to change....
optional< SocketAddress > fRemotePeerAddress
the address of the client which is talking to the server