xref: /openbmc/bmcweb/http/http2_connection.hpp (revision ebe4c574caac9dfd8b2754bda51c0cf869f1978f)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 #include "bmcweb_config.h"
5 
6 #include "async_resp.hpp"
7 #include "authentication.hpp"
8 #include "complete_response_fields.hpp"
9 #include "forward_unauthorized.hpp"
10 #include "http_body.hpp"
11 #include "http_connect_types.hpp"
12 #include "http_request.hpp"
13 #include "http_response.hpp"
14 #include "logging.hpp"
15 
16 // NOLINTNEXTLINE(misc-include-cleaner)
17 #include "nghttp2_adapters.hpp"
18 
19 #include <nghttp2/nghttp2.h>
20 #include <unistd.h>
21 
22 #include <boost/asio/buffer.hpp>
23 #include <boost/asio/ssl/stream.hpp>
24 #include <boost/beast/core/error.hpp>
25 #include <boost/beast/http/field.hpp>
26 #include <boost/beast/http/fields.hpp>
27 #include <boost/beast/http/message.hpp>
28 #include <boost/beast/http/verb.hpp>
29 #include <boost/optional/optional.hpp>
30 #include <boost/system/error_code.hpp>
31 
32 #include <array>
33 #include <bit>
34 #include <cstddef>
35 #include <cstdint>
36 #include <functional>
37 #include <map>
38 #include <memory>
39 #include <optional>
40 #include <span>
41 #include <string>
42 #include <string_view>
43 #include <type_traits>
44 #include <utility>
45 #include <vector>
46 
47 namespace crow
48 {
49 
50 struct Http2StreamData
51 {
52     std::shared_ptr<Request> req = std::make_shared<Request>();
53     std::optional<bmcweb::HttpBody::reader> reqReader;
54     std::string accept;
55     Response res;
56     std::optional<bmcweb::HttpBody::writer> writer;
57 };
58 
59 template <typename Adaptor, typename Handler>
60 class HTTP2Connection :
61     public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
62 {
63     using self_type = HTTP2Connection<Adaptor, Handler>;
64 
65   public:
HTTP2Connection(boost::asio::ssl::stream<Adaptor> && adaptorIn,Handler * handlerIn,std::function<std::string ()> & getCachedDateStrF,HttpType httpTypeIn)66     HTTP2Connection(boost::asio::ssl::stream<Adaptor>&& adaptorIn,
67                     Handler* handlerIn,
68                     std::function<std::string()>& getCachedDateStrF,
69                     HttpType httpTypeIn) :
70         httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
71         ngSession(initializeNghttp2Session()), handler(handlerIn),
72         getCachedDateStr(getCachedDateStrF)
73     {}
74 
start()75     void start()
76     {
77         // Create the control stream
78         streams[0];
79 
80         if (sendServerConnectionHeader() != 0)
81         {
82             BMCWEB_LOG_ERROR("send_server_connection_header failed");
83             return;
84         }
85         doRead();
86     }
87 
startFromSettings(std::string_view http2UpgradeSettings)88     void startFromSettings(std::string_view http2UpgradeSettings)
89     {
90         int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
91                                             false /*head_request*/);
92         if (ret != 0)
93         {
94             BMCWEB_LOG_ERROR("Failed to load upgrade header");
95             return;
96         }
97         // Create the control stream
98         streams[0];
99 
100         if (sendServerConnectionHeader() != 0)
101         {
102             BMCWEB_LOG_ERROR("send_server_connection_header failed");
103             return;
104         }
105         doRead();
106     }
107 
sendServerConnectionHeader()108     int sendServerConnectionHeader()
109     {
110         BMCWEB_LOG_DEBUG("send_server_connection_header()");
111 
112         uint32_t maxStreams = 4;
113         std::array<nghttp2_settings_entry, 2> iv = {
114             {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
115              {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
116         int rv = ngSession.submitSettings(iv);
117         if (rv != 0)
118         {
119             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
120             return -1;
121         }
122         writeBuffer();
123         return 0;
124     }
125 
fileReadCallback(nghttp2_session *,int32_t streamId,uint8_t * buf,size_t length,uint32_t * dataFlags,nghttp2_data_source *,void * userPtr)126     static ssize_t fileReadCallback(
127         nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
128         size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
129         void* userPtr)
130     {
131         self_type& self = userPtrToSelf(userPtr);
132 
133         auto streamIt = self.streams.find(streamId);
134         if (streamIt == self.streams.end())
135         {
136             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
137         }
138         Http2StreamData& stream = streamIt->second;
139         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
140         if (!stream.writer)
141         {
142             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
143         }
144         boost::beast::error_code ec;
145         boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
146             stream.writer->getWithMaxSize(ec, length);
147         if (ec)
148         {
149             BMCWEB_LOG_CRITICAL("Failed to get buffer");
150             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
151         }
152         if (!out)
153         {
154             BMCWEB_LOG_ERROR("Empty file, setting EOF");
155             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
156             return 0;
157         }
158 
159         BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
160         if (length < out->first.size())
161         {
162             BMCWEB_LOG_CRITICAL(
163                 "Buffer overflow that should never happen happened");
164             // Should never happen because of length limit on get() above
165             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
166         }
167         boost::asio::mutable_buffer writeableBuf(buf, length);
168         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
169         size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
170         if (copied != out->first.size())
171         {
172             BMCWEB_LOG_ERROR(
173                 "Couldn't copy all {} bytes into buffer, only copied {}",
174                 out->first.size(), copied);
175             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
176         }
177 
178         if (!out->second)
179         {
180             BMCWEB_LOG_DEBUG("Setting EOF flag");
181             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
182         }
183         return static_cast<ssize_t>(copied);
184     }
185 
headerFromStringViews(std::string_view name,std::string_view value,uint8_t flags)186     nghttp2_nv headerFromStringViews(std::string_view name,
187                                      std::string_view value, uint8_t flags)
188     {
189         uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
190         uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
191         return {nameData, valueData, name.size(), value.size(), flags};
192     }
193 
sendResponse(Response & completedRes,int32_t streamId)194     int sendResponse(Response& completedRes, int32_t streamId)
195     {
196         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
197 
198         auto it = streams.find(streamId);
199         if (it == streams.end())
200         {
201             close();
202             return -1;
203         }
204         Http2StreamData& stream = it->second;
205         Response& res = stream.res;
206         res = std::move(completedRes);
207 
208         completeResponseFields(stream.accept, res);
209         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
210         res.preparePayload();
211 
212         boost::beast::http::fields& fields = res.fields();
213         std::string code = std::to_string(res.resultInt());
214         std::vector<nghttp2_nv> hdr;
215         hdr.emplace_back(
216             headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
217         for (const boost::beast::http::fields::value_type& header : fields)
218         {
219             hdr.emplace_back(headerFromStringViews(
220                 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
221         }
222         http::response<bmcweb::HttpBody>& fbody = res.response;
223         stream.writer.emplace(fbody.base(), fbody.body());
224 
225         nghttp2_data_provider dataPrd{
226             .source = {.fd = 0},
227             .read_callback = fileReadCallback,
228         };
229 
230         int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
231         if (rv != 0)
232         {
233             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
234             close();
235             return -1;
236         }
237         writeBuffer();
238 
239         return 0;
240     }
241 
initializeNghttp2Session()242     nghttp2_session initializeNghttp2Session()
243     {
244         nghttp2_session_callbacks callbacks;
245         callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
246         callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
247         callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
248         callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
249         callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
250 
251         nghttp2_session session(callbacks);
252         session.setUserData(this);
253 
254         return session;
255     }
256 
onRequestRecv(int32_t streamId)257     int onRequestRecv(int32_t streamId)
258     {
259         BMCWEB_LOG_DEBUG("on_request_recv");
260 
261         auto it = streams.find(streamId);
262         if (it == streams.end())
263         {
264             close();
265             return -1;
266         }
267         auto& reqReader = it->second.reqReader;
268         if (reqReader)
269         {
270             boost::beast::error_code ec;
271             bmcweb::HttpBody::reader::finish(ec);
272             if (ec)
273             {
274                 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
275                 close();
276                 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
277             }
278         }
279         crow::Request& thisReq = *it->second.req;
280         it->second.accept = thisReq.getHeaderValue("Accept");
281 
282         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
283                          thisReq.url().encoded_path());
284 
285         crow::Response& thisRes = it->second.res;
286 
287         thisRes.setCompleteRequestHandler(
288             [this, streamId](Response& completeRes) {
289                 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
290                 if (sendResponse(completeRes, streamId) != 0)
291                 {
292                     close();
293                     return;
294                 }
295             });
296         auto asyncResp =
297             std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
298         if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
299         {
300             thisReq.session = crow::authentication::authenticate(
301                 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr);
302             if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
303                                                      thisReq.method()) &&
304                 thisReq.session == nullptr)
305             {
306                 BMCWEB_LOG_WARNING("Authentication failed");
307                 forward_unauthorized::sendUnauthorized(
308                     thisReq.url().encoded_path(),
309                     thisReq.getHeaderValue("X-Requested-With"),
310                     thisReq.getHeaderValue("Accept"), asyncResp->res);
311                 return 0;
312             }
313         }
314         std::string_view expected =
315             thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
316         BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
317         if (!expected.empty())
318         {
319             asyncResp->res.setExpectedHash(expected);
320         }
321         handler->handle(it->second.req, asyncResp);
322         return 0;
323     }
324 
onDataChunkRecvCallback(uint8_t,int32_t streamId,const uint8_t * data,size_t len)325     int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
326                                 const uint8_t* data, size_t len)
327     {
328         auto thisStream = streams.find(streamId);
329         if (thisStream == streams.end())
330         {
331             BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
332             close();
333             return -1;
334         }
335 
336         std::optional<bmcweb::HttpBody::reader>& reqReader =
337             thisStream->second.reqReader;
338         if (!reqReader)
339         {
340             reqReader.emplace(
341                 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
342                                          thisStream->second.req->req.body()));
343         }
344         boost::beast::error_code ec;
345         reqReader->put(boost::asio::const_buffer(data, len), ec);
346         if (ec)
347         {
348             BMCWEB_LOG_CRITICAL("Failed to write payload");
349             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
350         }
351         return 0;
352     }
353 
onDataChunkRecvStatic(nghttp2_session *,uint8_t flags,int32_t streamId,const uint8_t * data,size_t len,void * userData)354     static int onDataChunkRecvStatic(
355         nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
356         const uint8_t* data, size_t len, void* userData)
357     {
358         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
359         if (userData == nullptr)
360         {
361             BMCWEB_LOG_CRITICAL("user data was null?");
362             return NGHTTP2_ERR_CALLBACK_FAILURE;
363         }
364         return userPtrToSelf(userData).onDataChunkRecvCallback(
365             flags, streamId, data, len);
366     }
367 
onFrameRecvCallback(const nghttp2_frame & frame)368     int onFrameRecvCallback(const nghttp2_frame& frame)
369     {
370         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
371         switch (frame.hd.type)
372         {
373             case NGHTTP2_DATA:
374             case NGHTTP2_HEADERS:
375                 // Check that the client request has finished
376                 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
377                 {
378                     return onRequestRecv(frame.hd.stream_id);
379                 }
380                 break;
381             default:
382                 break;
383         }
384         return 0;
385     }
386 
onFrameRecvCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)387     static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
388                                          const nghttp2_frame* frame,
389                                          void* userData)
390     {
391         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
392         if (userData == nullptr)
393         {
394             BMCWEB_LOG_CRITICAL("user data was null?");
395             return NGHTTP2_ERR_CALLBACK_FAILURE;
396         }
397         if (frame == nullptr)
398         {
399             BMCWEB_LOG_CRITICAL("frame was null?");
400             return NGHTTP2_ERR_CALLBACK_FAILURE;
401         }
402         return userPtrToSelf(userData).onFrameRecvCallback(*frame);
403     }
404 
userPtrToSelf(void * userData)405     static self_type& userPtrToSelf(void* userData)
406     {
407         // This method exists to keep the unsafe reinterpret cast in one
408         // place.
409         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
410         return *reinterpret_cast<self_type*>(userData);
411     }
412 
onStreamCloseCallbackStatic(nghttp2_session *,int32_t streamId,uint32_t,void * userData)413     static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
414                                            int32_t streamId,
415                                            uint32_t /*unused*/, void* userData)
416     {
417         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
418         if (userData == nullptr)
419         {
420             BMCWEB_LOG_CRITICAL("user data was null?");
421             return NGHTTP2_ERR_CALLBACK_FAILURE;
422         }
423         if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
424         {
425             return -1;
426         }
427         return 0;
428     }
429 
onHeaderCallback(const nghttp2_frame & frame,std::span<const uint8_t> name,std::span<const uint8_t> value)430     int onHeaderCallback(const nghttp2_frame& frame,
431                          std::span<const uint8_t> name,
432                          std::span<const uint8_t> value)
433     {
434         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
435         std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
436                                 name.size());
437         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
438         std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
439                                  value.size());
440 
441         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
442                          valueSv);
443         if (frame.hd.type != NGHTTP2_HEADERS)
444         {
445             return 0;
446         }
447         if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
448         {
449             return 0;
450         }
451         auto thisStream = streams.find(frame.hd.stream_id);
452         if (thisStream == streams.end())
453         {
454             BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
455             close();
456             return -1;
457         }
458 
459         crow::Request& thisReq = *thisStream->second.req;
460 
461         if (nameSv == ":path")
462         {
463             thisReq.target(valueSv);
464         }
465         else if (nameSv == ":method")
466         {
467             boost::beast::http::verb verb =
468                 boost::beast::http::string_to_verb(valueSv);
469             if (verb == boost::beast::http::verb::unknown)
470             {
471                 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
472                 verb = boost::beast::http::verb::trace;
473             }
474             thisReq.method(verb);
475         }
476         else if (nameSv == ":scheme")
477         {
478             // Nothing to check on scheme
479         }
480         else
481         {
482             thisReq.addHeader(nameSv, valueSv);
483         }
484         return 0;
485     }
486 
onHeaderCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t vallen,uint8_t,void * userData)487     static int onHeaderCallbackStatic(
488         nghttp2_session* /* session */, const nghttp2_frame* frame,
489         const uint8_t* name, size_t namelen, const uint8_t* value,
490         size_t vallen, uint8_t /* flags */, void* userData)
491     {
492         if (userData == nullptr)
493         {
494             BMCWEB_LOG_CRITICAL("user data was null?");
495             return NGHTTP2_ERR_CALLBACK_FAILURE;
496         }
497         if (frame == nullptr)
498         {
499             BMCWEB_LOG_CRITICAL("frame was null?");
500             return NGHTTP2_ERR_CALLBACK_FAILURE;
501         }
502         if (name == nullptr)
503         {
504             BMCWEB_LOG_CRITICAL("name was null?");
505             return NGHTTP2_ERR_CALLBACK_FAILURE;
506         }
507         if (value == nullptr)
508         {
509             BMCWEB_LOG_CRITICAL("value was null?");
510             return NGHTTP2_ERR_CALLBACK_FAILURE;
511         }
512         return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
513                                                         {value, vallen});
514     }
515 
onBeginHeadersCallback(const nghttp2_frame & frame)516     int onBeginHeadersCallback(const nghttp2_frame& frame)
517     {
518         if (frame.hd.type == NGHTTP2_HEADERS &&
519             frame.headers.cat == NGHTTP2_HCAT_REQUEST)
520         {
521             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
522 
523             streams.emplace(frame.hd.stream_id, Http2StreamData());
524         }
525         return 0;
526     }
527 
onBeginHeadersCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)528     static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
529                                             const nghttp2_frame* frame,
530                                             void* userData)
531     {
532         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
533         if (userData == nullptr)
534         {
535             BMCWEB_LOG_CRITICAL("user data was null?");
536             return NGHTTP2_ERR_CALLBACK_FAILURE;
537         }
538         if (frame == nullptr)
539         {
540             BMCWEB_LOG_CRITICAL("frame was null?");
541             return NGHTTP2_ERR_CALLBACK_FAILURE;
542         }
543         return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
544     }
545 
afterWriteBuffer(const std::shared_ptr<self_type> & self,const boost::system::error_code & ec,size_t sendLength)546     static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
547                                  const boost::system::error_code& ec,
548                                  size_t sendLength)
549     {
550         self->isWriting = false;
551         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
552         if (ec)
553         {
554             self->close();
555             return;
556         }
557         self->writeBuffer();
558     }
559 
writeBuffer()560     void writeBuffer()
561     {
562         if (isWriting)
563         {
564             return;
565         }
566         std::span<const uint8_t> data = ngSession.memSend();
567         if (data.empty())
568         {
569             return;
570         }
571         isWriting = true;
572         if (httpType == HttpType::HTTPS)
573         {
574             boost::asio::async_write(
575                 adaptor, boost::asio::const_buffer(data.data(), data.size()),
576                 std::bind_front(afterWriteBuffer, shared_from_this()));
577         }
578         else if (httpType == HttpType::HTTP)
579         {
580             boost::asio::async_write(
581                 adaptor.next_layer(),
582                 boost::asio::const_buffer(data.data(), data.size()),
583                 std::bind_front(afterWriteBuffer, shared_from_this()));
584         }
585     }
586 
close()587     void close()
588     {
589         adaptor.next_layer().close();
590     }
591 
afterDoRead(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,size_t bytesTransferred)592     void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
593                      const boost::system::error_code& ec,
594                      size_t bytesTransferred)
595     {
596         BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
597                          bytesTransferred);
598 
599         if (ec)
600         {
601             BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
602                              ec.message());
603             close();
604             BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
605             return;
606         }
607         std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
608 
609         ssize_t readLen = ngSession.memRecv(bufferSpan);
610         if (readLen < 0)
611         {
612             BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
613             close();
614             return;
615         }
616         writeBuffer();
617 
618         doRead();
619     }
620 
doRead()621     void doRead()
622     {
623         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
624         if (httpType == HttpType::HTTPS)
625         {
626             adaptor.async_read_some(boost::asio::buffer(inBuffer),
627                                     std::bind_front(&self_type::afterDoRead,
628                                                     this, shared_from_this()));
629         }
630         else if (httpType == HttpType::HTTP)
631         {
632             adaptor.next_layer().async_read_some(
633                 boost::asio::buffer(inBuffer),
634                 std::bind_front(&self_type::afterDoRead, this,
635                                 shared_from_this()));
636         }
637     }
638 
639     // A mapping from http2 stream ID to Stream Data
640     std::map<int32_t, Http2StreamData> streams;
641 
642     std::array<uint8_t, 8192> inBuffer{};
643 
644     HttpType httpType = HttpType::BOTH;
645     boost::asio::ssl::stream<Adaptor> adaptor;
646     bool isWriting = false;
647 
648     nghttp2_session ngSession;
649 
650     Handler* handler;
651     std::function<std::string()>& getCachedDateStr;
652 
653     using std::enable_shared_from_this<
654         HTTP2Connection<Adaptor, Handler>>::shared_from_this;
655 
656     using std::enable_shared_from_this<
657         HTTP2Connection<Adaptor, Handler>>::weak_from_this;
658 };
659 } // namespace crow
660