async-http 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/context/choosing-a-client.md +164 -0
  4. data/context/concurrent-requests.md +134 -0
  5. data/context/getting-started.md +106 -69
  6. data/context/index.yaml +14 -3
  7. data/context/testing.md +173 -45
  8. data/lib/async/http/body/hijack.rb +1 -1
  9. data/lib/async/http/body/pipe.rb +2 -2
  10. data/lib/async/http/body.rb +1 -1
  11. data/lib/async/http/internet.rb +1 -1
  12. data/lib/async/http/middleware/location_redirector.rb +1 -1
  13. data/lib/async/http/mock/endpoint.rb +1 -1
  14. data/lib/async/http/protocol/configurable.rb +1 -1
  15. data/lib/async/http/protocol/http.rb +1 -1
  16. data/lib/async/http/protocol/http1/finishable.rb +2 -1
  17. data/lib/async/http/protocol/http1/request.rb +1 -1
  18. data/lib/async/http/protocol/http1/response.rb +1 -1
  19. data/lib/async/http/protocol/http1.rb +1 -1
  20. data/lib/async/http/protocol/http10.rb +1 -1
  21. data/lib/async/http/protocol/http11.rb +1 -1
  22. data/lib/async/http/protocol/http2/client.rb +7 -2
  23. data/lib/async/http/protocol/http2/connection.rb +20 -6
  24. data/lib/async/http/protocol/http2/input.rb +14 -3
  25. data/lib/async/http/protocol/http2/output.rb +23 -8
  26. data/lib/async/http/protocol/http2/response.rb +15 -2
  27. data/lib/async/http/protocol/http2/server.rb +1 -1
  28. data/lib/async/http/protocol/http2/stream.rb +61 -4
  29. data/lib/async/http/protocol/http2.rb +1 -1
  30. data/lib/async/http/protocol/request.rb +1 -1
  31. data/lib/async/http/protocol/response.rb +1 -1
  32. data/lib/async/http/proxy.rb +1 -1
  33. data/lib/async/http/statistics.rb +1 -1
  34. data/lib/async/http/version.rb +1 -1
  35. data/lib/async/http.rb +1 -1
  36. data/lib/traces/provider/async/http/client.rb +1 -1
  37. data/lib/traces/provider/async/http/protocol/http1/client.rb +1 -1
  38. data/lib/traces/provider/async/http/protocol/http2/client.rb +1 -1
  39. data/lib/traces/provider/async/http/server.rb +1 -1
  40. data/license.md +2 -1
  41. data/readme.md +18 -13
  42. data/releases.md +5 -0
  43. data.tar.gz.sig +0 -0
  44. metadata +11 -6
  45. metadata.gz.sig +0 -0
@@ -28,6 +28,9 @@ module Async
28
28
  @length = nil
29
29
  @input = nil
30
30
 
31
+ # The application can close its input before the peer finishes sending. HTTP/2 cannot close only the receiving side of a stream, so incoming data is discarded until local output also finishes. At that point, a no-error reset terminates the remaining wire stream.
32
+ @input_closed = false
33
+
31
34
  # Output buffer, writing request body or response body (window_updated):
32
35
  @output = nil
33
36
  end
@@ -114,14 +117,17 @@ module Async
114
117
  def process_data(frame)
115
118
  data = frame.unpack
116
119
 
117
- if @input
120
+ if input = @input
118
121
  unless data.empty?
119
- @input.write(data)
122
+ input.write(data)
120
123
  end
121
124
 
122
125
  if frame.end_stream?
123
- @input.close_write
126
+ input.close_write
124
127
  end
128
+ else
129
+ # The application has closed the input, so discard incoming data while maintaining flow control for the stream.
130
+ request_window_update
125
131
  end
126
132
 
127
133
  return data
@@ -131,6 +137,22 @@ module Async
131
137
  send_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)
132
138
  end
133
139
 
