diff --git a/lib/protocol/http2/stream.rb b/lib/protocol/http2/stream.rb index 3ddbcdb..6224a93 100644 --- a/lib/protocol/http2/stream.rb +++ b/lib/protocol/http2/stream.rb @@ -239,22 +239,21 @@ def open! return self end - # The stream has been closed. If closed due to a stream reset, the error will be set. + # The stream has been closed. If closed due to a stream reset with a non-zero error code, the error will be set. def closed(error = nil) end # Transition the stream into the closed state. - # @parameter error_code [Integer] the error code if the stream was closed due to a stream reset. + # @parameter error_code [Integer | Nil] The error code if the stream was closed due to a stream reset. def close!(error_code = nil) @state = :closed @connection.delete(@id) - if error_code - if error_code == REFUSED_STREAM - error = ::Protocol::HTTP::RefusedError.new("Stream refused.") - else - error = StreamError.for(error_code) - end + # `NO_ERROR` represents an orderly stream closure, so it is passed to `closed` as `nil` rather than converted to an exception. + if error_code == REFUSED_STREAM + error = ::Protocol::HTTP::RefusedError.new("Stream refused.") + elsif error_code && error_code != NO_ERROR + error = StreamError.for(error_code) end self.closed(error) diff --git a/releases.md b/releases.md index 4ce332c..6ef06e6 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Treat `RST_STREAM(NO_ERROR)` as an orderly stream closure rather than constructing a `StreamError`. + ## v0.27.0 - On a graceful `GOAWAY` (error code `0`), keep the connection open until the streams the remote peer accepted have completed, instead of closing it immediately and failing those requests with `EOFError`. diff --git a/test/protocol/http2/connection.rb b/test/protocol/http2/connection.rb index 23b0465..cc9b5d1 100644 --- a/test/protocol/http2/connection.rb +++ b/test/protocol/http2/connection.rb @@ -448,6 +448,24 @@ def closed(error) expect(stream.error).to be_a(Protocol::HTTP::RefusedError) end + it "closes stream without an error on NO_ERROR" do + stream = client.create_stream do |connection, id| + stream_class.create(connection, id) + end + stream.send_headers(request_headers, Protocol::HTTP2::END_STREAM) + + # Establish request stream on server: + server.read_frame + + # Server closes the stream without an error: + server.streams[stream.id].send_reset_stream(Protocol::HTTP2::NO_ERROR) + + client.read_frame + + expect(stream.state).to be == :closed + expect(stream.error).to be_nil + end + it "closes unprocessed streams with RefusedError on graceful GOAWAY" do stream.send_headers(request_headers, Protocol::HTTP2::END_STREAM)