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