140
+ # Close the application-facing receiving side of the stream. While local output remains active, incoming data is discarded with flow-control updates. Once local output is also closed, the remaining wire stream is terminated without an error.
141
+ # @parameter input [Input] The input body being closed.
142
+ # @parameter error [Exception | Nil] The error which closed the input.
143
+ def finish_input(input, error = nil)
144
+ if @input.equal?(input)
145
+ @input = nil
146
+ @input_closed = true
147
+
148
+ if error
149
+ send_reset_stream(::Protocol::HTTP2::Error::INTERNAL_ERROR)
150
+ else
151
+ close_if_finished
152
+ end
153
+ end
154
+ end
155
+
134
156
  # Set the body and begin sending it.
135
157
  def send_body(body, trailer = nil)
136
158
  @output = Output.new(self, body, trailer)
@@ -169,11 +191,32 @@ module Async
169
191
  return true
170
192
  end
171
193
 
194
+ # Send headers and apply any pending application-side closure.
195
+ def send_headers(...)
196
+ result = super
197
+ close_if_finished
198
+ return result
199
+ end
200
+
201
+ # Send data and apply any pending application-side closure.
202
+ def send_data(...)
203
+ result = super
204
+ close_if_finished
205
+ return result
206
+ end
207
+
172
208
  # When the stream transitions to the closed state, this method is called. There are roughly two ways this can happen:
173
209
  # - A frame is received which causes this stream to enter the closed state. This method will be invoked from the background reader task.
174
210
  # - A frame is sent which causes this stream to enter the closed state. This method will be invoked from that task.
175
211
  # While the input stream is relatively straight forward, the output stream can trigger the second case above
176
212
  def closed(error)
213
+ orderly_reset = error.is_a?(::Protocol::HTTP2::StreamError) &&
214
+ error.code == ::Protocol::HTTP2::Error::NO_ERROR
215
+
216
+ if orderly_reset
217
+ error = nil
218
+ end
219
+
177
220
  super
178
221
 
179
222
  if input = @input
@@ -183,7 +226,12 @@ module Async
183
226
 
184
227
  if output = @output
185
228
  @output = nil
186
- output.stop(error)
229
+
230
+ if orderly_reset
231
+ output.close_stream
232
+ else
233
+ output.stop(error)
234
+ end
187
235
  end
188
236
 
189
237
  if pool = @pool and @connection
@@ -192,6 +240,15 @@ module Async
192
240
 
193
241
  return self
194
242
  end
243
+
244
+ private
245
+
246
+ # If both application-facing directions are closed but the peer has not finished, terminate the remaining wire stream without an error.
247
+ def close_if_finished
248
+ if @input_closed && @state == :half_closed_local
249
+ send_reset_stream(::Protocol::HTTP2::Error::NO_ERROR)
250
+ end
251
+ end
195
252
  end
196
253
  end
197
254
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2025, by Samuel Williams.
4
+ # Copyright, 2018-2026, by Samuel Williams.
5
5
  # Copyright, 2024, by Thomas Morgan.
6
6
 
7
7
  require_relative "configurable"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
5
 
6
6
  require "protocol/http/request"
7
7
  require "protocol/http/headers"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
5
 
6
6
  require "protocol/http/response"
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2025, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "client"
7
7
  require_relative "endpoint"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2024, by Samuel Williams.
4
+ # Copyright, 2018-2026, by Samuel Williams.
5
5
 
6
6
  require "protocol/http/body/wrapper"
7
7
 
@@ -7,6 +7,6 @@
7
7
  module Async
8
8
  # @namespace
9
9
  module HTTP
10
- VERSION = "0.101.0"
10
+ VERSION = "0.102.0"
11
11
  end
12
12
  end
data/lib/async/http.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2024, by Samuel Williams.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "http/version"
7
7
  require_relative "http/client"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../../async/http/client"
7
7
  require "traces/provider"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../../../../async/http/protocol/http1/client"
7
7
  require "traces/provider"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../../../../async/http/protocol/http2/client"
7
7
  require "traces/provider"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require_relative "../../../../async/http/server"
7
7
  require "traces/provider"
data/license.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Copyright, 2017-2026, by Samuel Williams.
4
4
  Copyright, 2018, by Viacheslav Koval.
5
5
  Copyright, 2018, by Janko Marohnić.
6
- Copyright, 2019, by Denis Talakevich.
6
+ Copyright, 2019-2026, by Denis Talakevich.
7
7
  Copyright, 2019-2020, by Brian Morearty.
8
8
  Copyright, 2019, by Cyril Roelandt.
