tizen 2.4 release
[external/nghttp2.git] / src / asio_http2_handler.cc
1 /*
2  * nghttp2 - HTTP/2 C Library
3  *
4  * Copyright (c) 2014 Tatsuhiro Tsujikawa
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * "Software"), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be
15  * included in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24  */
25 #include "asio_http2_handler.h"
26
27 #include <iostream>
28
29 #include "http2.h"
30 #include "util.h"
31 #include "template.h"
32
33 namespace nghttp2 {
34
35 namespace asio_http2 {
36
37 channel::channel() : impl_(make_unique<channel_impl>()) {}
38
39 void channel::post(void_cb cb) { impl_->post(std::move(cb)); }
40
41 channel_impl &channel::impl() { return *impl_; }
42
43 channel_impl::channel_impl() : strand_(nullptr) {}
44
45 void channel_impl::post(void_cb cb) { strand_->post(std::move(cb)); }
46
47 void channel_impl::strand(boost::asio::io_service::strand *strand) {
48   strand_ = strand;
49 }
50
51 namespace server {
52
53 extern std::shared_ptr<std::string> cached_date;
54
55 request::request() : impl_(make_unique<request_impl>()) {}
56
57 const std::vector<header> &request::headers() const { return impl_->headers(); }
58
59 const std::string &request::method() const { return impl_->method(); }
60
61 const std::string &request::scheme() const { return impl_->scheme(); }
62
63 const std::string &request::authority() const { return impl_->authority(); }
64
65 const std::string &request::host() const { return impl_->host(); }
66
67 const std::string &request::path() const { return impl_->path(); }
68
69 bool request::push(std::string method, std::string path,
70                    std::vector<header> headers) {
71   return impl_->push(std::move(method), std::move(path), std::move(headers));
72 }
73
74 bool request::pushed() const { return impl_->pushed(); }
75
76 bool request::closed() const { return impl_->closed(); }
77
78 void request::on_data(data_cb cb) { return impl_->on_data(std::move(cb)); }
79
80 void request::on_end(void_cb cb) { return impl_->on_end(std::move(cb)); }
81
82 bool request::run_task(thread_cb start) {
83   return impl_->run_task(std::move(start));
84 }
85
86 request_impl &request::impl() { return *impl_; }
87
88 response::response() : impl_(make_unique<response_impl>()) {}
89
90 void response::write_head(unsigned int status_code,
91                           std::vector<header> headers) {
92   impl_->write_head(status_code, std::move(headers));
93 }
94
95 void response::end(std::string data) { impl_->end(std::move(data)); }
96
97 void response::end(read_cb cb) { impl_->end(std::move(cb)); }
98
99 void response::resume() { impl_->resume(); }
100
101 unsigned int response::status_code() const { return impl_->status_code(); }
102
103 bool response::started() const { return impl_->started(); }
104
105 response_impl &response::impl() { return *impl_; }
106
107 request_impl::request_impl() : pushed_(false) {}
108
109 const std::vector<header> &request_impl::headers() const { return headers_; }
110
111 const std::string &request_impl::method() const { return method_; }
112
113 const std::string &request_impl::scheme() const { return scheme_; }
114
115 const std::string &request_impl::authority() const { return authority_; }
116
117 const std::string &request_impl::host() const { return host_; }
118
119 const std::string &request_impl::path() const { return path_; }
120
121 void request_impl::set_header(std::vector<header> headers) {
122   headers_ = std::move(headers);
123 }
124
125 void request_impl::add_header(std::string name, std::string value) {
126   headers_.push_back(header{std::move(name), std::move(value)});
127 }
128
129 void request_impl::method(std::string arg) { method_ = std::move(arg); }
130
131 void request_impl::scheme(std::string arg) { scheme_ = std::move(arg); }
132
133 void request_impl::authority(std::string arg) { authority_ = std::move(arg); }
134
135 void request_impl::host(std::string arg) { host_ = std::move(arg); }
136
137 void request_impl::path(std::string arg) { path_ = std::move(arg); }
138
139 bool request_impl::push(std::string method, std::string path,
140                         std::vector<header> headers) {
141   if (closed()) {
142     return false;
143   }
144
145   auto handler = handler_.lock();
146   auto stream = stream_.lock();
147   auto rv = handler->push_promise(*stream, std::move(method), std::move(path),
148                                   std::move(headers));
149   return rv == 0;
150 }
151
152 bool request_impl::pushed() const { return pushed_; }
153
154 void request_impl::pushed(bool f) { pushed_ = f; }
155
156 bool request_impl::closed() const {
157   return handler_.expired() || stream_.expired();
158 }
159
160 void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); }
161
162 void request_impl::on_end(void_cb cb) { on_end_cb_ = std::move(cb); }
163
164 bool request_impl::run_task(thread_cb start) {
165   if (closed()) {
166     return false;
167   }
168
169   auto handler = handler_.lock();
170
171   return handler->run_task(std::move(start));
172 }
173
174 void request_impl::handler(std::weak_ptr<http2_handler> h) {
175   handler_ = std::move(h);
176 }
177
178 void request_impl::stream(std::weak_ptr<http2_stream> s) {
179   stream_ = std::move(s);
180 }
181
182 void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
183   if (on_data_cb_) {
184     on_data_cb_(data, len);
185   }
186 }
187
188 void request_impl::call_on_end() {
189   if (on_end_cb_) {
190     on_end_cb_();
191   }
192 }
193
194 response_impl::response_impl() : status_code_(200), started_(false) {}
195
196 unsigned int response_impl::status_code() const { return status_code_; }
197
198 void response_impl::write_head(unsigned int status_code,
199                                std::vector<header> headers) {
200   status_code_ = status_code;
201   headers_ = std::move(headers);
202 }
203
204 void response_impl::end(std::string data) {
205   if (started_) {
206     return;
207   }
208
209   auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
210                                                                 data.size());
211   auto read_cb = [strio](uint8_t *buf, size_t len) {
212     auto nread = std::min(len, strio->second);
213     memcpy(buf, strio->first.c_str(), nread);
214     strio->second -= nread;
215     if (strio->second == 0) {
216       return std::make_pair(nread, true);
217     }
218
219     return std::make_pair(nread, false);
220   };
221
222   end(std::move(read_cb));
223 }
224
225 void response_impl::end(read_cb cb) {
226   if (started_ || closed()) {
227     return;
228   }
229
230   read_cb_ = std::move(cb);
231   started_ = true;
232
233   auto handler = handler_.lock();
234   auto stream = stream_.lock();
235
236   if (handler->start_response(*stream) != 0) {
237     handler->stream_error(stream->get_stream_id(), NGHTTP2_INTERNAL_ERROR);
238     return;
239   }
240
241   if (!handler->inside_callback()) {
242     handler->initiate_write();
243   }
244 }
245
246 bool response_impl::closed() const {
247   return handler_.expired() || stream_.expired();
248 }
249
250 void response_impl::resume() {
251   if (closed()) {
252     return;
253   }
254
255   auto handler = handler_.lock();
256   auto stream = stream_.lock();
257   handler->resume(*stream);
258
259   if (!handler->inside_callback()) {
260     handler->initiate_write();
261   }
262 }
263
264 bool response_impl::started() const { return started_; }
265
266 const std::vector<header> &response_impl::headers() const { return headers_; }
267
268 void response_impl::handler(std::weak_ptr<http2_handler> h) {
269   handler_ = std::move(h);
270 }
271
272 void response_impl::stream(std::weak_ptr<http2_stream> s) {
273   stream_ = std::move(s);
274 }
275
276 std::pair<ssize_t, bool> response_impl::call_read(uint8_t *data,
277                                                   std::size_t len) {
278   if (read_cb_) {
279     return read_cb_(data, len);
280   }
281
282   return std::make_pair(0, true);
283 }
284
285 http2_stream::http2_stream(int32_t stream_id)
286     : request_(std::make_shared<request>()),
287       response_(std::make_shared<response>()), stream_id_(stream_id) {}
288
289 int32_t http2_stream::get_stream_id() const { return stream_id_; }
290
291 const std::shared_ptr<request> &http2_stream::get_request() { return request_; }
292
293 const std::shared_ptr<response> &http2_stream::get_response() {
294   return response_;
295 }
296
297 namespace {
298 int stream_error(nghttp2_session *session, int32_t stream_id,
299                  uint32_t error_code) {
300   return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
301                                    error_code);
302 }
303 } // namespace
304
305 namespace {
306 int on_begin_headers_callback(nghttp2_session *session,
307                               const nghttp2_frame *frame, void *user_data) {
308   auto handler = static_cast<http2_handler *>(user_data);
309
310   if (frame->hd.type != NGHTTP2_HEADERS ||
311       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
312     return 0;
313   }
314
315   handler->create_stream(frame->hd.stream_id);
316
317   return 0;
318 }
319 } // namespace
320
321 namespace {
322 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
323                        const uint8_t *name, size_t namelen,
324                        const uint8_t *value, size_t valuelen, uint8_t flags,
325                        void *user_data) {
326   auto handler = static_cast<http2_handler *>(user_data);
327   auto stream_id = frame->hd.stream_id;
328
329   if (frame->hd.type != NGHTTP2_HEADERS ||
330       frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
331     return 0;
332   }
333
334   auto stream = handler->find_stream(stream_id);
335   if (!stream) {
336     return 0;
337   }
338
339   if (!nghttp2_check_header_name(name, namelen) ||
340       !nghttp2_check_header_value(value, valuelen)) {
341     stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
342
343     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
344   }
345
346   auto &req = stream->get_request()->impl();
347
348   if (name[0] == ':' && !req.headers().empty()) {
349     stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
350     return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
351   }
352
353   if (util::streq(":method", name, namelen)) {
354     if (!req.method().empty()) {
355       stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
356       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
357     }
358     req.method(std::string(value, value + valuelen));
359   } else if (util::streq(":scheme", name, namelen)) {
360     if (!req.scheme().empty()) {
361       stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
362       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
363     }
364     req.scheme(std::string(value, value + valuelen));
365   } else if (util::streq(":authority", name, namelen)) {
366     if (!req.authority().empty()) {
367       stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
368       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
369     }
370     req.authority(std::string(value, value + valuelen));
371   } else if (util::streq(":path", name, namelen)) {
372     if (!req.path().empty()) {
373       stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
374       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
375     }
376     req.path(std::string(value, value + valuelen));
377   } else {
378     if (name[0] == ':') {
379       stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
380       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
381     }
382
383     if (util::streq("host", name, namelen)) {
384       req.host(std::string(value, value + valuelen));
385     }
386
387     req.add_header(std::string(name, name + namelen),
388                    std::string(value, value + valuelen));
389   }
390
391   return 0;
392 }
393 } // namespace
394
395 namespace {
396 int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
397                            void *user_data) {
398   auto handler = static_cast<http2_handler *>(user_data);
399   auto stream = handler->find_stream(frame->hd.stream_id);
400
401   switch (frame->hd.type) {
402   case NGHTTP2_DATA:
403     if (!stream) {
404       break;
405     }
406
407     if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
408       stream->get_request()->impl().call_on_end();
409     }
410
411     break;
412   case NGHTTP2_HEADERS: {
413     if (!stream || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
414       break;
415     }
416
417     auto &req = stream->get_request()->impl();
418
419     if (req.method().empty() || req.scheme().empty() || req.path().empty() ||
420         (req.authority().empty() && req.host().empty())) {
421       stream_error(session, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
422       return 0;
423     }
424
425     if (req.host().empty()) {
426       req.host(req.authority());
427     }
428
429     handler->call_on_request(*stream);
430
431     if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
432       stream->get_request()->impl().call_on_end();
433     }
434
435     break;
436   }
437   }
438
439   return 0;
440 }
441 } // namespace
442
443 namespace {
444 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
445                                 int32_t stream_id, const uint8_t *data,
446                                 size_t len, void *user_data) {
447   auto handler = static_cast<http2_handler *>(user_data);
448   auto stream = handler->find_stream(stream_id);
449
450   if (!stream) {
451     return 0;
452   }
453
454   stream->get_request()->impl().call_on_data(data, len);
455
456   return 0;
457 }
458
459 } // namespace
460
461 namespace {
462 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
463                              uint32_t error_code, void *user_data) {
464   auto handler = static_cast<http2_handler *>(user_data);
465
466   handler->close_stream(stream_id);
467
468   return 0;
469 }
470 } // namespace
471
472 namespace {
473 int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
474                            void *user_data) {
475   auto handler = static_cast<http2_handler *>(user_data);
476
477   if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
478     return 0;
479   }
480
481   auto stream = handler->find_stream(frame->push_promise.promised_stream_id);
482
483   if (!stream) {
484     return 0;
485   }
486
487   handler->call_on_request(*stream);
488
489   return 0;
490 }
491 } // namespace
492
493 namespace {
494 int on_frame_not_send_callback(nghttp2_session *session,
495                                const nghttp2_frame *frame, int lib_error_code,
496                                void *user_data) {
497   if (frame->hd.type != NGHTTP2_HEADERS) {
498     return 0;
499   }
500
501   // Issue RST_STREAM so that stream does not hang around.
502   nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
503                             NGHTTP2_INTERNAL_ERROR);
504
505   return 0;
506 }
507 } // namespace
508
509 http2_handler::http2_handler(boost::asio::io_service &io_service,
510                              boost::asio::io_service &task_io_service_,
511                              connection_write writefun, request_cb cb)
512     : writefun_(writefun), request_cb_(std::move(cb)), io_service_(io_service),
513       task_io_service_(task_io_service_),
514       strand_(std::make_shared<boost::asio::io_service::strand>(io_service_)),
515       session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {}
516
517 http2_handler::~http2_handler() { nghttp2_session_del(session_); }
518
519 int http2_handler::start() {
520   int rv;
521
522   nghttp2_session_callbacks *callbacks;
523   rv = nghttp2_session_callbacks_new(&callbacks);
524   if (rv != 0) {
525     return -1;
526   }
527
528   auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
529
530   nghttp2_session_callbacks_set_on_begin_headers_callback(
531       callbacks, on_begin_headers_callback);
532   nghttp2_session_callbacks_set_on_header_callback(callbacks,
533                                                    on_header_callback);
534   nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
535                                                        on_frame_recv_callback);
536   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
537       callbacks, on_data_chunk_recv_callback);
538   nghttp2_session_callbacks_set_on_stream_close_callback(
539       callbacks, on_stream_close_callback);
540   nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
541                                                        on_frame_send_callback);
542   nghttp2_session_callbacks_set_on_frame_not_send_callback(
543       callbacks, on_frame_not_send_callback);
544
545   nghttp2_option *option;
546   rv = nghttp2_option_new(&option);
547   if (rv != 0) {
548     return -1;
549   }
550
551   auto opt_del = defer(nghttp2_option_del, option);
552
553   nghttp2_option_set_recv_client_preface(option, 1);
554
555   rv = nghttp2_session_server_new2(&session_, callbacks, this, option);
556   if (rv != 0) {
557     return -1;
558   }
559
560   nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
561   nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1);
562
563   return 0;
564 }
565
566 std::shared_ptr<http2_stream> http2_handler::create_stream(int32_t stream_id) {
567   auto stream = std::make_shared<http2_stream>(stream_id);
568   streams_.emplace(stream_id, stream);
569
570   auto self = shared_from_this();
571   auto &req = stream->get_request()->impl();
572   auto &res = stream->get_response()->impl();
573   req.handler(self);
574   req.stream(stream);
575   res.handler(self);
576   res.stream(stream);
577
578   return stream;
579 }
580
581 void http2_handler::close_stream(int32_t stream_id) {
582   streams_.erase(stream_id);
583 }
584
585 std::shared_ptr<http2_stream> http2_handler::find_stream(int32_t stream_id) {
586   auto i = streams_.find(stream_id);
587   if (i == std::end(streams_)) {
588     return nullptr;
589   }
590
591   return (*i).second;
592 }
593
594 void http2_handler::call_on_request(http2_stream &stream) {
595   request_cb_(stream.get_request(), stream.get_response());
596 }
597
598 bool http2_handler::should_stop() const {
599   return !nghttp2_session_want_read(session_) &&
600          !nghttp2_session_want_write(session_);
601 }
602
603 int http2_handler::start_response(http2_stream &stream) {
604   int rv;
605
606   auto &res = stream.get_response()->impl();
607   auto &headers = res.headers();
608   auto nva = std::vector<nghttp2_nv>();
609   nva.reserve(2 + headers.size());
610   auto status = util::utos(res.status_code());
611   auto date = cached_date;
612   nva.push_back(nghttp2::http2::make_nv_ls(":status", status));
613   nva.push_back(nghttp2::http2::make_nv_ls("date", *date));
614   for (auto &hd : headers) {
615     nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
616   }
617
618   nghttp2_data_provider prd;
619   prd.source.ptr = &stream;
620   prd.read_callback =
621       [](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
622          size_t length, uint32_t *data_flags, nghttp2_data_source *source,
623          void *user_data) -> ssize_t {
624     auto &stream = *static_cast<http2_stream *>(source->ptr);
625     auto rv = stream.get_response()->impl().call_read(buf, length);
626     if (rv.first < 0) {
627       return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
628     }
629
630     if (rv.second) {
631       *data_flags |= NGHTTP2_DATA_FLAG_EOF;
632     } else if (rv.first == 0) {
633       return NGHTTP2_ERR_DEFERRED;
634     }
635
636     return rv.first;
637   };
638
639   rv = nghttp2_submit_response(session_, stream.get_stream_id(), nva.data(),
640                                nva.size(), &prd);
641
642   if (rv != 0) {
643     return -1;
644   }
645
646   return 0;
647 }
648
649 void http2_handler::enter_callback() {
650   assert(!inside_callback_);
651   inside_callback_ = true;
652 }
653
654 void http2_handler::leave_callback() {
655   assert(inside_callback_);
656   inside_callback_ = false;
657 }
658
659 bool http2_handler::inside_callback() const { return inside_callback_; }
660
661 void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) {
662   ::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code);
663 }
664
665 void http2_handler::initiate_write() { writefun_(); }
666
667 void http2_handler::resume(http2_stream &stream) {
668   nghttp2_session_resume_data(session_, stream.get_stream_id());
669 }
670
671 int http2_handler::push_promise(http2_stream &stream, std::string method,
672                                 std::string path, std::vector<header> headers) {
673   int rv;
674
675   auto &req = stream.get_request()->impl();
676
677   auto nva = std::vector<nghttp2_nv>();
678   nva.reserve(5 + headers.size());
679   nva.push_back(nghttp2::http2::make_nv_ls(":method", method));
680   nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.scheme()));
681   if (!req.authority().empty()) {
682     nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.authority()));
683   }
684   nva.push_back(nghttp2::http2::make_nv_ls(":path", path));
685   if (!req.host().empty()) {
686     nva.push_back(nghttp2::http2::make_nv_ls("host", req.host()));
687   }
688
689   for (auto &hd : headers) {
690     nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
691   }
692
693   rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
694                                    stream.get_stream_id(), nva.data(),
695                                    nva.size(), nullptr);
696
697   if (rv < 0) {
698     return -1;
699   }
700
701   auto promised_stream = create_stream(rv);
702   auto &promised_req = promised_stream->get_request()->impl();
703   promised_req.pushed(true);
704   promised_req.method(std::move(method));
705   promised_req.scheme(req.scheme());
706   promised_req.authority(req.authority());
707   promised_req.path(std::move(path));
708   promised_req.host(req.host());
709   promised_req.set_header(std::move(headers));
710   if (!req.host().empty()) {
711     promised_req.add_header("host", req.host());
712   }
713
714   return 0;
715 }
716
717 bool http2_handler::run_task(thread_cb start) {
718   auto strand = strand_;
719
720   try {
721     task_io_service_.post([start, strand]() {
722       channel chan;
723       chan.impl().strand(strand.get());
724
725       start(chan);
726     });
727
728     return true;
729   } catch (std::exception &ex) {
730     return false;
731   }
732 }
733
734 boost::asio::io_service &http2_handler::io_service() { return io_service_; }
735
736 callback_guard::callback_guard(http2_handler &h) : handler(h) {
737   handler.enter_callback();
738 }
739
740 callback_guard::~callback_guard() { handler.leave_callback(); }
741
742 } // namespace server
743
744 } // namespace asio_http2
745
746 } // namespace nghttp2