async-http 0.76.0 → 0.77.0

Sign up to get free protection for your applications and to get access to all the features.
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