nghttpx, nghttpd: Support non-final response

This commit is contained in:
Tatsuhiro Tsujikawa 2014-07-23 23:32:57 +09:00
parent 78df530b90
commit 4f815521ae
10 changed files with 227 additions and 55 deletions

View File

@ -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<nghttp2_nv>{
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);
}

View File

@ -126,6 +126,8 @@ public:
const std::vector<std::pair<std::string, std::string>>& 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);

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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<Http2Session*>(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<StreamData*>
(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<StreamData*>
(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<StreamData*>
(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<StreamData*>
(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();

View File

@ -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)) {
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) {

View File

@ -408,9 +408,26 @@ namespace {
int htp_hdrs_completecb(http_parser *htp)
{
auto downstream = static_cast<Downstream*>(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<Downstream*>(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

View File

@ -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,8 +730,12 @@ Downstream* HttpsUpstream::pop_downstream()
int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
{
if(LOG_ENABLED(INFO)) {
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/";
hdrs += util::utos(downstream->get_request_major());
@ -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() &&

View File

@ -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";
}