9
9
  Copyright, 2020, by Stefan Wrobel.
@@ -25,6 +25,7 @@ Copyright, 2024, by Hal Brodigan.
25
25
  Copyright, 2025, by Jean Boussier.
26
26
  Copyright, 2025, by Yuuji Yaginuma.
27
27
  Copyright, 2025, by William T. Nelson.
28
+ Copyright, 2026, by Jun Jiang.
28
29
 
29
30
  Permission is hereby granted, free of charge, to any person obtaining a copy
30
31
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -8,14 +8,23 @@ An asynchronous client and server implementation of HTTP/1.0, HTTP/1.1 and HTTP/
8
8
 
9
9
  Please see the [project documentation](https://socketry.github.io/async-http/) for more details.
10
10
 
11
- - [Getting Started](https://socketry.github.io/async-http/guides/getting-started/index) - This guide explains how to get started with `Async::HTTP`.
11
+ - [Getting Started](https://socketry.github.io/async-http/guides/getting-started/index) - This guide explains how to make HTTP requests and serve HTTP responses with `Async::HTTP`.
12
12
 
13
- - [Testing](https://socketry.github.io/async-http/guides/testing/index) - This guide explains how to use `Async::HTTP` clients and servers in your tests.
13
+ - [Choosing a Client](https://socketry.github.io/async-http/guides/choosing-a-client/index) - This guide explains how to choose between ruby:`Async::HTTP::Internet`, ruby:`Async::HTTP::Client`, and higher-level interfaces for libraries.
14
+
15
+ - [Concurrent Requests and Connection Pooling](https://socketry.github.io/async-http/guides/concurrent-requests/index) - This guide explains how to run HTTP requests concurrently while keeping request fan-out, connection usage, and resource life cycles bounded.
16
+
17
+ - [Testing](https://socketry.github.io/async-http/guides/testing/index) - This guide explains how to test `Async::HTTP` clients and servers without depending on external HTTP services.
14
18
 
15
19
  ## Releases
16
20
 
17
21
  Please see the [project releases](https://socketry.github.io/async-http/releases/index) for all releases.
18
22
 
23
+ ### v0.102.0
24
+
25
+ - Requests assigned to an HTTP/2 connection which has already closed are refused before being written, allowing them to be retried safely.
26
+ - HTTP/2 connections which received a graceful `GOAWAY` are removed from availability immediately, but remain in the pool until the server has finished answering the streams it accepted. The connection is closed after its final user releases it, so those requests no longer fail with `EOFError: Connection closed with N active stream(s)!`.
27
+
19
28
  ### v0.101.0
20
29
 
21
30
  - Handle remote disconnects in `Async::HTTP::Protocol::HTTP1::Server#each` without reporting them as server failures.
@@ -55,10 +64,6 @@ Please see the [project releases](https://socketry.github.io/async-http/releases
55
64
  - HTTP/1: Delegate request write failure handling to `protocol-http1`.
56
65
  - HTTP/2: Handle GOAWAY and REFUSED\_STREAM via `protocol-http2`, enabling automatic retry of unprocessed requests.
57
66
 
58
- ### v0.94.3
59
-
60
- - Fix response body leak in HTTP/2 server when stream is reset before `send_response` completes (e.g. client-side gRPC cancellation). The response body's `close` was never called, leaking any resources tied to body lifecycle (such as `rack.response_finished` callbacks and utilization metrics).
61
-
62
67
  ## See Also
63
68
 
64
69
  - [benchmark-http](https://github.com/socketry/benchmark-http) — A benchmarking tool to report on web server concurrency.
@@ -71,26 +76,26 @@ Please see the [project releases](https://socketry.github.io/async-http/releases
71
76
 
72
77
  We welcome contributions to this project.
73
78
 
74
- 1. Fork it.
79
+ 1. Fork the repository.
75
80
  2. Create your feature branch (`git checkout -b my-new-feature`).
76
- 3. Commit your changes (`git commit -am 'Add some feature'`).
81
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
77
82
  4. Push to the branch (`git push origin my-new-feature`).
78
- 5. Create new Pull Request.
83
+ 5. Create a new pull request.
79
84
 
80
85
  ### Running Tests
81
86
 
82
87
  To run the test suite:
83
88
 
84
- ``` shell
85
- bundle exec sus
89
+ ``` bash
90
+ $ bundle exec sus
86
91
  ```
87
92
 
88
93
  ### Making Releases
89
94
 
90
95
  To make a new release:
91
96
 
92
- ``` shell
93
- bundle exec bake gem:release:patch # or minor or major
97
+ ``` bash
98
+ $ bundle exec bake gem:release:patch # or minor or major
94
99
  ```
95
100
 
96
101
  ### Developer Certificate of Origin
data/releases.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Releases
2
2
 
3
+ ## v0.102.0
4
+
5
+ - Requests assigned to an HTTP/2 connection which has already closed are refused before being written, allowing them to be retried safely.
6
+ - HTTP/2 connections which received a graceful `GOAWAY` are removed from availability immediately, but remain in the pool until the server has finished answering the streams it accepted. The connection is closed after its final user releases it, so those requests no longer fail with `EOFError: Connection closed with N active stream(s)!`.
7
+
3
8
  ## v0.101.0
4
9
 
5
10
  - Handle remote disconnects in `Async::HTTP::Protocol::HTTP1::Server#each` without reporting them as server failures.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.101.0
4
+ version: 0.102.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -10,15 +10,16 @@ authors:
10
10
  - Janko Marohnić
11
11
  - Thomas Morgan
12
12
  - Adam Daniels
13
+ - Denis Talakevich
13
14
  - Igor Sidorov
14
15
  - William T. Nelson
15
16
  - Anton Zhuravsky
16
17
  - Cyril Roelandt
17
- - Denis Talakevich
18
18
  - Hal Brodigan
19
19
  - Ian Ker-Seymer
20
20
  - Jean Boussier
21
21
  - Josh Huber
22
+ - Jun Jiang
22
23
  - Marco Concetto Rudilosso
23
24
  - Olle Jonsson
24
25
  - Orgad Shaneh
@@ -82,14 +83,14 @@ dependencies:
82
83
  requirements:
83
84
  - - "~>"
84
85
  - !ruby/object:Gem::Version
85
- version: '0.11'
86
+ version: '0.12'
86
87
  type: :runtime
87
88
  prerelease: false
88
89
  version_requirements: !ruby/object:Gem::Requirement
89
90
  requirements:
90
91
  - - "~>"
91
92
  - !ruby/object:Gem::Version
92
- version: '0.11'
93
+ version: '0.12'
93
94
  - !ruby/object:Gem::Dependency
94
95
  name: io-endpoint
95
96
  requirement: !ruby/object:Gem::Requirement
@@ -152,14 +153,14 @@ dependencies:
152
153
  requirements:
153
154
  - - "~>"
154
155
  - !ruby/object:Gem::Version
155
- version: '0.26'
156
+ version: '0.27'
156
157
  type: :runtime
157
158
  prerelease: false
158
159
  version_requirements: !ruby/object:Gem::Requirement
159
160
  requirements:
160
161
  - - "~>"
161
162
  - !ruby/object:Gem::Version
162
- version: '0.26'
163
+ version: '0.27'
163
164
  - !ruby/object:Gem::Dependency
164
165
  name: protocol-url
165
166
  requirement: !ruby/object:Gem::Requirement
@@ -180,6 +181,8 @@ extra_rdoc_files: []
180
181
  files:
181
182
  - bake/async/http.rb
182
183
  - bake/async/http/h2spec.rb
184
+ - context/choosing-a-client.md
185
+ - context/concurrent-requests.md
183
186
  - context/getting-started.md
184
187
  - context/index.yaml
185
188
  - context/testing.md
@@ -240,6 +243,8 @@ homepage: https://github.com/socketry/async-http
240
243
  licenses:
241
244
  - MIT
242
245
  metadata:
246
+ bug_tracker_uri: https://github.com/socketry/async-http/issues
247
+ changelog_uri: https://github.com/socketry/async-http/blob/main/releases.md
243
248
  documentation_uri: https://socketry.github.io/async-http/
244
249
  source_code_uri: https://github.com/socketry/async-http.git
245
250
  rdoc_options: []
metadata.gz.sig CHANGED
Binary file