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