async-http 0.76.0 → 0.77.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 (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/async/http/h2spec.rb +6 -6
  4. data/bake/async/http.rb +3 -3
  5. data/lib/async/http/body/finishable.rb +56 -0
  6. data/lib/async/http/body/hijack.rb +4 -4
  7. data/lib/async/http/body/pipe.rb +1 -1
  8. data/lib/async/http/body/writable.rb +3 -3
  9. data/lib/async/http/body.rb +3 -3
  10. data/lib/async/http/client.rb +16 -18
  11. data/lib/async/http/endpoint.rb +10 -10
  12. data/lib/async/http/internet/instance.rb +1 -1
  13. data/lib/async/http/internet.rb +5 -5
  14. data/lib/async/http/middleware/location_redirector.rb +8 -8
  15. data/lib/async/http/mock/endpoint.rb +2 -2
  16. data/lib/async/http/mock.rb +1 -1
  17. data/lib/async/http/protocol/http.rb +2 -2
  18. data/lib/async/http/protocol/http1/client.rb +19 -6
  19. data/lib/async/http/protocol/http1/connection.rb +6 -7
  20. data/lib/async/http/protocol/http1/request.rb +3 -3
  21. data/lib/async/http/protocol/http1/response.rb +10 -2
  22. data/lib/async/http/protocol/http1/server.rb +20 -12
  23. data/lib/async/http/protocol/http1.rb +3 -3
  24. data/lib/async/http/protocol/http10.rb +1 -1
  25. data/lib/async/http/protocol/http11.rb +1 -1
  26. data/lib/async/http/protocol/http2/client.rb +4 -4
  27. data/lib/async/http/protocol/http2/connection.rb +12 -12
  28. data/lib/async/http/protocol/http2/input.rb +2 -2
  29. data/lib/async/http/protocol/http2/output.rb +2 -2
  30. data/lib/async/http/protocol/http2/request.rb +4 -4
  31. data/lib/async/http/protocol/http2/response.rb +14 -4
  32. data/lib/async/http/protocol/http2/server.rb +3 -3
  33. data/lib/async/http/protocol/http2/stream.rb +12 -4
  34. data/lib/async/http/protocol/http2.rb +3 -3
  35. data/lib/async/http/protocol/https.rb +3 -3
  36. data/lib/async/http/protocol/request.rb +3 -3
  37. data/lib/async/http/protocol/response.rb +3 -3
  38. data/lib/async/http/protocol.rb +3 -3
  39. data/lib/async/http/proxy.rb +4 -3
  40. data/lib/async/http/reference.rb +2 -2
  41. data/lib/async/http/relative_location.rb +1 -1
  42. data/lib/async/http/server.rb +13 -13
  43. data/lib/async/http/statistics.rb +4 -4
  44. data/lib/async/http/version.rb +1 -1
  45. data/lib/async/http.rb +5 -5
  46. data/readme.md +11 -0
  47. data/releases.md +11 -0
  48. data.tar.gz.sig +0 -0
  49. metadata +7 -6
  50. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd45f82b4d28a3e9a72bbdd7eaec1877b69cfce59a3d67b49a09b99b59b42fa4
4
- data.tar.gz: 05ceb7e93478b63e53bc9a16b829cc2c34efdc22953143192a5a60b310aa04b1
3
+ metadata.gz: 5a6e20407a7969b8eebeaf3f3a2dac09bdb3fe48130454ae49f46251a03b5c98
4
+ data.tar.gz: a827ae19dc5c7f60ed338357091f556d1e48b183fe8ab0c119d75547fce4a6cd
5
5
  SHA512:
6
- metadata.gz: 8b293794ba1fb14494a7187a0c1255674ea771df8cc587c6fa031abca25b8bf803f32ba4889d772d53fb8f4dce0705938f1013ab72c22353b4e09c7cc54f4467
7
- data.tar.gz: b0c407ee2c817bd8436bff520ae09744f44e35ba79bd992e76a783be52911fdedb0b397ab12c3cd1ffb00b3042bfc5d429f11b5252e16edbc2bab178172d747d
6
+ metadata.gz: 116770838b19e96fdc9ed4e1db8af834f0ed8676542d1090ed88c90d02ac0b978bd94378b41edc151108d86e61c39d2fbb9680d217790e1b1cb3f2ceed2aa64e
7
+ data.tar.gz: 2b05b93835b83052a89127f12db03fdcd79007b6b0f396134d9818352e48ffaf012057c0b818a56633f3e8b510e7861438e8016d2519bd4d0dec425dfd1113f7
checksums.yaml.gz.sig CHANGED
Binary file
@@ -21,12 +21,12 @@ end
21
21
  private
22
22
 
23
23
  def server
24
- require 'async'
25
- require 'async/container'
26
- require 'async/http/server'
27
- require 'io/endpoint/host_endpoint'
24
+ require "async"
25
+ require "async/container"
26
+ require "async/http/server"
27
+ require "io/endpoint/host_endpoint"
28
28
 
29
- endpoint = IO::Endpoint.tcp('127.0.0.1', 7272)
29
+ endpoint = IO::Endpoint.tcp("127.0.0.1", 7272)
30
30
 
31
31
  container = Async::Container.new
32
32
 
@@ -34,7 +34,7 @@ def server
34
34
 
35
35
  container.run(count: 1) do
36
36
  server = Async::HTTP::Server.for(endpoint, protocol: Async::HTTP::Protocol::HTTP2, scheme: "https") do |request|
37
- Protocol::HTTP::Response[200, {'content-type' => 'text/plain'}, ["Hello World"]]
37
+ Protocol::HTTP::Response[200, {"content-type" => "text/plain"}, ["Hello World"]]
38
38
  end
39
39
 
40
40
  Async do
data/bake/async/http.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
 
6
6
  # Fetch the specified URL and print the response.
7
7
  # @param url [String] the URL to parse and fetch.
8
8
  # @param method [String] the HTTP method to use.
9
9
  def fetch(url, method:)
10
- require 'async/http/internet'
11
- require 'kernel/sync'
10
+ require "async/http/internet"
11
+ require "kernel/sync"
12
12
 
13
13
  terminal = Console::Terminal.for($stdout)
14
14
  terminal[:request] = terminal.style(:blue, nil, :bold)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require "protocol/http/body/wrapper"
7
+ require "async/variable"
8
+
9
+ module Async
10
+ module HTTP
11
+ module Body
12
+ # Keeps track of whether a body is being read, and if so, waits for it to be closed.
13
+ class Finishable < ::Protocol::HTTP::Body::Wrapper
14
+ def initialize(body)
15
+ super(body)
16
+
17
+ @closed = Async::Variable.new
18
+ @error = nil
19
+
20
+ @reading = false
21
+ end
22
+
23
+ def reading?
24
+ @reading
25
+ end
26
+
27
+ def read
28
+ @reading = true
29
+
30
+ super
31
+ end
32
+
33
+ def close(error = nil)
34
+ unless @closed.resolved?
35
+ @error = error
36
+ @closed.value = true
37
+ end
38
+
39
+ super
40
+ end
41
+
42
+ def wait
43
+ if @reading
44
+ @closed.wait
45
+ else
46
+ self.discard
47
+ end
48
+ end
49
+
50
+ def inspect
51
+ "#<#{self.class} closed=#{@closed} error=#{@error}> | #{super}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/readable'
7
- require 'protocol/http/body/stream'
6
+ require "protocol/http/body/readable"
7
+ require "protocol/http/body/stream"
8
8
 
9
- require_relative 'writable'
9
+ require_relative "writable"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2020, by Bruno Sutic.
6
6
 
7
- require_relative 'writable'
7
+ require_relative "writable"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/writable'
7
- require 'async/queue'
6
+ require "protocol/http/body/writable"
7
+ require "async/queue"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/buffered'
7
- require_relative 'body/writable'
6
+ require "protocol/http/body/buffered"
7
+ require_relative "body/writable"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -4,16 +4,17 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2022, by Ian Ker-Seymer.
6
6
 
7
- require 'io/endpoint'
7
+ require "io/endpoint"
8
8
 
9
- require 'async/pool/controller'
9
+ require "async/pool/controller"
10
10
 
11
- require 'protocol/http/body/completable'
12
- require 'protocol/http/methods'
11
+ require "protocol/http/body/completable"
12
+ require "protocol/http/methods"
13
13
 
14
- require 'traces/provider'
14
+ require "traces/provider"
15
15
 
16
- require_relative 'protocol'
16
+ require_relative "protocol"
17
+ require_relative "body/finishable"
17
18
 
18
19
  module Async
19
20
  module HTTP
@@ -140,7 +141,7 @@ module Async
140
141
  def inspect
141
142
  "#<#{self.class} authority=#{@authority.inspect}>"
142
143
  end
143
-
144
+
144
145
  Traces::Provider(self) do
145
146
  def call(request)
146
147
  attributes = {
@@ -151,30 +152,30 @@ module Async
151
152
  }
152
153
 
153
154
  if protocol = request.protocol
154
- attributes['http.protocol'] = protocol
155
+ attributes["http.protocol"] = protocol
155
156
  end
156
157
 
157
158
  if length = request.body&.length
158
- attributes['http.request.length'] = length
159
+ attributes["http.request.length"] = length
159
160
  end
160
161
 
161
- Traces.trace('async.http.client.call', attributes: attributes) do |span|
162
+ Traces.trace("async.http.client.call", attributes: attributes) do |span|
162
163
  if context = Traces.trace_context
163
- request.headers['traceparent'] = context.to_s
164
+ request.headers["traceparent"] = context.to_s
164
165
  # request.headers['tracestate'] = context.state
165
166
  end
166
167
 
167
168
  super.tap do |response|
168
169
  if version = response&.version
169
- span['http.version'] = version
170
+ span["http.version"] = version
170
171
  end
171
172
 
172
173
  if status = response&.status
173
- span['http.status_code'] = status
174
+ span["http.status_code"] = status
174
175
  end
175
176
 
176
177
  if length = response.body&.length
177
- span['http.response.length'] = length
178
+ span["http.response.length"] = length
178
179
  end
179
180
  end
180
181
  end
@@ -186,10 +187,7 @@ module Async
186
187
  def make_response(request, connection)
187
188
  response = request.call(connection)
188
189
 
189
- # The connection won't be released until the body is completely read/released.
190
- ::Protocol::HTTP::Body::Completable.wrap(response) do
191
- @pool.release(connection)
192
- end
190
+ response.pool = @pool
193
191
 
194
192
  return response
195
193
  end
@@ -7,22 +7,22 @@
7
7
  # Copyright, 2024, by Igor Sidorov.
8
8
  # Copyright, 2024, by Hal Brodigan.
9
9
 
10
- require 'io/endpoint'
11
- require 'io/endpoint/host_endpoint'
12
- require 'io/endpoint/ssl_endpoint'
10
+ require "io/endpoint"
11
+ require "io/endpoint/host_endpoint"
12
+ require "io/endpoint/ssl_endpoint"
13
13
 
14
- require_relative 'protocol/http'
15
- require_relative 'protocol/https'
14
+ require_relative "protocol/http"
15
+ require_relative "protocol/https"
16
16
 
17
17
  module Async
18
18
  module HTTP
19
19
  # Represents a way to connect to a remote HTTP server.
20
20
  class Endpoint < ::IO::Endpoint::Generic
21
21
  SCHEMES = {
22
- 'http' => URI::HTTP,
23
- 'https' => URI::HTTPS,
24
- 'ws' => URI::WS,
25
- 'wss' => URI::WSS,
22
+ "http" => URI::HTTP,
23
+ "https" => URI::HTTPS,
24
+ "ws" => URI::WS,
25
+ "wss" => URI::WSS,
26
26
  }
27
27
 
28
28
  def self.parse(string, endpoint = nil, **options)
@@ -102,7 +102,7 @@ module Async
102
102
  end
103
103
 
104
104
  def secure?
105
- ['https', 'wss'].include?(self.scheme)
105
+ ["https", "wss"].include?(self.scheme)
106
106
  end
107
107
 
108
108
  def protocol
@@ -3,7 +3,7 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
- require_relative '../internet'
6
+ require_relative "../internet"
7
7
 
8
8
  ::Thread.attr_accessor :async_http_internet_instance
9
9
 
@@ -4,12 +4,12 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2024, by Igor Sidorov.
6
6
 
7
- require_relative 'client'
8
- require_relative 'endpoint'
7
+ require_relative "client"
8
+ require_relative "endpoint"
9
9
 
10
- require 'protocol/http/middleware'
11
- require 'protocol/http/body/buffered'
12
- require 'protocol/http/accept_encoding'
10
+ require "protocol/http/middleware"
11
+ require "protocol/http/body/buffered"
12
+ require "protocol/http/accept_encoding"
13
13
 
14
14
  module Async
15
15
  module HTTP
@@ -3,10 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2024, by Samuel Williams.
5
5
 
6
- require_relative '../reference'
6
+ require_relative "../reference"
7
7
 
8
- require 'protocol/http/middleware'
9
- require 'protocol/http/body/rewindable'
8
+ require "protocol/http/middleware"
9
+ require "protocol/http/body/rewindable"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -34,10 +34,10 @@ module Async
34
34
 
35
35
  # Header keys which should be deleted when changing a request from a POST to a GET as defined by <https://fetch.spec.whatwg.org/#request-body-header-name>.
36
36
  PROHIBITED_GET_HEADERS = [
37
- 'content-encoding',
38
- 'content-language',
39
- 'content-location',
40
- 'content-type',
37
+ "content-encoding",
38
+ "content-language",
39
+ "content-location",
40
+ "content-type",
41
41
  ]
42
42
 
43
43
  # maximum_hops is the max number of redirects. Set to 0 to allow 1 request with no redirects.
@@ -91,7 +91,7 @@ module Async
91
91
  hops += 1
92
92
 
93
93
  # Get the redirect location:
94
- unless location = response.headers['location']
94
+ unless location = response.headers["location"]
95
95
  return response
96
96
  end
97
97
 
@@ -3,9 +3,9 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2024, by Samuel Williams.
5
5
 
6
- require_relative '../protocol'
6
+ require_relative "../protocol"
7
7
 
8
- require 'async/queue'
8
+ require "async/queue"
9
9
 
10
10
  module Async
11
11
  module HTTP
@@ -3,4 +3,4 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2024, by Samuel Williams.
5
5
 
6
- require_relative 'mock/endpoint'
6
+ require_relative "mock/endpoint"
@@ -4,8 +4,8 @@
4
4
  # Copyright, 2024, by Thomas Morgan.
5
5
  # Copyright, 2024, by Samuel Williams.
6
6
 
7
- require_relative 'http1'
8
- require_relative 'http2'
7
+ require_relative "http1"
8
+ require_relative "http2"
9
9
 
10
10
  module Async
11
11
  module HTTP
@@ -3,18 +3,32 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative 'connection'
6
+ require_relative "connection"
7
7
 
8
8
  module Async
9
9
  module HTTP
10
10
  module Protocol
11
11
  module HTTP1
12
12
  class Client < Connection
13
+ def initialize(...)
14
+ super
15
+
16
+ @pool = nil
17
+ end
18
+
19
+ attr_accessor :pool
20
+
21
+ def closed!
22
+ super
23
+
24
+ if pool = @pool
25
+ @pool = nil
26
+ pool.release(self)
27
+ end
28
+ end
29
+
13
30
  # Used by the client to send requests to the remote server.
14
31
  def call(request, task: Task.current)
15
- # We need to keep track of connections which are not in the initial "ready" state.
16
- @ready = false
17
-
18
32
  Console.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
19
33
 
20
34
  # Mark the start of the trailers:
@@ -54,12 +68,11 @@ module Async
54
68
  end
55
69
 
56
70
  response = Response.read(self, request)
57
- @ready = true
58
71
 
59
72
  return response
60
73
  rescue
61
74
  # This will ensure that #reusable? returns false.
62
- @stream.close
75
+ self.close
63
76
 
64
77
  raise
65
78
  end
@@ -3,10 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http1'
6
+ require "protocol/http1"
7
7
 
8
- require_relative 'request'
9
- require_relative 'response'
8
+ require_relative "request"
9
+ require_relative "response"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -16,12 +16,11 @@ module Async
16
16
  def initialize(stream, version)
17
17
  super(stream)
18
18
 
19
- @ready = true
20
19
  @version = version
21
20
  end
22
21
 
23
22
  def to_s
24
- "\#<#{self.class} negotiated #{@version}, currently #{@ready ? 'ready' : 'in-use'}>"
23
+ "\#<#{self.class} negotiated #{@version}, #{@state}>"
25
24
  end
26
25
 
27
26
  def as_json(...)
@@ -62,11 +61,11 @@ module Async
62
61
 
63
62
  # Can we use this connection to make requests?
64
63
  def viable?
65
- @ready && @stream&.readable?
64
+ self.idle? && @stream&.readable?
66
65
  end
67
66
 
68
67
  def reusable?
69
- @ready && @persistent && @stream && !@stream.closed?
68
+ @persistent && @stream && !@stream.closed?
70
69
  end
71
70
  end
72
71
  end
@@ -3,7 +3,7 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative '../request'
6
+ require_relative "../request"
7
7
 
8
8
  module Async
9
9
  module HTTP
@@ -16,13 +16,13 @@ module Async
16
16
  end
17
17
  end
18
18
 
19
- UPGRADE = 'upgrade'
19
+ UPGRADE = "upgrade"
20
20
 
21
21
  def initialize(connection, authority, method, path, version, headers, body)
22
22
  @connection = connection
23
23
 
24
24
  # HTTP/1 requests with an upgrade header (which can contain zero or more values) are extracted into the protocol field of the request, and we expect a response to select one of those protocols with a status code of 101 Switching Protocols.
25
- protocol = headers.delete('upgrade')
25
+ protocol = headers.delete("upgrade")
26
26
 
27
27
  super(nil, authority, method, path, version, headers, body, protocol, self.public_method(:write_interim_response))
28
28
  end
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2023, by Josh Huber.
6
6
 
7
- require_relative '../response'
7
+ require_relative "../response"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -23,7 +23,7 @@ module Async
23
23
  end
24
24
  end
25
25
 
26
- UPGRADE = 'upgrade'
26
+ UPGRADE = "upgrade"
27
27
 
28
28
  # @attribute [String] The HTTP response line reason.
29
29
  attr :reason
@@ -39,6 +39,14 @@ module Async
39
39
  super(version, status, headers, body, protocol)
40
40
  end
41
41
 
42
+ def pool=(pool)
43
+ if @connection.idle? or @connection.closed?
44
+ pool.release(@connection)
45
+ else
46
+ @connection.pool = pool
47
+ end
48
+ end
49
+
42
50
  def connection
43
51
  @connection
44
52
  end
@@ -6,8 +6,10 @@
6
6
  # Copyright, 2023, by Thomas Morgan.
7
7
  # Copyright, 2024, by Anton Zhuravsky.
8
8
 
9
- require_relative 'connection'
10
- require 'console/event/failure'
9
+ require_relative "connection"
10
+ require_relative "../../body/finishable"
11
+
12
+ require "console/event/failure"
11
13
 
12
14
  module Async
13
15
  module HTTP
@@ -20,7 +22,7 @@ module Async
20
22
  write_body(@version, nil)
21
23
  rescue => error
22
24
  # At this point, there is very little we can do to recover:
23
- Console::Event::Failure.for(error).emit(self, "Failed to write failure response.", severity: :debug)
25
+ Console::Event::Failure.for(error).emit(self, "Failed to write failure response!", severity: :debug)
24
26
  end
25
27
 
26
28
  def next_request
@@ -35,7 +37,7 @@ module Async
35
37
  end
36
38
 
37
39
  return request
38
- rescue ::Protocol::HTTP1::BadRequest
40
+ rescue ::Protocol::HTTP1::BadRequest => error
39
41
  fail_request(400)
40
42
  # Conceivably we could retry here, but we don't really know how bad the error is, so it's better to just fail:
41
43
  raise
@@ -46,7 +48,13 @@ module Async
46
48
  task.annotate("Reading #{self.version} requests for #{self.class}.")
47
49
 
48
50
  while request = next_request
51
+ if body = request.body
52
+ finishable = Body::Finishable.new(body)
53
+ request.body = finishable
54
+ end
55
+
49
56
  response = yield(request, self)
57
+ version = request.version
50
58
  body = response&.body
51
59
 
52
60
  if hijacked?
@@ -77,7 +85,7 @@ module Async
77
85
  # This code path is to support legacy behavior where the response status is set to 101, but the protocol is not upgraded. This may not be a valid use case, but it is supported for compatibility. We expect the response headers to contain the `upgrade` header.
78
86
  write_response(@version, response.status, response.headers)
79
87
 
80
- stream = write_tunnel_body(request.version)
88
+ stream = write_tunnel_body(version)
81
89
 
82
90
  # Same as above:
83
91
  request = nil
@@ -89,7 +97,7 @@ module Async
89
97
  write_response(@version, response.status, response.headers)
90
98
 
91
99
  if request.connect? and response.success?
92
- stream = write_tunnel_body(request.version)
100
+ stream = write_tunnel_body(version)
93
101
 
94
102
  # Same as above:
95
103
  request = nil
@@ -99,26 +107,26 @@ module Async
99
107
  return body.call(stream)
100
108
  else
101
109
  head = request.head?
102
- version = request.version
103
110
 
104
111
  # Same as above:
105
- request = nil unless request.body
112
+ request = nil
106
113
  response = nil
107
114
 
108
115
  write_body(version, body, head, trailer)
109
116
  end
110
117
  end
111
118
 
112
- # We are done with the body, you shouldn't need to call close on it:
119
+ # We are done with the body:
113
120
  body = nil
114
121
  else
115
122
  # If the request failed to generate a response, it was an internal server error:
116
123
  write_response(@version, 500, {})
117
- write_body(request.version, nil)
124
+ write_body(version, nil)
125
+
126
+ request&.finish
118
127
  end
119
128
 
120
- # Gracefully finish reading the request body if it was not already done so.
121
- request&.each{}
129
+ finishable&.wait
122
130
 
123
131
  # This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
124
132
  task.yield
@@ -4,10 +4,10 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2024, by Thomas Morgan.
6
6
 
7
- require_relative 'http1/client'
8
- require_relative 'http1/server'
7
+ require_relative "http1/client"
8
+ require_relative "http1/server"
9
9
 
10
- require 'io/stream'
10
+ require "io/stream"
11
11
 
12
12
  module Async
13
13
  module HTTP
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2024, by Thomas Morgan.
6
6
 
7
- require_relative 'http1'
7
+ require_relative "http1"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -5,7 +5,7 @@
5
5
  # Copyright, 2018, by Janko Marohnić.
6
6
  # Copyright, 2024, by Thomas Morgan.
7
7
 
8
- require_relative 'http1'
8
+ require_relative "http1"
9
9
 
10
10
  module Async
11
11
  module HTTP
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative 'connection'
7
- require_relative 'response'
6
+ require_relative "connection"
7
+ require_relative "response"
8
8
 
9
- require 'protocol/http2/client'
9
+ require "protocol/http2/client"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -4,25 +4,25 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2020, by Bruno Sutic.
6
6
 
7
- require_relative 'stream'
7
+ require_relative "stream"
8
8
 
9
- require 'async/semaphore'
9
+ require "async/semaphore"
10
10
 
11
11
  module Async
12
12
  module HTTP
13
13
  module Protocol
14
14
  module HTTP2
15
- HTTPS = 'https'.freeze
16
- SCHEME = ':scheme'.freeze
17
- METHOD = ':method'.freeze
18
- PATH = ':path'.freeze
19
- AUTHORITY = ':authority'.freeze
20
- STATUS = ':status'.freeze
21
- PROTOCOL = ':protocol'.freeze
15
+ HTTPS = "https".freeze
16
+ SCHEME = ":scheme".freeze
17
+ METHOD = ":method".freeze
18
+ PATH = ":path".freeze
19
+ AUTHORITY = ":authority".freeze
20
+ STATUS = ":status".freeze
21
+ PROTOCOL = ":protocol".freeze
22
22
 
23
- CONTENT_LENGTH = 'content-length'.freeze
24
- CONNECTION = 'connection'.freeze
25
- TRAILER = 'trailer'.freeze
23
+ CONTENT_LENGTH = "content-length".freeze
24
+ CONNECTION = "connection".freeze
25
+ TRAILER = "trailer".freeze
26
26
 
27
27
  module Connection
28
28
  def initialize(*)
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/writable'
6
+ require "protocol/http/body/writable"
7
7
 
8
8
  module Async
9
9
  module HTTP
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/stream'
6
+ require "protocol/http/body/stream"
7
7
 
8
8
  module Async
9
9
  module HTTP
@@ -3,8 +3,8 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative '../request'
7
- require_relative 'stream'
6
+ require_relative "../request"
7
+ require_relative "stream"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -53,7 +53,7 @@ module Async
53
53
  @length = Integer(value)
54
54
  elsif key == CONNECTION
55
55
  raise ::Protocol::HTTP2::HeaderError, "Connection header is not allowed!"
56
- elsif key.start_with? ':'
56
+ elsif key.start_with? ":"
57
57
  raise ::Protocol::HTTP2::HeaderError, "Invalid pseudo-header #{key}!"
58
58
  elsif key =~ /[A-Z]/
59
59
  raise ::Protocol::HTTP2::HeaderError, "Invalid characters in header #{key}!"
@@ -107,7 +107,7 @@ module Async
107
107
  end
108
108
 
109
109
  NO_RESPONSE = [
110
- [STATUS, '500'],
110
+ [STATUS, "500"],
111
111
  ]
112
112
 
113
113
  def send_response(response)
@@ -3,8 +3,8 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative '../response'
7
- require_relative 'stream'
6
+ require_relative "../response"
7
+ require_relative "stream"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -41,7 +41,7 @@ module Async
41
41
  # While in theory, the response pseudo-headers may be extended in the future, currently they only response pseudo-header is :status, so we can assume it is always the first header.
42
42
  status_header = headers.shift
43
43
 
44
- if status_header.first != ':status'
44
+ if status_header.first != ":status"
45
45
  raise ProtocolError, "Invalid response headers: #{headers.inspect}"
46
46
  end
47
47
 
@@ -137,6 +137,16 @@ module Async
137
137
  attr :stream
138
138
  attr :request
139
139
 
140
+ def pool=(pool)
141
+ # If we are already closed, the stream can be released now:
142
+ if @stream.closed?
143
+ pool.release(@stream.connection)
144
+ else
145
+ # Otherwise, we will release the stream when it is closed:
146
+ @stream.pool = pool
147
+ end
148
+ end
149
+
140
150
  def connection
141
151
  @stream.connection
142
152
  end
@@ -175,7 +185,7 @@ module Async
175
185
  raise ::Protocol::HTTP2::HeaderError, "Request path already specified!" if request.path
176
186
 
177
187
  request.path = value
178
- elsif key.start_with? ':'
188
+ elsif key.start_with? ":"
179
189
  raise ::Protocol::HTTP2::HeaderError, "Invalid pseudo-header #{key}!"
180
190
  else
181
191
  request.headers[key] = value
@@ -3,10 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require_relative 'connection'
7
- require_relative 'request'
6
+ require_relative "connection"
7
+ require_relative "request"
8
8
 
9
- require 'protocol/http2/server'
9
+ require "protocol/http2/server"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -5,10 +5,10 @@
5
5
  # Copyright, 2022, by Marco Concetto Rudilosso.
6
6
  # Copyright, 2023, by Thomas Morgan.
7
7
 
8
- require 'protocol/http2/stream'
8
+ require "protocol/http2/stream"
9
9
 
10
- require_relative 'input'
11
- require_relative 'output'
10
+ require_relative "input"
11
+ require_relative "output"
12
12
 
13
13
  module Async
14
14
  module HTTP
@@ -20,6 +20,8 @@ module Async
20
20
 
21
21
  @headers = nil
22
22
 
23
+ @pool = nil
24
+
23
25
  # Input buffer, reading request body, or response body (receive_data):
24
26
  @length = nil
25
27
  @input = nil
@@ -30,12 +32,14 @@ module Async
30
32
 
31
33
  attr_accessor :headers
32
34
 
35
+ attr_accessor :pool
36
+
33
37
  attr :input
34
38
 
35
39
  def add_header(key, value)
36
40
  if key == CONNECTION
37
41
  raise ::Protocol::HTTP2::HeaderError, "Connection header is not allowed!"
38
- elsif key.start_with? ':'
42
+ elsif key.start_with? ":"
39
43
  raise ::Protocol::HTTP2::HeaderError, "Invalid pseudo-header #{key}!"
40
44
  elsif key =~ /[A-Z]/
41
45
  raise ::Protocol::HTTP2::HeaderError, "Invalid upper-case characters in header #{key}!"
@@ -158,6 +162,10 @@ module Async
158
162
  @output = nil
159
163
  end
160
164
 
165
+ if pool = @pool and @connection
166
+ pool.release(@connection)
167
+ end
168
+
161
169
  return self
162
170
  end
163
171
  end
@@ -4,10 +4,10 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2024, by Thomas Morgan.
6
6
 
7
- require_relative 'http2/client'
8
- require_relative 'http2/server'
7
+ require_relative "http2/client"
8
+ require_relative "http2/server"
9
9
 
10
- require 'io/stream'
10
+ require "io/stream"
11
11
 
12
12
  module Async
13
13
  module HTTP
@@ -4,10 +4,10 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2019, by Brian Morearty.
6
6
 
7
- require_relative 'http10'
8
- require_relative 'http11'
7
+ require_relative "http10"
8
+ require_relative "http11"
9
9
 
10
- require_relative 'http2'
10
+ require_relative "http2"
11
11
 
12
12
  module Async
13
13
  module HTTP
@@ -3,10 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/request'
7
- require 'protocol/http/headers'
6
+ require "protocol/http/request"
7
+ require "protocol/http/headers"
8
8
 
9
- require_relative '../body/writable'
9
+ require_relative "../body/writable"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2023, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/response'
6
+ require "protocol/http/response"
7
7
 
8
- require_relative '../body/writable'
8
+ require_relative "../body/writable"
9
9
 
10
10
  module Async
11
11
  module HTTP
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2023, by Samuel Williams.
4
+ # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
- require_relative 'protocol/http1'
7
- require_relative 'protocol/https'
6
+ require_relative "protocol/http1"
7
+ require_relative "protocol/https"
8
8
 
9
9
  module Async
10
10
  module HTTP
@@ -3,10 +3,10 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
- require_relative 'client'
7
- require_relative 'endpoint'
6
+ require_relative "client"
7
+ require_relative "endpoint"
8
8
 
9
- require_relative 'body/pipe'
9
+ require_relative "body/pipe"
10
10
 
11
11
  module Async
12
12
  module HTTP
@@ -96,6 +96,7 @@ module Async
96
96
  end
97
97
  else
98
98
  # This ensures we don't leave a response dangling:
99
+ input.close
99
100
  response.close
100
101
 
101
102
  raise ConnectFailure, response
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/reference'
6
+ require "protocol/http/reference"
7
7
 
8
8
  module Async
9
9
  module HTTP
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2018-2024, by Samuel Williams.
5
5
  # Copyright, 2019-2020, by Brian Morearty.
6
6
 
7
- require_relative 'middleware/location_redirector'
7
+ require_relative "middleware/location_redirector"
8
8
 
9
9
  warn "`Async::HTTP::RelativeLocation` is deprecated and will be removed in the next release. Please use `Async::HTTP::Middleware::LocationRedirector` instead.", uplevel: 1
10
10
 
@@ -4,12 +4,12 @@
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
  # Copyright, 2019, by Brian Morearty.
6
6
 
7
- require 'async'
8
- require 'io/endpoint'
9
- require 'protocol/http/middleware'
10
- require 'traces/provider'
7
+ require "async"
8
+ require "io/endpoint"
9
+ require "protocol/http/middleware"
10
+ require "traces/provider"
11
11
 
12
- require_relative 'protocol'
12
+ require_relative "protocol"
13
13
 
14
14
  module Async
15
15
  module HTTP
@@ -76,8 +76,8 @@ module Async
76
76
 
77
77
  Traces::Provider(self) do
78
78
  def call(request)
79
- if trace_parent = request.headers['traceparent']
80
- Traces.trace_context = Traces::Context.parse(trace_parent.join, request.headers['tracestate'], remote: true)
79
+ if trace_parent = request.headers["traceparent"]
80
+ Traces.trace_context = Traces::Context.parse(trace_parent.join, request.headers["tracestate"], remote: true)
81
81
  end
82
82
 
83
83
  attributes = {
@@ -86,25 +86,25 @@ module Async
86
86
  'http.authority': request.authority,
87
87
  'http.scheme': request.scheme,
88
88
  'http.path': request.path,
89
- 'http.user_agent': request.headers['user-agent'],
89
+ 'http.user_agent': request.headers["user-agent"],
90
90
  }
91
91
 
92
92
  if length = request.body&.length
93
- attributes['http.request.length'] = length
93
+ attributes["http.request.length"] = length
94
94
  end
95
95
 
96
96
  if protocol = request.protocol
97
- attributes['http.protocol'] = protocol
97
+ attributes["http.protocol"] = protocol
98
98
  end
99
99
 
100
- Traces.trace('async.http.server.call', resource: "#{request.method} #{request.path}", attributes: attributes) do |span|
100
+ Traces.trace("async.http.server.call", resource: "#{request.method} #{request.path}", attributes: attributes) do |span|
101
101
  super.tap do |response|
102
102
  if status = response&.status
103
- span['http.status_code'] = status
103
+ span["http.status_code"] = status
104
104
  end
105
105
 
106
106
  if length = response&.body&.length
107
- span['http.response.length'] = length
107
+ span["http.response.length"] = length
108
108
  end
109
109
  end
110
110
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
4
+ # Copyright, 2018-2024, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/wrapper'
6
+ require "protocol/http/body/wrapper"
7
7
 
8
- require 'async/clock'
8
+ require "async/clock"
9
9
 
10
10
  module Async
11
11
  module HTTP
@@ -89,7 +89,7 @@ module Async
89
89
  parts << "took #{format_duration(duration)} until first chunk"
90
90
  end
91
91
 
92
- return parts.join('; ')
92
+ return parts.join("; ")
93
93
  end
94
94
 
95
95
  def inspect
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module HTTP
8
- VERSION = "0.76.0"
8
+ VERSION = "0.77.0"
9
9
  end
10
10
  end
data/lib/async/http.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2017-2024, by Samuel Williams.
5
5
 
6
- require_relative 'http/version'
6
+ require_relative "http/version"
7
7
 
8
- require_relative 'http/client'
9
- require_relative 'http/server'
8
+ require_relative "http/client"
9
+ require_relative "http/server"
10
10
 
11
- require_relative 'http/internet'
11
+ require_relative "http/internet"
12
12
 
13
- require_relative 'http/endpoint'
13
+ require_relative "http/endpoint"
data/readme.md CHANGED
@@ -16,6 +16,17 @@ Please see the [project documentation](https://socketry.github.io/async-http/) f
16
16
 
17
17
  Please see the [project releases](https://socketry.github.io/async-http/releases/index) for all releases.
18
18
 
19
+ ### v0.77.0
20
+
21
+ - Improved HTTP/1 connection handling.
22
+ - The input stream is no longer closed when the output stream is closed.
23
+
24
+ ### v0.76.0
25
+
26
+ - `Async::HTTP::Body::Writable` is moved to `Protocol::HTTP::Body::Writable`.
27
+ - Remove `Async::HTTP::Body::Delayed` with no replacement.
28
+ - Remove `Async::HTTP::Body::Slowloris` with no replacement.
29
+
19
30
  ### v0.75.0
20
31
 
21
32
  - Better handling of HTTP/1 \&lt;-\&gt; HTTP/2 proxying, specifically upgrade/CONNECT requests.
data/releases.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Releases
2
2
 
3
+ ## v0.77.0
4
+
5
+ - Improved HTTP/1 connection handling.
6
+ - The input stream is no longer closed when the output stream is closed.
7
+
8
+ ## v0.76.0
9
+
10
+ - `Async::HTTP::Body::Writable` is moved to `Protocol::HTTP::Body::Writable`.
11
+ - Remove `Async::HTTP::Body::Delayed` with no replacement.
12
+ - Remove `Async::HTTP::Body::Slowloris` with no replacement.
13
+
3
14
  ## v0.75.0
4
15
 
5
16
  - Better handling of HTTP/1 \<-\> HTTP/2 proxying, specifically upgrade/CONNECT requests.
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.76.0
4
+ version: 0.77.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -58,7 +58,7 @@ cert_chain:
58
58
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
59
59
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
60
60
  -----END CERTIFICATE-----
61
- date: 2024-09-10 00:00:00.000000000 Z
61
+ date: 2024-09-19 00:00:00.000000000 Z
62
62
  dependencies:
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: async
@@ -122,28 +122,28 @@ dependencies:
122
122
  requirements:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
- version: '0.34'
125
+ version: '0.37'
126
126
  type: :runtime
127
127
  prerelease: false
128
128
  version_requirements: !ruby/object:Gem::Requirement
129
129
  requirements:
130
130
  - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: '0.34'
132
+ version: '0.37'
133
133
  - !ruby/object:Gem::Dependency
134
134
  name: protocol-http1
135
135
  requirement: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: '0.20'
139
+ version: '0.25'
140
140
  type: :runtime
141
141
  prerelease: false
142
142
  version_requirements: !ruby/object:Gem::Requirement
143
143
  requirements:
144
144
  - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: '0.20'
146
+ version: '0.25'
147
147
  - !ruby/object:Gem::Dependency
148
148
  name: protocol-http2
149
149
  requirement: !ruby/object:Gem::Requirement
@@ -182,6 +182,7 @@ files:
182
182
  - bake/async/http/h2spec.rb
183
183
  - lib/async/http.rb
184
184
  - lib/async/http/body.rb
185
+ - lib/async/http/body/finishable.rb
185
186
  - lib/async/http/body/hijack.rb
186
187
  - lib/async/http/body/pipe.rb
187
188
  - lib/async/http/body/writable.rb
metadata.gz.sig CHANGED
Binary file