Skip to content

Commit 382befe

Browse files
Use RefusedError directly in stream close! transition.
1 parent 5fbef08 commit 382befe

5 files changed

Lines changed: 37 additions & 11 deletions

File tree

lib/protocol/http2/connection.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def receive_goaway(frame)
238238
self.close!
239239

240240
# Streams above the last stream ID were not processed by the remote peer and are safe to retry (RFC 9113 §6.8).
241-
error = ::Protocol::HTTP::RequestRefusedError.new("GOAWAY: request not processed.")
241+
error = ::Protocol::HTTP::RefusedError.new("GOAWAY: request not processed.")
242242

243243
@streams.each_value do |stream|
244244
if stream.id > @remote_stream_id

lib/protocol/http2/stream.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,11 @@ def close!(error_code = nil)
250250
@connection.delete(@id)
251251

252252
if error_code
253-
error = StreamError.new("Stream closed!", error_code)
253+
if error_code == REFUSED_STREAM
254+
error = ::Protocol::HTTP::RefusedError.new("Stream refused.")
255+
else
256+
error = StreamError.new("Stream closed!", error_code)
257+
end
254258
end
255259

256260
self.closed(error)

protocol-http2.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ Gem::Specification.new do |spec|
2525
spec.required_ruby_version = ">= 3.3"
2626

2727
spec.add_dependency "protocol-hpack", "~> 1.4"
28-
spec.add_dependency "protocol-http", "~> 0.61"
28+
spec.add_dependency "protocol-http", "~> 0.62"
2929
end

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- On RST_STREAM with REFUSED_STREAM, close the stream with `Protocol::HTTP::RefusedError` instead of `StreamError`.
6+
37
## v0.25.0
48

59
- On GOAWAY, proactively close unprocessed streams (ID above `last_stream_id`) with `Protocol::HTTP::RequestRefusedError`, enabling safe retry of non-idempotent requests.

test/protocol/http2/connection.rb

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,25 @@ def closed(error)
402402
end
403403
end
404404

405-
it "closes unprocessed streams with RequestRefusedError on graceful GOAWAY" do
405+
it "closes stream with RefusedError on REFUSED_STREAM" do
406+
stream = client.create_stream do |connection, id|
407+
stream_class.create(connection, id)
408+
end
409+
stream.send_headers(request_headers, Protocol::HTTP2::END_STREAM)
410+
411+
# Establish request stream on server:
412+
server.read_frame
413+
414+
# Server refuses the stream:
415+
server.streams[stream.id].send_reset_stream(Protocol::HTTP2::REFUSED_STREAM)
416+
417+
client.read_frame
418+
419+
expect(stream.state).to be == :closed
420+
expect(stream.error).to be_a(Protocol::HTTP::RefusedError)
421+
end
422+
423+
it "closes unprocessed streams with RefusedError on graceful GOAWAY" do
406424
stream.send_headers(request_headers, Protocol::HTTP2::END_STREAM)
407425

408426
# Establish request stream on server:
@@ -418,15 +436,15 @@ def closed(error)
418436

419437
client.read_frame
420438

421-
# The unprocessed stream (id=3) should be closed with RequestRefusedError:
439+
# The unprocessed stream (id=3) should be closed with RefusedError:
422440
expect(another_stream.state).to be == :closed
423-
expect(another_stream.error).to be_a(Protocol::HTTP::RequestRefusedError)
441+
expect(another_stream.error).to be_a(Protocol::HTTP::RefusedError)
424442

425443
# The processed stream (id=1) should still be open:
426444
expect(stream.state).not.to be == :closed
427445
end
428446

429-
it "closes all streams with RequestRefusedError on GOAWAY with last_stream_id=0" do
447+
it "closes all streams with RefusedError on GOAWAY with last_stream_id=0" do
430448
another_stream = client.create_stream do |connection, id|
431449
stream_class.create(connection, id)
432450
end
@@ -438,10 +456,10 @@ def closed(error)
438456
client.read_frame
439457

440458
expect(another_stream.state).to be == :closed
441-
expect(another_stream.error).to be_a(Protocol::HTTP::RequestRefusedError)
459+
expect(another_stream.error).to be_a(Protocol::HTTP::RefusedError)
442460
end
443461

444-
it "closes unprocessed streams with RequestRefusedError on non-graceful GOAWAY" do
462+
it "closes unprocessed streams with RefusedError on non-graceful GOAWAY" do
445463
stream.send_headers(request_headers, Protocol::HTTP2::END_STREAM)
446464

447465
# Establish request stream on server:
@@ -459,9 +477,9 @@ def closed(error)
459477
client.read_frame
460478
end.to raise_exception(Protocol::HTTP2::GoawayError)
461479

462-
# The unprocessed stream should still have been closed with RequestRefusedError:
480+
# The unprocessed stream should still have been closed with RefusedError:
463481
expect(another_stream.state).to be == :closed
464-
expect(another_stream.error).to be_a(Protocol::HTTP::RequestRefusedError)
482+
expect(another_stream.error).to be_a(Protocol::HTTP::RefusedError)
465483
end
466484

467485
it "client can handle non-graceful shutdown" do

0 commit comments

Comments
 (0)