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