diff --git a/src/HttpServer.cc b/src/HttpServer.cc index fbf836a9..7c7093db 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -843,6 +843,16 @@ int Http2Handler::submit_response(const std::string& status, data_prd); } +int Http2Handler::submit_non_final_response(const std::string& status, + int32_t stream_id) +{ + auto nva = std::vector{ + http2::make_nv_ls(":status", status) + }; + return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, + nullptr, nva.data(), nva.size(), nullptr); +} + int Http2Handler::submit_push_promise(Stream *stream, const std::string& push_path) { @@ -1266,6 +1276,12 @@ int hd_on_frame_recv_callback return 0; } + auto expect100 = http2::get_header(stream->headers, "expect"); + + if(expect100 && util::strieq("100-continue", expect100->value.c_str())) { + hd->submit_non_final_response("100", frame->hd.stream_id); + } + if(hd->get_config()->early_response) { prepare_response(stream, hd); } diff --git a/src/HttpServer.h b/src/HttpServer.h index 1d3fa456..23048c92 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -126,6 +126,8 @@ public: const std::vector>& headers, nghttp2_data_provider *data_prd); + int submit_non_final_response(const std::string& status, int32_t stream_id); + int submit_push_promise(Stream *stream, const std::string& push_path); int submit_rst_stream(Stream *stream, nghttp2_error_code error_code); diff --git a/src/http2.cc b/src/http2.cc index 4971f970..e7ec30e6 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -165,7 +165,6 @@ size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]); namespace { const char *IGN_HD[] = { "connection", - "expect", "http2-settings", "keep-alive", "proxy-connection", @@ -186,7 +185,6 @@ namespace { const char *HTTP1_IGN_HD[] = { "connection", "cookie", - "expect", "http2-settings", "keep-alive", "proxy-connection", diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc index 64e155bf..8e8d9fcd 100644 --- a/src/shrpx_downstream.cc +++ b/src/shrpx_downstream.cc @@ -63,12 +63,12 @@ Downstream::Downstream(Upstream *upstream, int stream_id, int priority) http2_settings_seen_(false), chunked_request_(false), request_connection_close_(false), - request_expect_100_continue_(false), request_header_key_prev_(false), request_http2_expect_body_(false), chunked_response_(false), response_connection_close_(false), - response_header_key_prev_(false) + response_header_key_prev_(false), + expect_final_response_(false) {} Downstream::~Downstream() @@ -424,11 +424,6 @@ void Downstream::set_request_http2_expect_body(bool f) request_http2_expect_body_ = f; } -bool Downstream::get_expect_100_continue() const -{ - return request_expect_100_continue_; -} - bool Downstream::get_output_buffer_full() { if(dconn_) { @@ -744,11 +739,6 @@ void Downstream::inspect_http1_request() if(util::strifind(hd.value.c_str(), "chunked")) { chunked_request_ = true; } - } else if(!request_expect_100_continue_ && - util::strieq(hd.name.c_str(), "expect")) { - if(util::strifind(hd.value.c_str(), "100-continue")) { - request_expect_100_continue_ = true; - } } } } @@ -765,6 +755,18 @@ void Downstream::inspect_http1_response() } } +void Downstream::reset_response() +{ + response_http_status_ = 0; + response_major_ = 1; + response_minor_ = 1; +} + +bool Downstream::get_non_final_response() const +{ + return response_http_status_ / 100 == 1; +} + bool Downstream::get_upgraded() const { return upgraded_; @@ -806,4 +808,14 @@ void Downstream::set_response_rst_stream_error_code response_rst_stream_error_code_ = error_code; } +void Downstream::set_expect_final_response(bool f) +{ + expect_final_response_ = f; +} + +bool Downstream::get_expect_final_response() const +{ + return expect_final_response_; +} + } // namespace shrpx diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h index 82105ec3..bd92d325 100644 --- a/src/shrpx_downstream.h +++ b/src/shrpx_downstream.h @@ -140,7 +140,6 @@ public: const std::string& get_request_user_agent() const; bool get_request_http2_expect_body() const; void set_request_http2_expect_body(bool f); - bool get_expect_100_continue() const; int push_upload_data_chunk(const uint8_t *data, size_t datalen); int end_upload_data(); enum { @@ -207,6 +206,11 @@ public: void set_response_rst_stream_error_code(nghttp2_error_code error_code); // Inspects HTTP/1 response. This checks tranfer-encoding etc. void inspect_http1_response(); + // Clears some of member variables for response. + void reset_response(); + bool get_non_final_response() const; + void set_expect_final_response(bool f); + bool get_expect_final_response() const; // Call this method when there is incoming data in downstream // connection. @@ -274,13 +278,13 @@ private: bool chunked_request_; bool request_connection_close_; - bool request_expect_100_continue_; bool request_header_key_prev_; bool request_http2_expect_body_; bool chunked_response_; bool response_connection_close_; bool response_header_key_prev_; + bool expect_final_response_; }; } // namespace shrpx diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc index 41bb1e00..f767a668 100644 --- a/src/shrpx_http2_session.cc +++ b/src/shrpx_http2_session.cc @@ -752,7 +752,6 @@ int on_stream_close_callback (nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, void *user_data) { - int rv; auto http2session = static_cast(user_data); if(LOG_ENABLED(INFO)) { SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id @@ -769,11 +768,8 @@ int on_stream_close_callback if(dconn) { auto downstream = dconn->get_downstream(); if(downstream && downstream->get_downstream_stream_id() == stream_id) { - auto upstream = downstream->get_upstream(); if(error_code == NGHTTP2_NO_ERROR) { - downstream->set_response_state(Downstream::MSG_COMPLETE); - rv = upstream->on_downstream_body_complete(downstream); - if(rv != 0) { + if(downstream->get_response_state() != Downstream::MSG_COMPLETE) { downstream->set_response_state(Downstream::MSG_RESET); } } else { @@ -841,10 +837,6 @@ int on_header_callback(nghttp2_session *session, uint8_t flags, void *user_data) { - if(frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { - return 0; - } auto sd = static_cast (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); if(!sd || !sd->dconn) { @@ -854,6 +846,13 @@ int on_header_callback(nghttp2_session *session, if(!downstream) { return 0; } + + if(frame->hd.type != NGHTTP2_HEADERS || + (frame->headers.cat != NGHTTP2_HCAT_RESPONSE && + !downstream->get_expect_final_response())) { + return 0; + } + if(downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) { if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "Too large header block size=" @@ -905,19 +904,14 @@ int on_begin_headers_callback(nghttp2_session *session, namespace { int on_response_headers(Http2Session *http2session, + Downstream *downstream, nghttp2_session *session, const nghttp2_frame *frame) { int rv; - auto sd = static_cast - (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); - if(!sd || !sd->dconn) { - return 0; - } - auto downstream = sd->dconn->get_downstream(); - if(!downstream) { - return 0; - } + + auto upstream = downstream->get_upstream(); + downstream->normalize_response_headers(); auto& nva = downstream->get_response_headers(); @@ -935,13 +929,38 @@ int on_response_headers(Http2Session *http2session, NGHTTP2_PROTOCOL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); call_downstream_readcb(http2session, downstream); + return 0; } + downstream->set_response_http_status(strtoul(status->value.c_str(), nullptr, 10)); downstream->set_response_major(2); downstream->set_response_minor(0); + if(downstream->get_non_final_response()) { + + if(LOG_ENABLED(INFO)) { + SSLOG(INFO, http2session) << "HTTP non-final response. stream_id=" + << frame->hd.stream_id; + } + + downstream->set_expect_final_response(true); + rv = upstream->on_downstream_header_complete(downstream); + + // Now Dowstream's response headers are erased. + + if(rv != 0) { + http2session->submit_rst_stream(frame->hd.stream_id, + NGHTTP2_PROTOCOL_ERROR); + downstream->set_response_state(Downstream::MSG_RESET); + } + + return 0; + } + + downstream->set_expect_final_response(false); + if(LOG_ENABLED(INFO)) { std::stringstream ss; for(auto& nv : nva) { @@ -975,7 +994,6 @@ int on_response_headers(Http2Session *http2session, } } - auto upstream = downstream->get_upstream(); downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->check_upgrade_fulfilled(); if(downstream->get_upgraded()) { @@ -1033,19 +1051,69 @@ int on_frame_recv_callback http2session->submit_rst_stream(frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR); downstream->set_response_state(Downstream::MSG_RESET); + + } else if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + + if(downstream->get_response_state() != Downstream::MSG_RESET) { + + downstream->set_response_state(Downstream::MSG_COMPLETE); + + rv = upstream->on_downstream_body_complete(downstream); + + if(rv != 0) { + downstream->set_response_state(Downstream::MSG_RESET); + } + } } + call_downstream_readcb(http2session, downstream); break; } - case NGHTTP2_HEADERS: + case NGHTTP2_HEADERS: { + auto sd = static_cast + (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); + if(!sd || !sd->dconn) { + break; + } + auto downstream = sd->dconn->get_downstream(); + + if(!downstream) { + return 0; + } + if(frame->headers.cat == NGHTTP2_HCAT_RESPONSE) { - rv = on_response_headers(http2session, session, frame); + rv = on_response_headers(http2session, downstream, session, frame); if(rv != 0) { return rv; } } + + if(frame->headers.cat == NGHTTP2_HCAT_HEADERS && + downstream->get_expect_final_response()) { + + rv = on_response_headers(http2session, downstream, session, frame); + + if(rv != 0) { + return rv; + } + } + + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + if(downstream->get_response_state() != Downstream::MSG_RESET) { + downstream->set_response_state(Downstream::MSG_COMPLETE); + + auto upstream = downstream->get_upstream(); + + rv = upstream->on_downstream_body_complete(downstream); + + if(rv != 0) { + downstream->set_response_state(Downstream::MSG_RESET); + } + } + } break; + } case NGHTTP2_RST_STREAM: { auto sd = static_cast (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); @@ -1055,7 +1123,7 @@ int on_frame_recv_callback downstream->get_downstream_stream_id() == frame->hd.stream_id) { if(downstream->get_upgraded() && downstream->get_response_state() == Downstream::HEADER_COMPLETE) { - // For tunneled connection, we has to submit RST_STREAM to + // For tunneled connection, we have to submit RST_STREAM to // upstream *after* whole response body is sent. We just set // MSG_COMPLETE here. Upstream will take care of that. if(LOG_ENABLED(INFO)) { @@ -1122,6 +1190,14 @@ int on_data_chunk_recv_callback(nghttp2_session *session, return 0; } + // We don't want DATA after non-final response, which is illegal in + // HTTP. + if(downstream->get_non_final_response()) { + http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR); + http2session->handle_ign_data_chunk(len); + return 0; + } + downstream->add_response_bodylen(len); auto upstream = downstream->get_upstream(); diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 647f645e..3f6063e7 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -1153,9 +1153,16 @@ nghttp2_session* Http2Upstream::get_http2_session() // nghttp2_session_recv. These calls may delete downstream. int Http2Upstream::on_downstream_header_complete(Downstream *downstream) { + int rv; + if(LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response header completed"; + if(downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } } + downstream->normalize_response_headers(); if(!get_config()->http2_proxy && !get_config()->client_proxy) { downstream->rewrite_norm_location_response_header @@ -1172,6 +1179,22 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) nva.push_back(http2::make_nv_ls(":status", response_status)); http2::copy_norm_headers_to_nva(nva, downstream->get_response_headers()); + + if(downstream->get_non_final_response()) { + rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, + downstream->get_stream_id(), nullptr, + nva.data(), nva.size(), nullptr); + + downstream->clear_response_headers(); + + if(rv != 0) { + ULOG(FATAL, this) << "nghttp2_submit_headers() failed"; + return -1; + } + + return 0; + } + auto via = downstream->get_norm_response_header("via"); if(get_config()->no_via) { if(via != end_headers) { @@ -1214,7 +1237,6 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) data_prd.source.ptr = downstream; data_prd.read_callback = downstream_data_read_callback; - int rv; rv = nghttp2_submit_response(session_, downstream->get_stream_id(), nva.data(), nva.size(), &data_prd); if(rv != 0) { diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc index 9d748391..140c2336 100644 --- a/src/shrpx_http_downstream_connection.cc +++ b/src/shrpx_http_downstream_connection.cc @@ -408,9 +408,26 @@ namespace { int htp_hdrs_completecb(http_parser *htp) { auto downstream = static_cast(htp->data); + auto upstream = downstream->get_upstream(); + int rv; + downstream->set_response_http_status(htp->status_code); downstream->set_response_major(htp->http_major); downstream->set_response_minor(htp->http_minor); + + if(downstream->get_non_final_response()) { + // For non-final response code, we just call + // on_downstream_header_complete() without changing response + // state. + rv = upstream->on_downstream_header_complete(downstream); + + if(rv != 0) { + return -1; + } + + return 0; + } + downstream->set_response_connection_close(!http_should_keep_alive(htp)); downstream->set_response_state(Downstream::HEADER_COMPLETE); downstream->inspect_http1_response(); @@ -418,15 +435,13 @@ int htp_hdrs_completecb(http_parser *htp) if(downstream->get_upgraded()) { downstream->set_response_connection_close(true); } - if(downstream->get_upstream()->on_downstream_header_complete(downstream) - != 0) { + if(upstream->on_downstream_header_complete(downstream) != 0) { return -1; } if(downstream->get_upgraded()) { // Upgrade complete, read until EOF in both ends - if(downstream->get_upstream()->resume_read(SHRPX_MSG_BLOCK, - downstream) != 0) { + if(upstream->resume_read(SHRPX_MSG_BLOCK, downstream) != 0) { return -1; } downstream->set_request_state(Downstream::HEADER_COMPLETE); @@ -443,6 +458,9 @@ int htp_hdrs_completecb(http_parser *htp) // 304 status code with nonzero Content-Length, but without response // body. See // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20#section-3.3 + + // TODO It seems that the cases other than HEAD are handled by + // http-parser. Need test. return downstream->get_request_method() == "HEAD" || (100 <= status && status <= 199) || status == 204 || status == 304 ? 1 : 0; @@ -505,6 +523,13 @@ namespace { int htp_msg_completecb(http_parser *htp) { auto downstream = static_cast(htp->data); + + if(downstream->get_non_final_response()) { + downstream->reset_response(); + + return 0; + } + downstream->set_response_state(Downstream::MSG_COMPLETE); // Block reading another response message from (broken?) // server. This callback is not called if the connection is diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index 0dd47b32..ad2861d3 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -190,16 +190,6 @@ int htp_hdrs_completecb(http_parser *htp) auto dconn = upstream->get_client_handler()->get_downstream_connection(); - if(downstream->get_expect_100_continue()) { - static const char reply_100[] = "HTTP/1.1 100 Continue\r\n\r\n"; - if(bufferevent_write(upstream->get_client_handler()->get_bev(), - reply_100, sizeof(reply_100)-1) != 0) { - ULOG(FATAL, upstream) << "bufferevent_write() faild"; - delete dconn; - return -1; - } - } - rv = dconn->attach_downstream(downstream); if(rv != 0) { @@ -740,7 +730,11 @@ Downstream* HttpsUpstream::pop_downstream() int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) { if(LOG_ENABLED(INFO)) { - DLOG(INFO, downstream) << "HTTP response header completed"; + if(downstream->get_non_final_response()) { + DLOG(INFO, downstream) << "HTTP non-final response header"; + } else { + DLOG(INFO, downstream) << "HTTP response header completed"; + } } std::string hdrs = "HTTP/"; @@ -759,6 +753,20 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) http2::build_http1_headers_from_norm_headers (hdrs, downstream->get_response_headers()); + if(downstream->get_non_final_response()) { + hdrs += "\r\n"; + + auto output = bufferevent_get_output(handler_->get_bev()); + if(evbuffer_add(output, hdrs.c_str(), hdrs.size()) != 0) { + ULOG(FATAL, this) << "evbuffer_add() failed"; + return -1; + } + + downstream->clear_response_headers(); + + return 0; + } + // We check downstream->get_response_connection_close() in case when // the Content-Length is not available. if(!downstream->get_request_connection_close() && diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index 4032004f..84302a05 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -886,6 +886,15 @@ spdylay_session* SpdyUpstream::get_http2_session() // spdylay_session_recv. These calls may delete downstream. int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) { + if(downstream->get_non_final_response()) { + // SPDY does not support non-final response. We could send it + // with HEADERS and final response in SYN_REPLY, but it is not + // official way. + downstream->clear_response_headers(); + + return 0; + } + if(LOG_ENABLED(INFO)) { DLOG(INFO, downstream) << "HTTP response header completed"; }