2 * nghttp2 - HTTP/2 C Library
4 * Copyright (c) 2015 Tatsuhiro Tsujikawa
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:
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
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.
25 #include "asio_client_session_impl.h"
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"
38 namespace asio_http2 {
41 session_impl::session_impl(boost::asio::io_service &io_service)
42 : wblen_(0), io_service_(io_service), resolver_(io_service),
43 session_(nullptr), data_pending_(nullptr), data_pendinglen_(0),
44 writing_(false), inside_callback_(false) {}
46 session_impl::~session_impl() {
47 // finish up all active stream
48 for (auto &p : streams_) {
49 auto &strm = p.second;
50 auto &req = strm->request().impl();
51 req.call_on_close(NGHTTP2_INTERNAL_ERROR);
54 nghttp2_session_del(session_);
57 void session_impl::start_resolve(const std::string &host,
58 const std::string &service) {
59 resolver_.async_resolve({host, service},
60 [this](const boost::system::error_code &ec,
61 tcp::resolver::iterator endpoint_it) {
67 start_connect(endpoint_it);
71 void session_impl::connected(tcp::resolver::iterator endpoint_it) {
72 if (!setup_session()) {
76 socket().set_option(boost::asio::ip::tcp::no_delay(true));
81 auto &connect_cb = on_connect();
83 connect_cb(endpoint_it);
87 void session_impl::not_connected(const boost::system::error_code &ec) {
91 void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); }
93 void session_impl::on_error(error_cb cb) { error_cb_ = std::move(cb); }
95 const connect_cb &session_impl::on_connect() const { return connect_cb_; }
97 const error_cb &session_impl::on_error() const { return error_cb_; }
99 void session_impl::call_error_cb(const boost::system::error_code &ec) {
100 auto &error_cb = on_error();
108 int on_begin_headers_callback(nghttp2_session *session,
109 const nghttp2_frame *frame, void *user_data) {
110 if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
114 auto sess = static_cast<session_impl *>(user_data);
115 sess->create_push_stream(frame->push_promise.promised_stream_id);
122 int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
123 const uint8_t *name, size_t namelen,
124 const uint8_t *value, size_t valuelen, uint8_t flags,
126 auto sess = static_cast<session_impl *>(user_data);
129 switch (frame->hd.type) {
130 case NGHTTP2_HEADERS: {
131 strm = sess->find_stream(frame->hd.stream_id);
137 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
138 !strm->expect_final_response()) {
142 auto token = http2::lookup_token(name, namelen);
144 auto &res = strm->response().impl();
145 if (token == http2::HD__STATUS) {
146 res.status_code(util::parse_uint(value, valuelen));
149 if (token == http2::HD_CONTENT_LENGTH) {
150 res.content_length(util::parse_uint(value, valuelen));
153 res.header().emplace(
154 std::string(name, name + namelen),
155 header_value{std::string(value, value + valuelen),
156 (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
160 case NGHTTP2_PUSH_PROMISE: {
161 strm = sess->find_stream(frame->push_promise.promised_stream_id);
166 auto &req = strm->request().impl();
167 auto &uri = req.uri();
169 switch (http2::lookup_token(name, namelen)) {
170 case http2::HD__METHOD:
171 req.method(std::string(value, value + valuelen));
173 case http2::HD__SCHEME:
174 uri.scheme.assign(value, value + valuelen);
176 case http2::HD__PATH:
177 split_path(uri, value, value + valuelen);
179 case http2::HD__AUTHORITY:
180 uri.host.assign(value, value + valuelen);
183 if (uri.host.empty()) {
184 uri.host.assign(value, value + valuelen);
188 req.header().emplace(
189 std::string(name, name + namelen),
190 header_value{std::string(value, value + valuelen),
191 (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0});
205 int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
207 auto sess = static_cast<session_impl *>(user_data);
208 auto strm = sess->find_stream(frame->hd.stream_id);
210 switch (frame->hd.type) {
215 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
216 strm->response().impl().call_on_data(nullptr, 0);
220 case NGHTTP2_HEADERS: {
226 if (frame->headers.cat == NGHTTP2_HCAT_HEADERS &&
227 !strm->expect_final_response()) {
231 if (strm->expect_final_response()) {
232 // wait for final response
236 auto &req = strm->request().impl();
237 req.call_on_response(strm->response());
238 if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
239 strm->response().impl().call_on_data(nullptr, 0);
243 case NGHTTP2_PUSH_PROMISE: {
248 auto push_strm = sess->find_stream(frame->push_promise.promised_stream_id);
253 strm->request().impl().call_on_push(push_strm->request());
263 int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
264 int32_t stream_id, const uint8_t *data,
265 size_t len, void *user_data) {
266 auto sess = static_cast<session_impl *>(user_data);
267 auto strm = sess->find_stream(stream_id);
272 auto &res = strm->response().impl();
273 res.call_on_data(data, len);
280 int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
281 uint32_t error_code, void *user_data) {
282 auto sess = static_cast<session_impl *>(user_data);
283 auto strm = sess->pop_stream(stream_id);
288 strm->request().impl().call_on_close(error_code);
294 bool session_impl::setup_session() {
295 nghttp2_session_callbacks *callbacks;
296 nghttp2_session_callbacks_new(&callbacks);
297 auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
299 nghttp2_session_callbacks_set_on_begin_headers_callback(
300 callbacks, on_begin_headers_callback);
301 nghttp2_session_callbacks_set_on_header_callback(callbacks,
303 nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
304 on_frame_recv_callback);
305 nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
306 callbacks, on_data_chunk_recv_callback);
307 nghttp2_session_callbacks_set_on_stream_close_callback(
308 callbacks, on_stream_close_callback);
310 auto rv = nghttp2_session_client_new(&session_, callbacks, this);
312 call_error_cb(make_error_code(static_cast<nghttp2_error>(rv)));
316 const uint32_t window_size = 256 * 1024 * 1024;
318 std::array<nghttp2_settings_entry, 2> iv{
319 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100},
320 // typically client is just a *sink* and just process data as
321 // much as possible. Use large window size by default.
322 {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, window_size}}};
323 nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(), iv.size());
324 // increase connection window size up to window_size
325 nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0,
327 NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE);
331 int session_impl::write_trailer(stream &strm, header_map h) {
333 auto nva = std::vector<nghttp2_nv>();
334 nva.reserve(h.size());
336 nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value,
337 hd.second.sensitive));
340 rv = nghttp2_submit_trailer(session_, strm.stream_id(), nva.data(),
352 void session_impl::cancel(stream &strm, uint32_t error_code) {
353 nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(),
358 void session_impl::resume(stream &strm) {
359 nghttp2_session_resume_data(session_, strm.stream_id());
363 stream *session_impl::find_stream(int32_t stream_id) {
364 auto it = streams_.find(stream_id);
365 if (it == std::end(streams_)) {
368 return (*it).second.get();
371 std::unique_ptr<stream> session_impl::pop_stream(int32_t stream_id) {
372 auto it = streams_.find(stream_id);
373 if (it == std::end(streams_)) {
376 auto strm = std::move((*it).second);
381 stream *session_impl::create_push_stream(int32_t stream_id) {
382 auto strm = create_stream();
383 strm->stream_id(stream_id);
384 auto p = streams_.emplace(stream_id, std::move(strm));
386 return (*p.first).second.get();
389 std::unique_ptr<stream> session_impl::create_stream() {
390 return make_unique<stream>(this);
393 const request *session_impl::submit(boost::system::error_code &ec,
394 const std::string &method,
395 const std::string &uri, generator_cb cb,
400 // TODO Handle CONNECT method
401 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
402 ec = make_error_code(boost::system::errc::invalid_argument);
406 if ((u.field_set & (1 << UF_SCHEMA)) == 0 ||
407 (u.field_set & (1 << UF_HOST)) == 0) {
408 ec = make_error_code(boost::system::errc::invalid_argument);
412 auto strm = create_stream();
413 auto &req = strm->request().impl();
414 auto &uref = req.uri();
416 http2::copy_url_component(uref.scheme, &u, UF_SCHEMA, uri.c_str());
417 http2::copy_url_component(uref.host, &u, UF_HOST, uri.c_str());
418 http2::copy_url_component(uref.raw_path, &u, UF_PATH, uri.c_str());
419 http2::copy_url_component(uref.raw_query, &u, UF_QUERY, uri.c_str());
421 if (util::ipv6_numeric_addr(uref.host.c_str())) {
422 uref.host = "[" + uref.host;
425 if (u.field_set & (1 << UF_PORT)) {
427 uref.host += util::utos(u.port);
430 if (uref.raw_path.empty()) {
434 uref.path = percent_decode(uref.raw_path);
436 auto path = uref.raw_path;
437 if (u.field_set & (1 << UF_QUERY)) {
439 path += uref.raw_query;
442 auto nva = std::vector<nghttp2_nv>();
443 nva.reserve(3 + h.size());
444 nva.push_back(http2::make_nv_ls(":method", method));
445 nva.push_back(http2::make_nv_ls(":scheme", uref.scheme));
446 nva.push_back(http2::make_nv_ls(":path", path));
447 nva.push_back(http2::make_nv_ls(":authority", uref.host));
450 http2::make_nv(kv.first, kv.second.value, kv.second.sensitive));
453 req.header(std::move(h));
455 nghttp2_data_provider *prdptr = nullptr;
456 nghttp2_data_provider prd;
459 strm->request().impl().on_read(std::move(cb));
460 prd.source.ptr = strm.get();
462 [](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
463 size_t length, uint32_t *data_flags, nghttp2_data_source *source,
464 void *user_data) -> ssize_t {
465 auto strm = static_cast<stream *>(source->ptr);
466 return strm->request().impl().call_on_read(buf, length, data_flags);
471 auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(),
472 nva.size(), prdptr, strm.get());
474 ec = make_error_code(static_cast<nghttp2_error>(stream_id));
480 strm->stream_id(stream_id);
482 auto p = streams_.emplace(stream_id, std::move(strm));
484 return &(*p.first).second->request();
487 void session_impl::shutdown() {
488 nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
492 boost::asio::io_service &session_impl::io_service() { return io_service_; }
494 void session_impl::signal_write() {
495 if (!inside_callback_) {
500 bool session_impl::should_stop() const {
501 return !writing_ && !nghttp2_session_want_read(session_) &&
502 !nghttp2_session_want_write(session_);
506 struct callback_guard {
507 callback_guard(session_impl &sess) : sess(sess) { sess.enter_callback(); }
508 ~callback_guard() { sess.leave_callback(); }
514 void session_impl::enter_callback() {
515 assert(!inside_callback_);
516 inside_callback_ = true;
519 void session_impl::leave_callback() {
520 assert(inside_callback_);
521 inside_callback_ = false;
524 void session_impl::do_read() {
525 read_socket([this](const boost::system::error_code &ec,
526 std::size_t bytes_transferred) {
528 if (ec.value() == boost::asio::error::operation_aborted) {
536 callback_guard cg(*this);
539 nghttp2_session_mem_recv(session_, rb_.data(), bytes_transferred);
541 if (rv != static_cast<ssize_t>(bytes_transferred)) {
542 call_error_cb(make_error_code(
543 static_cast<nghttp2_error>(rv < 0 ? rv : NGHTTP2_ERR_PROTO)));
560 void session_impl::do_write() {
566 std::copy_n(data_pending_, data_pendinglen_, std::begin(wb_) + wblen_);
568 wblen_ += data_pendinglen_;
570 data_pending_ = nullptr;
571 data_pendinglen_ = 0;
575 callback_guard cg(*this);
579 auto n = nghttp2_session_mem_send(session_, &data);
581 call_error_cb(make_error_code(static_cast<nghttp2_error>(n)));
590 if (wblen_ + n > wb_.size()) {
591 data_pending_ = data;
592 data_pendinglen_ = n;
597 std::copy_n(data, n, std::begin(wb_) + wblen_);
609 write_socket([this](const boost::system::error_code &ec, std::size_t n) {
623 } // namespace client
624 } // namespace asio_http2