summaryrefslogtreecommitdiff
path: root/src/node_http2.cc
diff options
context:
space:
mode:
authorAnatoli Papirovski <apapirovski@mac.com>2018-04-16 22:03:10 +0200
committerAnatoli Papirovski <apapirovski@mac.com>2018-04-28 18:17:28 +0200
commitb55a11d1b17b3e4b9650ef8e7b4e57ef83dc441d (patch)
tree0fd4c64468fb877011a89737967466ed752d455d /src/node_http2.cc
parentc51b7b296e0fd59a00b1c1337d744f4fc8d2fb35 (diff)
downloadandroid-node-v8-b55a11d1b17b3e4b9650ef8e7b4e57ef83dc441d.tar.gz
android-node-v8-b55a11d1b17b3e4b9650ef8e7b4e57ef83dc441d.tar.bz2
android-node-v8-b55a11d1b17b3e4b9650ef8e7b4e57ef83dc441d.zip
http2: fix responses to long payload reqs
When a request with a long payload is received, http2 does not allow a response that does not process all the incoming payload. Add a conditional Http2Stream.close call that runs only if the user hasn't attempted to read the stream. PR-URL: https://github.com/nodejs/node/pull/20084 Fixes: https://github.com/nodejs/node/issues/20060 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'src/node_http2.cc')
-rw-r--r--src/node_http2.cc69
1 files changed, 52 insertions, 17 deletions
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 1c5c68f094..05d9243ee3 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -1364,16 +1364,35 @@ void Http2Session::MaybeScheduleWrite() {
// storage for data and metadata that was associated with these writes.
void Http2Session::ClearOutgoing(int status) {
CHECK_NE(flags_ & SESSION_STATE_SENDING, 0);
- flags_ &= ~SESSION_STATE_SENDING;
- for (const nghttp2_stream_write& wr : outgoing_buffers_) {
- WriteWrap* wrap = wr.req_wrap;
- if (wrap != nullptr)
- wrap->Done(status);
+ if (outgoing_buffers_.size() > 0) {
+ outgoing_storage_.clear();
+
+ for (const nghttp2_stream_write& wr : outgoing_buffers_) {
+ WriteWrap* wrap = wr.req_wrap;
+ if (wrap != nullptr)
+ wrap->Done(status);
+ }
+
+ outgoing_buffers_.clear();
}
- outgoing_buffers_.clear();
- outgoing_storage_.clear();
+ flags_ &= ~SESSION_STATE_SENDING;
+
+ // Now that we've finished sending queued data, if there are any pending
+ // RstStreams we should try sending again and then flush them one by one.
+ if (pending_rst_streams_.size() > 0) {
+ std::vector<int32_t> current_pending_rst_streams;
+ pending_rst_streams_.swap(current_pending_rst_streams);
+
+ SendPendingData();
+
+ for (int32_t stream_id : current_pending_rst_streams) {
+ Http2Stream* stream = FindStream(stream_id);
+ if (stream != nullptr)
+ stream->FlushRstStream();
+ }
+ }
}
// Queue a given block of data for sending. This always creates a copy,
@@ -1397,18 +1416,19 @@ void Http2Session::CopyDataIntoOutgoing(const uint8_t* src, size_t src_length) {
// chunk out to the i/o socket to be sent. This is a particularly hot method
// that will generally be called at least twice be event loop iteration.
// This is a potential performance optimization target later.
-void Http2Session::SendPendingData() {
+// Returns non-zero value if a write is already in progress.
+uint8_t Http2Session::SendPendingData() {
DEBUG_HTTP2SESSION(this, "sending pending data");
// Do not attempt to send data on the socket if the destroying flag has
// been set. That means everything is shutting down and the socket
// will not be usable.
if (IsDestroyed())
- return;
+ return 0;
flags_ &= ~SESSION_STATE_WRITE_SCHEDULED;
// SendPendingData should not be called recursively.
if (flags_ & SESSION_STATE_SENDING)
- return;
+ return 1;
// This is cleared by ClearOutgoing().
flags_ |= SESSION_STATE_SENDING;
@@ -1432,15 +1452,15 @@ void Http2Session::SendPendingData() {
// does take care of things like closing the individual streams after
// a socket has been torn down, so we still need to call it.
ClearOutgoing(UV_ECANCELED);
- return;
+ return 0;
}
// Part Two: Pass Data to the underlying stream
size_t count = outgoing_buffers_.size();
if (count == 0) {
- flags_ &= ~SESSION_STATE_SENDING;
- return;
+ ClearOutgoing(0);
+ return 0;
}
MaybeStackBuffer<uv_buf_t, 32> bufs;
bufs.AllocateSufficientStorage(count);
@@ -1471,6 +1491,8 @@ void Http2Session::SendPendingData() {
DEBUG_HTTP2SESSION2(this, "wants data in return? %d",
nghttp2_session_want_read(session_));
+
+ return 0;
}
@@ -1830,12 +1852,25 @@ int Http2Stream::SubmitPriority(nghttp2_priority_spec* prispec,
// peer.
void Http2Stream::SubmitRstStream(const uint32_t code) {
CHECK(!this->IsDestroyed());
+ code_ = code;
+ // If possible, force a purge of any currently pending data here to make sure
+ // it is sent before closing the stream. If it returns non-zero then we need
+ // to wait until the current write finishes and try again to avoid nghttp2
+ // behaviour where it prioritizes RstStream over everything else.
+ if (session_->SendPendingData() != 0) {
+ session_->AddPendingRstStream(id_);
+ return;
+ }
+
+ FlushRstStream();
+}
+
+void Http2Stream::FlushRstStream() {
+ if (IsDestroyed())
+ return;
Http2Scope h2scope(this);
- // Force a purge of any currently pending data here to make sure
- // it is sent before closing the stream.
- session_->SendPendingData();
CHECK_EQ(nghttp2_submit_rst_stream(**session_, NGHTTP2_FLAG_NONE,
- id_, code), 0);
+ id_, code_), 0);
}