xref: /openbmc/bmcweb/http/http_connection.hpp (revision 60c922df)
1 #pragma once
2 #include "bmcweb_config.h"
3 
4 #include "authorization.hpp"
5 #include "http_response.hpp"
6 #include "http_utility.hpp"
7 #include "logging.hpp"
8 #include "timer_queue.hpp"
9 #include "utility.hpp"
10 
11 #include <boost/algorithm/string.hpp>
12 #include <boost/algorithm/string/predicate.hpp>
13 #include <boost/asio/io_context.hpp>
14 #include <boost/asio/ip/tcp.hpp>
15 #include <boost/asio/ssl/stream.hpp>
16 #include <boost/beast/core/flat_static_buffer.hpp>
17 #include <boost/beast/ssl/ssl_stream.hpp>
18 #include <boost/beast/websocket.hpp>
19 #include <boost/url/url_view.hpp>
20 #include <json_html_serializer.hpp>
21 #include <security_headers.hpp>
22 #include <ssl_key_handler.hpp>
23 
24 #include <atomic>
25 #include <chrono>
26 #include <vector>
27 
28 namespace crow
29 {
30 
31 inline void prettyPrintJson(crow::Response& res)
32 {
33     json_html_util::dumpHtml(res.body(), res.jsonValue);
34 
35     res.addHeader("Content-Type", "text/html;charset=UTF-8");
36 }
37 
38 #ifdef BMCWEB_ENABLE_DEBUG
39 static std::atomic<int> connectionCount;
40 #endif
41 
42 // request body limit size set by the bmcwebHttpReqBodyLimitMb option
43 constexpr unsigned int httpReqBodyLimit =
44     1024 * 1024 * bmcwebHttpReqBodyLimitMb;
45 
46 constexpr uint64_t loggedOutPostBodyLimit = 4096;
47 
48 constexpr uint32_t httpHeaderLimit = 8192;
49 
50 // drop all connections after 1 minute, this time limit was chosen
51 // arbitrarily and can be adjusted later if needed
52 static constexpr const size_t loggedInAttempts =
53     (60 / timerQueueTimeoutSeconds);
54 
55 static constexpr const size_t loggedOutAttempts =
56     (15 / timerQueueTimeoutSeconds);
57 
58 template <typename Adaptor, typename Handler>
59 class Connection :
60     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
61 {
62   public:
63     Connection(Handler* handlerIn,
64                std::function<std::string()>& getCachedDateStrF,
65                detail::TimerQueue& timerQueueIn, Adaptor adaptorIn) :
66         adaptor(std::move(adaptorIn)),
67         handler(handlerIn), getCachedDateStr(getCachedDateStrF),
68         timerQueue(timerQueueIn)
69     {
70         parser.emplace(std::piecewise_construct, std::make_tuple());
71         parser->body_limit(httpReqBodyLimit);
72         parser->header_limit(httpHeaderLimit);
73 
74 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
75         prepareMutualTls();
76 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
77 
78 #ifdef BMCWEB_ENABLE_DEBUG
79         connectionCount++;
80         BMCWEB_LOG_DEBUG << this << " Connection open, total "
81                          << connectionCount;
82 #endif
83     }
84 
85     ~Connection()
86     {
87         res.setCompleteRequestHandler(nullptr);
88         cancelDeadlineTimer();
89 #ifdef BMCWEB_ENABLE_DEBUG
90         connectionCount--;
91         BMCWEB_LOG_DEBUG << this << " Connection closed, total "
92                          << connectionCount;
93 #endif
94     }
95 
96     void prepareMutualTls()
97     {
98         std::error_code error;
99         std::filesystem::path caPath(ensuressl::trustStorePath);
100         auto caAvailable = !std::filesystem::is_empty(caPath, error);
101         caAvailable = caAvailable && !error;
102         if (caAvailable && persistent_data::SessionStore::getInstance()
103                                .getAuthMethodsConfig()
104                                .tls)
105         {
106             adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
107             std::string id = "bmcweb";
108             int ret = SSL_set_session_id_context(
109                 adaptor.native_handle(),
110                 reinterpret_cast<const unsigned char*>(id.c_str()),
111                 static_cast<unsigned int>(id.length()));
112             if (ret == 0)
113             {
114                 BMCWEB_LOG_ERROR << this << " failed to set SSL id";
115             }
116         }
117 
118         adaptor.set_verify_callback([this](
119                                         bool preverified,
120                                         boost::asio::ssl::verify_context& ctx) {
121             // do nothing if TLS is disabled
122             if (!persistent_data::SessionStore::getInstance()
123                      .getAuthMethodsConfig()
124                      .tls)
125             {
126                 BMCWEB_LOG_DEBUG << this << " TLS auth_config is disabled";
127                 return true;
128             }
129 
130             // We always return true to allow full auth flow
131             if (!preverified)
132             {
133                 BMCWEB_LOG_DEBUG << this << " TLS preverification failed.";
134                 return true;
135             }
136 
137             X509_STORE_CTX* cts = ctx.native_handle();
138             if (cts == nullptr)
139             {
140                 BMCWEB_LOG_DEBUG << this << " Cannot get native TLS handle.";
141                 return true;
142             }
143 
144             // Get certificate
145             X509* peerCert =
146                 X509_STORE_CTX_get_current_cert(ctx.native_handle());
147             if (peerCert == nullptr)
148             {
149                 BMCWEB_LOG_DEBUG << this
150                                  << " Cannot get current TLS certificate.";
151                 return true;
152             }
153 
154             // Check if certificate is OK
155             int error = X509_STORE_CTX_get_error(cts);
156             if (error != X509_V_OK)
157             {
158                 BMCWEB_LOG_INFO << this << " Last TLS error is: " << error;
159                 return true;
160             }
161             // Check that we have reached final certificate in chain
162             int32_t depth = X509_STORE_CTX_get_error_depth(cts);
163             if (depth != 0)
164 
165             {
166                 BMCWEB_LOG_DEBUG
167                     << this << " Certificate verification in progress (depth "
168                     << depth << "), waiting to reach final depth";
169                 return true;
170             }
171 
172             BMCWEB_LOG_DEBUG << this
173                              << " Certificate verification of final depth";
174 
175             // Verify KeyUsage
176             bool isKeyUsageDigitalSignature = false;
177             bool isKeyUsageKeyAgreement = false;
178 
179             ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>(
180                 X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr));
181 
182             if (usage == nullptr)
183             {
184                 BMCWEB_LOG_DEBUG << this << " TLS usage is null";
185                 return true;
186             }
187 
188             for (int i = 0; i < usage->length; i++)
189             {
190                 if (KU_DIGITAL_SIGNATURE & usage->data[i])
191                 {
192                     isKeyUsageDigitalSignature = true;
193                 }
194                 if (KU_KEY_AGREEMENT & usage->data[i])
195                 {
196                     isKeyUsageKeyAgreement = true;
197                 }
198             }
199             ASN1_BIT_STRING_free(usage);
200 
201             if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
202             {
203                 BMCWEB_LOG_DEBUG << this
204                                  << " Certificate ExtendedKeyUsage does "
205                                     "not allow provided certificate to "
206                                     "be used for user authentication";
207                 return true;
208             }
209 
210             // Determine that ExtendedKeyUsage includes Client Auth
211 
212             stack_st_ASN1_OBJECT* extUsage =
213                 static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i(
214                     peerCert, NID_ext_key_usage, nullptr, nullptr));
215 
216             if (extUsage == nullptr)
217             {
218                 BMCWEB_LOG_DEBUG << this << " TLS extUsage is null";
219                 return true;
220             }
221 
222             bool isExKeyUsageClientAuth = false;
223             for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
224             {
225                 if (NID_client_auth ==
226                     OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i)))
227                 {
228                     isExKeyUsageClientAuth = true;
229                     break;
230                 }
231             }
232             sk_ASN1_OBJECT_free(extUsage);
233 
234             // Certificate has to have proper key usages set
235             if (!isExKeyUsageClientAuth)
236             {
237                 BMCWEB_LOG_DEBUG << this
238                                  << " Certificate ExtendedKeyUsage does "
239                                     "not allow provided certificate to "
240                                     "be used for user authentication";
241                 return true;
242             }
243             std::string sslUser;
244             // Extract username contained in CommonName
245             sslUser.resize(256, '\0');
246 
247             int status = X509_NAME_get_text_by_NID(
248                 X509_get_subject_name(peerCert), NID_commonName, sslUser.data(),
249                 static_cast<int>(sslUser.size()));
250 
251             if (status == -1)
252             {
253                 BMCWEB_LOG_DEBUG
254                     << this << " TLS cannot get username to create session";
255                 return true;
256             }
257 
258             size_t lastChar = sslUser.find('\0');
259             if (lastChar == std::string::npos || lastChar == 0)
260             {
261                 BMCWEB_LOG_DEBUG << this << " Invalid TLS user name";
262                 return true;
263             }
264             sslUser.resize(lastChar);
265             std::string unsupportedClientId = "";
266             sessionIsFromTransport = true;
267             userSession = persistent_data::SessionStore::getInstance()
268                               .generateUserSession(
269                                   sslUser, req->ipAddress.to_string(),
270                                   unsupportedClientId,
271                                   persistent_data::PersistenceType::TIMEOUT);
272             if (userSession != nullptr)
273             {
274                 BMCWEB_LOG_DEBUG
275                     << this
276                     << " Generating TLS session: " << userSession->uniqueId;
277             }
278             return true;
279         });
280     }
281 
282     Adaptor& socket()
283     {
284         return adaptor;
285     }
286 
287     void start()
288     {
289 
290         startDeadline(0);
291 
292         // TODO(ed) Abstract this to a more clever class with the idea of an
293         // asynchronous "start"
294         if constexpr (std::is_same_v<Adaptor,
295                                      boost::beast::ssl_stream<
296                                          boost::asio::ip::tcp::socket>>)
297         {
298             adaptor.async_handshake(boost::asio::ssl::stream_base::server,
299                                     [this, self(shared_from_this())](
300                                         const boost::system::error_code& ec) {
301                                         if (ec)
302                                         {
303                                             return;
304                                         }
305                                         doReadHeaders();
306                                     });
307         }
308         else
309         {
310             doReadHeaders();
311         }
312     }
313 
314     void handle()
315     {
316         cancelDeadlineTimer();
317         std::error_code reqEc;
318         crow::Request& thisReq = req.emplace(parser->release(), reqEc);
319         if (reqEc)
320         {
321             BMCWEB_LOG_DEBUG << "Request failed to construct" << reqEc;
322             return;
323         }
324         thisReq.session = userSession;
325 
326         // Fetch the client IP address
327         readClientIp();
328 
329         // Check for HTTP version 1.1.
330         if (thisReq.version() == 11)
331         {
332             if (thisReq.getHeaderValue(boost::beast::http::field::host).empty())
333             {
334                 res.result(boost::beast::http::status::bad_request);
335                 completeRequest();
336                 return;
337             }
338         }
339 
340         BMCWEB_LOG_INFO << "Request: "
341                         << " " << this << " HTTP/" << thisReq.version() / 10
342                         << "." << thisReq.version() % 10 << ' '
343                         << thisReq.methodString() << " " << thisReq.target()
344                         << " " << thisReq.ipAddress;
345 
346         res.setCompleteRequestHandler(nullptr);
347         res.isAliveHelper = [this]() -> bool { return isAlive(); };
348 
349         thisReq.ioService = static_cast<decltype(thisReq.ioService)>(
350             &adaptor.get_executor().context());
351 
352         if (res.completed)
353         {
354             completeRequest();
355             return;
356         }
357 #ifndef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
358         if (!crow::authorization::isOnAllowlist(req->url, req->method()) &&
359             thisReq.session == nullptr)
360         {
361             BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed";
362             forward_unauthorized::sendUnauthorized(
363                 req->url, req->getHeaderValue("User-Agent"),
364                 req->getHeaderValue("Accept"), res);
365             completeRequest();
366             return;
367         }
368 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
369         res.setCompleteRequestHandler([self(shared_from_this())] {
370             boost::asio::post(self->adaptor.get_executor(),
371                               [self] { self->completeRequest(); });
372         });
373 
374         if (thisReq.isUpgrade() &&
375             boost::iequals(
376                 thisReq.getHeaderValue(boost::beast::http::field::upgrade),
377                 "websocket"))
378         {
379             handler->handleUpgrade(thisReq, res, std::move(adaptor));
380             // delete lambda with self shared_ptr
381             // to enable connection destruction
382             res.setCompleteRequestHandler(nullptr);
383             return;
384         }
385         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(res);
386         handler->handle(thisReq, asyncResp);
387     }
388 
389     bool isAlive()
390     {
391         if constexpr (std::is_same_v<Adaptor,
392                                      boost::beast::ssl_stream<
393                                          boost::asio::ip::tcp::socket>>)
394         {
395             return adaptor.next_layer().is_open();
396         }
397         else
398         {
399             return adaptor.is_open();
400         }
401     }
402     void close()
403     {
404         if constexpr (std::is_same_v<Adaptor,
405                                      boost::beast::ssl_stream<
406                                          boost::asio::ip::tcp::socket>>)
407         {
408             adaptor.next_layer().close();
409 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
410             if (userSession != nullptr)
411             {
412                 BMCWEB_LOG_DEBUG
413                     << this
414                     << " Removing TLS session: " << userSession->uniqueId;
415                 persistent_data::SessionStore::getInstance().removeSession(
416                     userSession);
417             }
418 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
419         }
420         else
421         {
422             adaptor.close();
423         }
424     }
425 
426     void completeRequest()
427     {
428         if (!req)
429         {
430             return;
431         }
432         BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' '
433                         << res.resultInt() << " keepalive=" << req->keepAlive();
434 
435         addSecurityHeaders(*req, res);
436 
437         crow::authorization::cleanupTempSession(*req);
438 
439         if (!isAlive())
440         {
441             // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " <<
442             // isReading
443             // << ' ' << isWriting;
444             // delete this;
445 
446             // delete lambda with self shared_ptr
447             // to enable connection destruction
448             res.setCompleteRequestHandler(nullptr);
449             return;
450         }
451         if (res.body().empty() && !res.jsonValue.empty())
452         {
453             if (http_helpers::requestPrefersHtml(req->getHeaderValue("Accept")))
454             {
455                 prettyPrintJson(res);
456             }
457             else
458             {
459                 res.jsonMode();
460                 res.body() = res.jsonValue.dump(
461                     2, ' ', true, nlohmann::json::error_handler_t::replace);
462             }
463         }
464 
465         if (res.resultInt() >= 400 && res.body().empty())
466         {
467             res.body() = std::string(res.reason());
468         }
469 
470         if (res.result() == boost::beast::http::status::no_content)
471         {
472             // Boost beast throws if content is provided on a no-content
473             // response.  Ideally, this would never happen, but in the case that
474             // it does, we don't want to throw.
475             BMCWEB_LOG_CRITICAL
476                 << this << " Response content provided but code was no-content";
477             res.body().clear();
478         }
479 
480         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
481 
482         res.keepAlive(req->keepAlive());
483 
484         doWrite();
485 
486         // delete lambda with self shared_ptr
487         // to enable connection destruction
488         res.setCompleteRequestHandler(nullptr);
489     }
490 
491     void readClientIp()
492     {
493         boost::asio::ip::address ip;
494         boost::system::error_code ec = getClientIp(ip);
495         if (ec)
496         {
497             return;
498         }
499         req->ipAddress = ip;
500     }
501 
502     boost::system::error_code getClientIp(boost::asio::ip::address& ip)
503     {
504         boost::system::error_code ec;
505         BMCWEB_LOG_DEBUG << "Fetch the client IP address";
506         boost::asio::ip::tcp::endpoint endpoint =
507             boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
508 
509         if (ec)
510         {
511             // If remote endpoint fails keep going. "ClientOriginIPAddress"
512             // will be empty.
513             BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : "
514                              << ec;
515             return ec;
516         }
517         ip = endpoint.address();
518         return ec;
519     }
520 
521   private:
522     void doReadHeaders()
523     {
524         BMCWEB_LOG_DEBUG << this << " doReadHeaders";
525 
526         // Clean up any previous Connection.
527         boost::beast::http::async_read_header(
528             adaptor, buffer, *parser,
529             [this,
530              self(shared_from_this())](const boost::system::error_code& ec,
531                                        std::size_t bytesTransferred) {
532                 BMCWEB_LOG_DEBUG << this << " async_read_header "
533                                  << bytesTransferred << " Bytes";
534                 bool errorWhileReading = false;
535                 if (ec)
536                 {
537                     errorWhileReading = true;
538                     if (ec == boost::asio::error::eof)
539                     {
540                         BMCWEB_LOG_WARNING
541                             << this << " Error while reading: " << ec.message();
542                     }
543                     else
544                     {
545                         BMCWEB_LOG_ERROR
546                             << this << " Error while reading: " << ec.message();
547                     }
548                 }
549                 else
550                 {
551                     // if the adaptor isn't open anymore, and wasn't handed to a
552                     // websocket, treat as an error
553                     if (!isAlive() &&
554                         !boost::beast::websocket::is_upgrade(parser->get()))
555                     {
556                         errorWhileReading = true;
557                     }
558                 }
559 
560                 cancelDeadlineTimer();
561 
562                 if (errorWhileReading)
563                 {
564                     close();
565                     BMCWEB_LOG_DEBUG << this << " from read(1)";
566                     return;
567                 }
568 
569                 readClientIp();
570 
571                 boost::asio::ip::address ip;
572                 if (getClientIp(ip))
573                 {
574                     BMCWEB_LOG_DEBUG << "Unable to get client IP";
575                 }
576                 sessionIsFromTransport = false;
577 #ifndef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
578                 boost::beast::http::verb method = parser->get().method();
579                 userSession = crow::authorization::authenticate(
580                     ip, res, method, parser->get().base(), userSession);
581 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
582                 bool loggedIn = userSession != nullptr;
583                 if (loggedIn)
584                 {
585                     startDeadline(loggedInAttempts);
586                     BMCWEB_LOG_DEBUG << "Starting slow deadline";
587 
588                     req->urlParams = req->urlView.query_params();
589 
590 #ifdef BMCWEB_ENABLE_DEBUG
591                     std::string paramList = "";
592                     for (const auto param : req->urlParams)
593                     {
594                         paramList += param->key() + " " + param->value() + " ";
595                     }
596                     BMCWEB_LOG_DEBUG << "QueryParams: " << paramList;
597 #endif
598                 }
599                 else
600                 {
601                     const boost::optional<uint64_t> contentLength =
602                         parser->content_length();
603                     if (contentLength &&
604                         *contentLength > loggedOutPostBodyLimit)
605                     {
606                         BMCWEB_LOG_DEBUG << "Content length greater than limit "
607                                          << *contentLength;
608                         close();
609                         return;
610                     }
611 
612                     startDeadline(loggedOutAttempts);
613                     BMCWEB_LOG_DEBUG << "Starting quick deadline";
614                 }
615                 doRead();
616             });
617     }
618 
619     void doRead()
620     {
621         BMCWEB_LOG_DEBUG << this << " doRead";
622 
623         boost::beast::http::async_read(
624             adaptor, buffer, *parser,
625             [this,
626              self(shared_from_this())](const boost::system::error_code& ec,
627                                        std::size_t bytesTransferred) {
628                 BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred
629                                  << " Bytes";
630 
631                 bool errorWhileReading = false;
632                 if (ec)
633                 {
634                     BMCWEB_LOG_ERROR
635                         << this << " Error while reading: " << ec.message();
636                     errorWhileReading = true;
637                 }
638                 else
639                 {
640                     if (isAlive())
641                     {
642                         cancelDeadlineTimer();
643                         if (userSession != nullptr)
644                         {
645                             startDeadline(loggedInAttempts);
646                         }
647                         else
648                         {
649                             startDeadline(loggedOutAttempts);
650                         }
651                     }
652                     else
653                     {
654                         errorWhileReading = true;
655                     }
656                 }
657                 if (errorWhileReading)
658                 {
659                     cancelDeadlineTimer();
660                     close();
661                     BMCWEB_LOG_DEBUG << this << " from read(1)";
662                     return;
663                 }
664                 handle();
665             });
666     }
667 
668     void doWrite()
669     {
670         bool loggedIn = req && req->session;
671         if (loggedIn)
672         {
673             startDeadline(loggedInAttempts);
674         }
675         else
676         {
677             startDeadline(loggedOutAttempts);
678         }
679         BMCWEB_LOG_DEBUG << this << " doWrite";
680         res.preparePayload();
681         serializer.emplace(*res.stringResponse);
682         boost::beast::http::async_write(
683             adaptor, *serializer,
684             [this,
685              self(shared_from_this())](const boost::system::error_code& ec,
686                                        std::size_t bytesTransferred) {
687                 BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred
688                                  << " bytes";
689 
690                 cancelDeadlineTimer();
691 
692                 if (ec)
693                 {
694                     BMCWEB_LOG_DEBUG << this << " from write(2)";
695                     return;
696                 }
697                 if (!res.keepAlive())
698                 {
699                     close();
700                     BMCWEB_LOG_DEBUG << this << " from write(1)";
701                     return;
702                 }
703 
704                 serializer.reset();
705                 BMCWEB_LOG_DEBUG << this << " Clearing response";
706                 res.clear();
707                 parser.emplace(std::piecewise_construct, std::make_tuple());
708                 parser->body_limit(httpReqBodyLimit); // reset body limit for
709                                                       // newly created parser
710                 buffer.consume(buffer.size());
711 
712                 // If the session was built from the transport, we don't need to
713                 // clear it.  All other sessions are generated per request.
714                 if (!sessionIsFromTransport)
715                 {
716                     userSession = nullptr;
717                 }
718 
719                 // Destroy the Request via the std::optional
720                 req.reset();
721                 doReadHeaders();
722             });
723     }
724 
725     void cancelDeadlineTimer()
726     {
727         if (timerCancelKey)
728         {
729             BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue
730                              << ' ' << *timerCancelKey;
731             timerQueue.cancel(*timerCancelKey);
732             timerCancelKey.reset();
733         }
734     }
735 
736     void startDeadline(size_t timerIterations)
737     {
738         cancelDeadlineTimer();
739 
740         if (timerIterations)
741         {
742             timerIterations--;
743         }
744 
745         timerCancelKey =
746             timerQueue.add([self(shared_from_this()), timerIterations,
747                             readCount{parser->get().body().size()}] {
748                 // Mark timer as not active to avoid canceling it during
749                 // Connection destructor which leads to double free issue
750                 self->timerCancelKey.reset();
751                 if (!self->isAlive())
752                 {
753                     return;
754                 }
755 
756                 bool loggedIn = self->req && self->req->session;
757                 // allow slow uploads for logged in users
758                 if (loggedIn && self->parser->get().body().size() > readCount)
759                 {
760                     BMCWEB_LOG_DEBUG << self.get()
761                                      << " restart timer - read in progress";
762                     self->startDeadline(timerIterations);
763                     return;
764                 }
765 
766                 // Threshold can be used to drop slow connections
767                 // to protect against slow-rate DoS attack
768                 if (timerIterations)
769                 {
770                     BMCWEB_LOG_DEBUG << self.get() << " restart timer";
771                     self->startDeadline(timerIterations);
772                     return;
773                 }
774 
775                 self->close();
776             });
777 
778         if (!timerCancelKey)
779         {
780             close();
781             return;
782         }
783         BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' '
784                          << *timerCancelKey;
785     }
786 
787   private:
788     Adaptor adaptor;
789     Handler* handler;
790     // Making this a std::optional allows it to be efficiently destroyed and
791     // re-created on Connection reset
792     std::optional<
793         boost::beast::http::request_parser<boost::beast::http::string_body>>
794         parser;
795 
796     boost::beast::flat_static_buffer<8192> buffer;
797 
798     std::optional<boost::beast::http::response_serializer<
799         boost::beast::http::string_body>>
800         serializer;
801 
802     std::optional<crow::Request> req;
803     crow::Response res;
804 
805     bool sessionIsFromTransport = false;
806     std::shared_ptr<persistent_data::UserSession> userSession;
807 
808     std::optional<size_t> timerCancelKey;
809 
810     std::function<std::string()>& getCachedDateStr;
811     detail::TimerQueue& timerQueue;
812 
813     using std::enable_shared_from_this<
814         Connection<Adaptor, Handler>>::shared_from_this;
815 };
816 } // namespace crow
817