Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion lib/http/2/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,12 @@ def receive(data)
# PUSH_PROMISE and CONTINUATION frames MUST be minimally
# processed to ensure a consistent compression state
decode_headers(frame)
return if @state == :closed
# A GOAWAY moves the connection to :closed, but streams the peer
# promised to finish (id <= last_stream_id, already in @streams) can
# still complete (RFC 9113 Section 6.8). Only discard HEADERS that
# would open a NEW stream; a known stream's response must be
# delivered (its DATA frames already are - see the branch below).
return if @state == :closed && !@streams.key?(stream_id)

stream = @streams[stream_id]
if stream.nil?
Expand Down
30 changes: 30 additions & 0 deletions spec/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,36 @@
client << f.generate(push_promise_frame)
expect(client.active_stream_count).to eq 1
end

it "should deliver response headers for a stream the GOAWAY promised to finish" do
stream = client.new_stream # id 1
stream.send headers_frame

received = nil
stream.on(:headers) { |h| received = h }

# GOAWAY (last_stream: 2) covers this stream (id 1), so the server
# promised to finish it: its response, arriving after the GOAWAY, must
# still be delivered rather than dropped once the connection is :closed.
client << f.generate(goaway_frame)
client << f.generate(headers_frame.merge(payload: Compressor.new.encode(RESPONSE_HEADERS)))

expect(received).to eq(RESPONSE_HEADERS)
end

it "should drop response headers for a stream the GOAWAY refused" do
client << f.generate(goaway_frame) # last_stream: 2

opened = false
client.on(:stream) { opened = true }

# A stream beyond last_stream_id (id 5) was refused and never processed;
# its (unexpected) headers must not open a stream on the closed connection.
client << f.generate(headers_frame.merge(stream: 5, payload: Compressor.new.encode(RESPONSE_HEADERS)))

expect(opened).to be(false)
expect(client.active_stream_count).to eq 0
end
end

context "framing" do
Expand Down