1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2015 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_client_session_impl.h"
26
27 #include <iostream>
28
29 #include "asio_client_stream.h"
30 #include "asio_client_request_impl.h"
31 #include "asio_client_response_impl.h"
32 #include "asio_common.h"
33 #include "template.h"
34 #include "util.h"
35 #include "http2.h"
36
37 namespace nghttp2 {
38 namespace asio_http2 {
39 namespace client {
40
session_impl(boost::asio::io_service & io_service,const boost::posix_time::time_duration & connect_timeout)41 session_impl::session_impl(
42 boost::asio::io_service &io_service,
43 const boost::posix_time::time_duration &connect_timeout)
44 : wblen_(0),
45 io_service_(io_service),
46 resolver_(io_service),
47 deadline_(io_service),
48 connect_timeout_(connect_timeout),
49 read_timeout_(boost::posix_time::seconds(60)),
50 ping_(io_service),
51 session_(nullptr),
52 data_pending_(nullptr),
53 data_pendinglen_(0),
54 writing_(false),
55 inside_callback_(false),
56 stopped_(false) {}
57
~session_impl()58 session_impl::~session_impl() {
59 // finish up all active stream
60 for (auto &p : streams_) {
61 auto &strm = p.second;
62 auto &req = strm->request().impl();
63 req.call_on_close(NGHTTP2_INTERNAL_ERROR);
64 }
65
66 nghttp2_session_del(session_);
67 }
68
start_resolve(const std::string & host,const std::string & service)69 void session_impl::start_resolve(const std::string &host,
70 const std::string &service) {
71 deadline_.expires_from_now(connect_timeout_);
72
73 auto self = shared_from_this();
74
75 resolver_.async_resolve({host, service},
76 [self](const boost::system::error_code &ec,
77 tcp::resolver::iterator endpoint_it) {
78 if (ec) {
79 self->not_connected(ec);
80 return;
81 }
82
83 self->start_connect(endpoint_it);
84 });
85
86 deadline_.async_wait(std::bind(&session_impl::handle_deadline, self));
87 }
88
handle_deadline()89 void session_impl::handle_deadline() {
90 if (stopped_) {
91 return;
92 }
93
94 if (deadline_.expires_at() <=
95 boost::asio::deadline_timer::traits_type::now()) {
96 call_error_cb(boost::asio::error::timed_out);
97 stop();
98 deadline_.expires_at(boost::posix_time::pos_infin);
99 return;
100 }
101
102 deadline_.async_wait(
103 std::bind(&session_impl::handle_deadline, this->shared_from_this()));
104 }
105
handle_ping2(const boost::system::error_code & ec,int)106 void handle_ping2(const boost::system::error_code &ec, int) {}
107
start_ping()108 void session_impl::start_ping() {
109 ping_.expires_from_now(boost::posix_time::seconds(30));
110 ping_.async_wait(std::bind(&session_impl::handle_ping, shared_from_this(),
111 std::placeholders::_1));
112 }
113
handle_ping(const boost::system::error_code & ec)114 void session_impl::handle_ping(const boost::system::error_code &ec) {
115 if (stopped_ || ec == boost::asio::error::operation_aborted ||
116 !streams_.empty()) {
117 return;
118 }
119
120 nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, nullptr);
121
122 signal_write();
123
124 start_ping();
125 }
126
connected(tcp::resolver::iterator endpoint_it)127 void session_impl::connected(tcp::resolver::iterator endpoint_it) {
128 if (!setup_session()) {
129 return;
130 }
131
132 socket().set_option(boost::asio::ip::tcp::no_delay(true));
133
134 do_write();
135 do_read();
136
137 start_ping();
138
139 auto &connect_cb = on_connect();
140 if (connect_cb) {
141 connect_cb(endpoint_it);
142 }
143 }
144
not_connected(const boost::system::error_code & ec)145 void session_impl::not_connected(const boost::system::error_code &ec) {
146 call_error_cb(ec);
147 stop();
148 }
149
on_connect(connect_cb cb)150 void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
151
on_error(error_cb cb)152 void session_impl::on_error(error_cb cb) { error_cb_ = std::move(cb); }
153
on_connect() const154 const connect_cb &session_impl::on_connect() const { return connect_cb_; }
155
on_error() const156 const error_cb &session_impl::on_error() const { return error_cb_; }
157
call_error_cb(const boost::system::error_code & ec)158 void session_impl::call_error_cb(const boost::system::error_code &ec) {
159 if (stopped_) {
160 return;
161 }
162 auto &error_cb = on_error();
163 if (!error_cb) {
164 return;
165 }
166 error_cb(ec);
167 }
168
169 namespace {
on_begin_headers_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)170 int on_begin_headers_callback(nghttp2_session *session,
171 const nghttp2_frame *frame, void *user_data) {
172 if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
173 return 0;
174 }
175
176 auto sess = static_cast<session_impl *>(user_data);
177 sess->create_push_stream(frame->push_promise.promised_stream_id);
178
179 return 0;
180 }
181 } // namespace
182
183 namespace {
on_header_callback(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * user_data)184 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
185 const uint8_t *name, size_t namelen,
186 const uint8_t *value, size_t valuelen, uint8_t flags,
187 void *user_data) {
188 auto sess = static_cast<session_impl *>(user_data);
189 stream *strm;
190
191 switch (frame->hd.type) {
192 case NGHTTP2_HEADERS: {
193 strm = sess->find_stream(frame->hd.stream_id);
194 if (!strm) {
195 return 0;
196 }
197
198 // ignore trailers
199 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
200 !strm->expect_final_response()) {
201 return 0;
202 }
203
204 auto token = http2::lookup_token(name, namelen);
205
206 auto &res = strm->response().impl();
207 if (token == http2::HD__STATUS) {
208 res.status_code(util::parse_uint(value, valuelen));
209 } else {
210 if (res.header_buffer_size() + namelen + valuelen > 64_k) {
211 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
212 frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
213 break;
214 }
215 res.update_header_buffer_size(namelen + valuelen);
216
217 if (token == http2::HD_CONTENT_LENGTH) {
218 res.content_length(util::parse_uint(value, valuelen));
219 }
220
221 res.header().emplace(
222 std::string(name, name + namelen),
223 header_value{std::string(value, value + valuelen),
224 (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
225 }
226 break;
227 }
228 case NGHTTP2_PUSH_PROMISE: {
229 strm = sess->find_stream(frame->push_promise.promised_stream_id);
230 if (!strm) {
231 return 0;
232 }
233
234 auto &req = strm->request().impl();
235 auto &uri = req.uri();
236
237 switch (http2::lookup_token(name, namelen)) {
238 case http2::HD__METHOD:
239 req.method(std::string(value, value + valuelen));
240 break;
241 case http2::HD__SCHEME:
242 uri.scheme.assign(value, value + valuelen);
243 break;
244 case http2::HD__PATH:
245 split_path(uri, value, value + valuelen);
246 break;
247 case http2::HD__AUTHORITY:
248 uri.host.assign(value, value + valuelen);
249 break;
250 case http2::HD_HOST:
251 if (uri.host.empty()) {
252 uri.host.assign(value, value + valuelen);
253 }
254 // fall through
255 default:
256 if (req.header_buffer_size() + namelen + valuelen > 64_k) {
257 nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
258 frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
259 break;
260 }
261 req.update_header_buffer_size(namelen + valuelen);
262
263 req.header().emplace(
264 std::string(name, name + namelen),
265 header_value{std::string(value, value + valuelen),
266 (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
267 }
268
269 break;
270 }
271 default:
272 return 0;
273 }
274
275 return 0;
276 }
277 } // namespace
278
279 namespace {
on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)280 int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
281 void *user_data) {
282 auto sess = static_cast<session_impl *>(user_data);
283 auto strm = sess->find_stream(frame->hd.stream_id);
284
285 switch (frame->hd.type) {
286 case NGHTTP2_DATA: {
287 if (!strm) {
288 return 0;
289 }
290 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
291 strm->response().impl().call_on_data(nullptr, 0);
292 }
293 break;
294 }
295 case NGHTTP2_HEADERS: {
296 if (!strm) {
297 return 0;
298 }
299
300 // ignore trailers
301 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
302 !strm->expect_final_response()) {
303 return 0;
304 }
305
306 if (strm->expect_final_response()) {
307 // wait for final response
308 return 0;
309 }
310
311 auto &req = strm->request().impl();
312 req.call_on_response(strm->response());
313 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
314 strm->response().impl().call_on_data(nullptr, 0);
315 }
316 break;
317 }
318 case NGHTTP2_PUSH_PROMISE: {
319 if (!strm) {
320 return 0;
321 }
322
323 auto push_strm = sess->find_stream(frame->push_promise.promised_stream_id);
324 if (!push_strm) {
325 return 0;
326 }
327
328 strm->request().impl().call_on_push(push_strm->request());
329
330 break;
331 }
332 }
333 return 0;
334 }
335 } // namespace
336
337 namespace {
on_data_chunk_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)338 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
339 int32_t stream_id, const uint8_t *data,
340 size_t len, void *user_data) {
341 auto sess = static_cast<session_impl *>(user_data);
342 auto strm = sess->find_stream(stream_id);
343 if (!strm) {
344 return 0;
345 }
346
347 auto &res = strm->response().impl();
348 res.call_on_data(data, len);
349
350 return 0;
351 }
352 } // namespace
353
354 namespace {
on_stream_close_callback(nghttp2_session * session,int32_t stream_id,uint32_t error_code,void * user_data)355 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
356 uint32_t error_code, void *user_data) {
357 auto sess = static_cast<session_impl *>(user_data);
358 auto strm = sess->pop_stream(stream_id);
359 if (!strm) {
360 return 0;
361 }
362
363 strm->request().impl().call_on_close(error_code);
364
365 return 0;
366 }
367 } // namespace
368
setup_session()369 bool session_impl::setup_session() {
370 nghttp2_session_callbacks *callbacks;
371 nghttp2_session_callbacks_new(&callbacks);
372 auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
373
374 nghttp2_session_callbacks_set_on_begin_headers_callback(
375 callbacks, on_begin_headers_callback);
376 nghttp2_session_callbacks_set_on_header_callback(callbacks,
377 on_header_callback);
378 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
379 on_frame_recv_callback);
380 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
381 callbacks, on_data_chunk_recv_callback);
382 nghttp2_session_callbacks_set_on_stream_close_callback(
383 callbacks, on_stream_close_callback);
384
385 auto rv = nghttp2_session_client_new(&session_, callbacks, this);
386 if (rv != 0) {
387 call_error_cb(make_error_code(static_cast<nghttp2_error>(rv)));
388 return false;
389 }
390
391 const uint32_t window_size = 256_m;
392
393 std::array<nghttp2_settings_entry, 2> iv{
394 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
395 // typically client is just a *sink* and just process data as
396 // much as possible. Use large window size by default.
397 {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, window_size}}};
398 nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), iv.size());
399 // increase connection window size up to window_size
400 nghttp2_session_set_local_window_size(session_, NGHTTP2_FLAG_NONE, 0,
401 window_size);
402 return true;
403 }
404
write_trailer(stream & strm,header_map h)405 int session_impl::write_trailer(stream &strm, header_map h) {
406 int rv;
407 auto nva = std::vector<nghttp2_nv>();
408 nva.reserve(h.size());
409 for (auto &hd : h) {
410 nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value,
411 hd.second.sensitive));
412 }
413
414 rv = nghttp2_submit_trailer(session_, strm.stream_id(), nva.data(),
415 nva.size());
416
417 if (rv != 0) {
418 return -1;
419 }
420
421 signal_write();
422
423 return 0;
424 }
425
cancel(stream & strm,uint32_t error_code)426 void session_impl::cancel(stream &strm, uint32_t error_code) {
427 if (stopped_) {
428 return;
429 }
430
431 nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
432 error_code);
433 signal_write();
434 }
435
resume(stream & strm)436 void session_impl::resume(stream &strm) {
437 if (stopped_) {
438 return;
439 }
440
441 nghttp2_session_resume_data(session_, strm.stream_id());
442 signal_write();
443 }
444
find_stream(int32_t stream_id)445 stream *session_impl::find_stream(int32_t stream_id) {
446 auto it = streams_.find(stream_id);
447 if (it == std::end(streams_)) {
448 return nullptr;
449 }
450 return (*it).second.get();
451 }
452
pop_stream(int32_t stream_id)453 std::unique_ptr<stream> session_impl::pop_stream(int32_t stream_id) {
454 auto it = streams_.find(stream_id);
455 if (it == std::end(streams_)) {
456 return nullptr;
457 }
458 auto strm = std::move((*it).second);
459 streams_.erase(it);
460 if (streams_.empty()) {
461 start_ping();
462 }
463 return strm;
464 }
465
create_push_stream(int32_t stream_id)466 stream *session_impl::create_push_stream(int32_t stream_id) {
467 auto strm = create_stream();
468 strm->stream_id(stream_id);
469 auto p = streams_.emplace(stream_id, std::move(strm));
470 assert(p.second);
471 ping_.cancel();
472 return (*p.first).second.get();
473 }
474
create_stream()475 std::unique_ptr<stream> session_impl::create_stream() {
476 return std::make_unique<stream>(this);
477 }
478
submit(boost::system::error_code & ec,const std::string & method,const std::string & uri,generator_cb cb,header_map h,priority_spec prio)479 const request *session_impl::submit(boost::system::error_code &ec,
480 const std::string &method,
481 const std::string &uri, generator_cb cb,
482 header_map h, priority_spec prio) {
483 ec.clear();
484
485 if (stopped_) {
486 ec = make_error_code(static_cast<nghttp2_error>(NGHTTP2_INTERNAL_ERROR));
487 return nullptr;
488 }
489
490 http_parser_url u{};
491 // TODO Handle CONNECT method
492 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
493 ec = make_error_code(boost::system::errc::invalid_argument);
494 return nullptr;
495 }
496
497 if ((u.field_set & (1 << UF_SCHEMA)) == 0 ||
498 (u.field_set & (1 << UF_HOST)) == 0) {
499 ec = make_error_code(boost::system::errc::invalid_argument);
500 return nullptr;
501 }
502
503 auto strm = create_stream();
504 auto &req = strm->request().impl();
505 auto &uref = req.uri();
506
507 http2::copy_url_component(uref.scheme, &u, UF_SCHEMA, uri.c_str());
508 http2::copy_url_component(uref.host, &u, UF_HOST, uri.c_str());
509 http2::copy_url_component(uref.raw_path, &u, UF_PATH, uri.c_str());
510 http2::copy_url_component(uref.raw_query, &u, UF_QUERY, uri.c_str());
511
512 if (util::ipv6_numeric_addr(uref.host.c_str())) {
513 uref.host = "[" + uref.host;
514 uref.host += ']';
515 }
516 if (u.field_set & (1 << UF_PORT)) {
517 uref.host += ':';
518 uref.host += util::utos(u.port);
519 }
520
521 if (uref.raw_path.empty()) {
522 uref.raw_path = "/";
523 }
524
525 uref.path = percent_decode(uref.raw_path);
526
527 auto path = uref.raw_path;
528 if (u.field_set & (1 << UF_QUERY)) {
529 path += '?';
530 path += uref.raw_query;
531 }
532
533 auto nva = std::vector<nghttp2_nv>();
534 nva.reserve(4 + h.size());
535 nva.push_back(http2::make_nv_ls(":method", method));
536 nva.push_back(http2::make_nv_ls(":scheme", uref.scheme));
537 nva.push_back(http2::make_nv_ls(":path", path));
538 nva.push_back(http2::make_nv_ls(":authority", uref.host));
539 for (auto &kv : h) {
540 nva.push_back(
541 http2::make_nv(kv.first, kv.second.value, kv.second.sensitive));
542 }
543
544 req.header(std::move(h));
545
546 nghttp2_data_provider *prdptr = nullptr;
547 nghttp2_data_provider prd;
548
549 if (cb) {
550 strm->request().impl().on_read(std::move(cb));
551 prd.source.ptr = strm.get();
552 prd.read_callback = [](nghttp2_session *session, int32_t stream_id,
553 uint8_t *buf, size_t length, uint32_t *data_flags,
554 nghttp2_data_source *source,
555 void *user_data) -> ssize_t {
556 auto strm = static_cast<stream *>(source->ptr);
557 return strm->request().impl().call_on_read(buf, length, data_flags);
558 };
559 prdptr = &prd;
560 }
561
562 auto stream_id = nghttp2_submit_request(session_, prio.get(), nva.data(),
563 nva.size(), prdptr, strm.get());
564 if (stream_id < 0) {
565 ec = make_error_code(static_cast<nghttp2_error>(stream_id));
566 return nullptr;
567 }
568
569 signal_write();
570
571 strm->stream_id(stream_id);
572
573 auto p = streams_.emplace(stream_id, std::move(strm));
574 assert(p.second);
575 ping_.cancel();
576 return &(*p.first).second->request();
577 }
578
shutdown()579 void session_impl::shutdown() {
580 if (stopped_) {
581 return;
582 }
583
584 nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
585 signal_write();
586 }
587
io_service()588 boost::asio::io_service &session_impl::io_service() { return io_service_; }
589
signal_write()590 void session_impl::signal_write() {
591 if (!inside_callback_) {
592 do_write();
593 }
594 }
595
should_stop() const596 bool session_impl::should_stop() const {
597 return !writing_ && !nghttp2_session_want_read(session_) &&
598 !nghttp2_session_want_write(session_);
599 }
600
601 namespace {
602 struct callback_guard {
callback_guardnghttp2::asio_http2::client::__anonb941e1e30811::callback_guard603 callback_guard(session_impl &sess) : sess(sess) { sess.enter_callback(); }
~callback_guardnghttp2::asio_http2::client::__anonb941e1e30811::callback_guard604 ~callback_guard() { sess.leave_callback(); }
605
606 session_impl &sess;
607 };
608 } // namespace
609
enter_callback()610 void session_impl::enter_callback() {
611 assert(!inside_callback_);
612 inside_callback_ = true;
613 }
614
leave_callback()615 void session_impl::leave_callback() {
616 assert(inside_callback_);
617 inside_callback_ = false;
618 }
619
do_read()620 void session_impl::do_read() {
621 if (stopped_) {
622 return;
623 }
624
625 deadline_.expires_from_now(read_timeout_);
626
627 auto self = this->shared_from_this();
628
629 read_socket([self](const boost::system::error_code &ec,
630 std::size_t bytes_transferred) {
631 if (ec) {
632 if (!self->should_stop()) {
633 self->call_error_cb(ec);
634 }
635 self->stop();
636 return;
637 }
638
639 {
640 callback_guard cg(*self);
641
642 auto rv = nghttp2_session_mem_recv(self->session_, self->rb_.data(),
643 bytes_transferred);
644
645 if (rv != static_cast<ssize_t>(bytes_transferred)) {
646 self->call_error_cb(make_error_code(
647 static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
648 self->stop();
649 return;
650 }
651 }
652
653 self->do_write();
654
655 if (self->should_stop()) {
656 self->stop();
657 return;
658 }
659
660 self->do_read();
661 });
662 }
663
do_write()664 void session_impl::do_write() {
665 if (stopped_) {
666 return;
667 }
668
669 if (writing_) {
670 return;
671 }
672
673 if (data_pending_) {
674 std::copy_n(data_pending_, data_pendinglen_, std::begin(wb_) + wblen_);
675
676 wblen_ += data_pendinglen_;
677
678 data_pending_ = nullptr;
679 data_pendinglen_ = 0;
680 }
681
682 {
683 callback_guard cg(*this);
684
685 for (;;) {
686 const uint8_t *data;
687 auto n = nghttp2_session_mem_send(session_, &data);
688 if (n < 0) {
689 call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
690 stop();
691 return;
692 }
693
694 if (n == 0) {
695 break;
696 }
697
698 if (wblen_ + n > wb_.size()) {
699 data_pending_ = data;
700 data_pendinglen_ = n;
701
702 break;
703 }
704
705 std::copy_n(data, n, std::begin(wb_) + wblen_);
706
707 wblen_ += n;
708 }
709 }
710
711 if (wblen_ == 0) {
712 if (should_stop()) {
713 stop();
714 }
715 return;
716 }
717
718 writing_ = true;
719
720 // Reset read deadline here, because normally client is sending
721 // something, it does not expect timeout while doing it.
722 deadline_.expires_from_now(read_timeout_);
723
724 auto self = this->shared_from_this();
725
726 write_socket([self](const boost::system::error_code &ec, std::size_t n) {
727 if (ec) {
728 self->call_error_cb(ec);
729 self->stop();
730 return;
731 }
732
733 self->wblen_ = 0;
734 self->writing_ = false;
735
736 self->do_write();
737 });
738 }
739
stop()740 void session_impl::stop() {
741 if (stopped_) {
742 return;
743 }
744
745 shutdown_socket();
746 deadline_.cancel();
747 ping_.cancel();
748 stopped_ = true;
749 }
750
stopped() const751 bool session_impl::stopped() const { return stopped_; }
752
read_timeout(const boost::posix_time::time_duration & t)753 void session_impl::read_timeout(const boost::posix_time::time_duration &t) {
754 read_timeout_ = t;
755 }
756
757 } // namespace client
758 } // namespace asio_http2
759 } // namespace nghttp2
760