diff --git a/examples/asio-sv.cc b/examples/asio-sv.cc index 5578d037..c880d0d6 100644 --- a/examples/asio-sv.cc +++ b/examples/asio-sv.cc @@ -70,6 +70,17 @@ int main(int argc, char *argv[]) { res.write_head(200); res.end("under construction!"); }); + server.handle("/push", [](const request &req, const response &res) { + boost::system::error_code ec; + auto push = res.push(ec, "GET", "/push/1"); + if (!ec) { + push->write_head(200); + push->end("server push FTW!"); + } + + res.write_head(200); + res.end("you'll receive server push!"); + }); server.listen("*", port); } catch (std::exception &e) { diff --git a/src/asio_common.cc b/src/asio_common.cc index ec0df2b3..3fec23e9 100644 --- a/src/asio_common.cc +++ b/src/asio_common.cc @@ -63,6 +63,11 @@ read_cb string_reader(std::string data) { }; } +read_cb deferred_reader() { + return [](uint8_t *buf, size_t len, + uint32_t *data_flags) { return NGHTTP2_ERR_DEFERRED; }; +} + template std::shared_ptr> defer_shared(F &&f, T &&... t) { return std::make_shared>(std::forward(f), diff --git a/src/asio_common.h b/src/asio_common.h index fa3b01eb..28a69a31 100644 --- a/src/asio_common.h +++ b/src/asio_common.h @@ -37,9 +37,12 @@ namespace nghttp2 { namespace asio_http2 { +boost::system::error_code make_error_code(nghttp2_error ev); + read_cb string_reader(std::string data); -boost::system::error_code make_error_code(nghttp2_error ev); +// Returns read_cb, which just returns NGHTTP2_ERR_DEFERRED +read_cb deferred_reader(); template void split_path(uri_ref &dst, InputIt first, InputIt last) { diff --git a/src/asio_server_http2_handler.cc b/src/asio_server_http2_handler.cc index c6ca65d2..48685df9 100644 --- a/src/asio_server_http2_handler.cc +++ b/src/asio_server_http2_handler.cc @@ -204,8 +204,7 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, } auto &res = strm->response().impl(); - res.push_promise_sent(true); - res.start_response(); + res.push_promise_sent(); return 0; } diff --git a/src/asio_server_response.cc b/src/asio_server_response.cc index ef4403d8..3cb074de 100644 --- a/src/asio_server_response.cc +++ b/src/asio_server_response.cc @@ -64,8 +64,6 @@ boost::asio::io_service &response::io_service() const { return impl_->io_service(); } -bool response::started() const { return impl_->started(); } - response_impl &response::impl() const { return *impl_; } } // namespace server diff --git a/src/asio_server_response_impl.cc b/src/asio_server_response_impl.cc index 0c9d0406..205cac19 100644 --- a/src/asio_server_response_impl.cc +++ b/src/asio_server_response_impl.cc @@ -33,33 +33,58 @@ namespace asio_http2 { namespace server { response_impl::response_impl() - : strm_(nullptr), status_code_(200), started_(false), pushed_(false), + : strm_(nullptr), read_cb_(deferred_reader()), status_code_(200), + state_(response_state::INITIAL), pushed_(false), push_promise_sent_(false) {} unsigned int response_impl::status_code() const { return status_code_; } void response_impl::write_head(unsigned int status_code, header_map h) { - status_code_ = status_code; - header_ = std::move(h); -} - -void response_impl::end(std::string data) { - if (started_) { + if (state_ != response_state::INITIAL) { return; } + status_code_ = status_code; + header_ = std::move(h); + + state_ = response_state::HEADER_DONE; + + if (pushed_ && !push_promise_sent_) { + return; + } + + start_response(); +} + +void response_impl::end(std::string data) { end(string_reader(std::move(data))); } void response_impl::end(read_cb cb) { - if (started_) { + if (state_ == response_state::BODY_STARTED) { return; } read_cb_ = std::move(cb); - started_ = true; - start_response(); + if (state_ == response_state::INITIAL) { + write_head(status_code_); + } else { + // read_cb is changed, start writing in case it is deferred. + auto handler = strm_->handler(); + handler->resume(*strm_); + } + + state_ = response_state::BODY_STARTED; +} + +void response_impl::start_response() { + auto handler = strm_->handler(); + + if (handler->start_response(*strm_) != 0) { + handler->stream_error(strm_->get_stream_id(), NGHTTP2_INTERNAL_ERROR); + return; + } } void response_impl::on_close(close_cb cb) { close_cb_ = std::move(cb); } @@ -75,19 +100,6 @@ void response_impl::cancel(uint32_t error_code) { handler->stream_error(strm_->get_stream_id(), error_code); } -void response_impl::start_response() { - if (!started_ || (pushed_ && !push_promise_sent_)) { - return; - } - - auto handler = strm_->handler(); - - if (handler->start_response(*strm_) != 0) { - handler->stream_error(strm_->get_stream_id(), NGHTTP2_INTERNAL_ERROR); - return; - } -} - response *response_impl::push(boost::system::error_code &ec, std::string method, std::string raw_path_query, header_map h) const { auto handler = strm_->handler(); @@ -104,11 +116,18 @@ boost::asio::io_service &response_impl::io_service() { return strm_->handler()->io_service(); } -bool response_impl::started() const { return started_; } - void response_impl::pushed(bool f) { pushed_ = f; } -void response_impl::push_promise_sent(bool f) { push_promise_sent_ = f; } +void response_impl::push_promise_sent() { + if (push_promise_sent_) { + return; + } + push_promise_sent_ = true; + if (state_ == response_state::INITIAL) { + return; + } + start_response(); +} const header_map &response_impl::header() const { return header_; } diff --git a/src/asio_server_response_impl.h b/src/asio_server_response_impl.h index 5e1246ff..fb3e3172 100644 --- a/src/asio_server_response_impl.h +++ b/src/asio_server_response_impl.h @@ -35,6 +35,14 @@ namespace server { class stream; +enum class response_state { + INITIAL, + // response_impl::write_head() was called + HEADER_DONE, + // response_impl::end() was called + BODY_STARTED, +}; + class response_impl { public: response_impl(); @@ -55,9 +63,8 @@ public: unsigned int status_code() const; const header_map &header() const; - bool started() const; void pushed(bool f); - void push_promise_sent(bool f); + void push_promise_sent(); void stream(class stream *s); read_cb::result_type call_read(uint8_t *data, std::size_t len, uint32_t *data_flags); @@ -69,8 +76,7 @@ private: read_cb read_cb_; close_cb close_cb_; unsigned int status_code_; - // true if response started (end() is called) - bool started_; + response_state state_; // true if this is pushed stream's response bool pushed_; // true if PUSH_PROMISE is sent if this is response of a pushed diff --git a/src/includes/nghttp2/asio_http2_server.h b/src/includes/nghttp2/asio_http2_server.h index 35bab796..dfccc629 100644 --- a/src/includes/nghttp2/asio_http2_server.h +++ b/src/includes/nghttp2/asio_http2_server.h @@ -97,9 +97,6 @@ public: // Returns status code. unsigned int status_code() const; - // Returns true if response has been started. - bool started() const; - // Returns boost::asio::io_service this response is running on. boost::asio::io_service &io_service() const;