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