async-http 0.77.0 → 0.79.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a6e20407a7969b8eebeaf3f3a2dac09bdb3fe48130454ae49f46251a03b5c98
4
- data.tar.gz: a827ae19dc5c7f60ed338357091f556d1e48b183fe8ab0c119d75547fce4a6cd
3
+ metadata.gz: 93d3d9d22a84537ba522b417586a7bc02d243d56defe26fb9a3937470675b244
4
+ data.tar.gz: 8c16add5b2c301948235c739d076f92c400cdd745cc36881fac49125a41c5bc4
5
5
  SHA512:
6
- metadata.gz: 116770838b19e96fdc9ed4e1db8af834f0ed8676542d1090ed88c90d02ac0b978bd94378b41edc151108d86e61c39d2fbb9680d217790e1b1cb3f2ceed2aa64e
7
- data.tar.gz: 2b05b93835b83052a89127f12db03fdcd79007b6b0f396134d9818352e48ffaf012057c0b818a56633f3e8b510e7861438e8016d2519bd4d0dec425dfd1113f7
6
+ metadata.gz: cf362c7394967d2746e6d37984f25f5fc02e8e1fa89b7d5f6afcf31d87e665d75877cd5a9cd920f1ae87b6ecbc73e7d9b7cedc72467a69d95b5d24a04b897a8e
7
+ data.tar.gz: e922cf9cfd745423d9350f633d1a152dd8f6e06ae23abd3c8b1e403f49abb7089181aa68a1ded1a329cf2017b7bf98feb6a60fed45e1f8582eb3b01ca401ce4d
checksums.yaml.gz.sig CHANGED
Binary file
@@ -14,7 +14,6 @@ require "protocol/http/methods"
14
14
  require "traces/provider"
15
15
 
16
16
  require_relative "protocol"
17
- require_relative "body/finishable"
18
17
 
19
18
  module Async
20
19
  module HTTP
@@ -18,11 +18,12 @@ module Async
18
18
 
19
19
  attr_accessor :pool
20
20
 
21
- def closed!
21
+ def closed(error = nil)
22
22
  super
23
23
 
24
24
  if pool = @pool
25
25
  @pool = nil
26
+ # If the connection is not reusable, this will retire it from the connection pool and invoke `#close`.
26
27
  pool.release(self)
27
28
  end
28
29
  end
@@ -50,30 +51,32 @@ module Async
50
51
  task.async(annotation: "Upgrading request...") do
51
52
  # If this fails, this connection will be closed.
52
53
  write_upgrade_body(protocol, body)
54
+ rescue => error
55
+ self.close(error)
53
56
  end
54
57
  elsif request.connect?
55
58
  task.async(annotation: "Tunnneling request...") do
56
59
  write_tunnel_body(@version, body)
60
+ rescue => error
61
+ self.close(error)
57
62
  end
58
63
  else
59
64
  task.async(annotation: "Streaming request...") do
60
65
  # Once we start writing the body, we can't recover if the request fails. That's because the body might be generated dynamically, streaming, etc.
61
66
  write_body(@version, body, false, trailer)
67
+ rescue => error
68
+ self.close(error)
62
69
  end
63
70
  end
64
71
  elsif protocol = request.protocol
65
72
  write_upgrade_body(protocol)
66
73
  else
67
- write_body(@version, body, false, trailer)
74
+ write_body(@version, request.body, false, trailer)
68
75
  end
69
76
 
70
- response = Response.read(self, request)
71
-
72
- return response
73
- rescue
74
- # This will ensure that #reusable? returns false.
75
- self.close
76
-
77
+ return Response.read(self, request)
78
+ rescue => error
79
+ self.close(error)
77
80
  raise
78
81
  end
79
82
  end
@@ -43,6 +43,8 @@ module Async
43
43
 
44
44
  def read_line?
45
45
  @stream.read_until(CRLF)
46
+ rescue Errno::ECONNRESET
47
+ return nil
46
48
  end
47
49
 
48
50
  def read_line
@@ -0,0 +1,62 @@
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 Protocol
12
+ module HTTP1
13
+ # Keeps track of whether a body is being read, and if so, waits for it to be closed.
14
+ class Finishable < ::Protocol::HTTP::Body::Wrapper
15
+ def initialize(body)
16
+ super(body)
17
+
18
+ @closed = Async::Variable.new
19
+ @error = nil
20
+
21
+ @reading = false
22
+ end
23
+
24
+ def reading?
25
+ @reading
26
+ end
27
+
28
+ def read
29
+ @reading = true
30
+
31
+ super
32
+ end
33
+
34
+ def close(error = nil)
35
+ super
36
+
37
+ unless @closed.resolved?
38
+ @error = error
39
+ @closed.value = true
40
+ end
41
+ end
42
+
43
+ def wait(persistent = true)
44
+ if @reading
45
+ @closed.wait
46
+ elsif persistent
47
+ # If the connection can be reused, let's gracefully discard the body:
48
+ self.discard
49
+ else
50
+ # Else, we don't care about the body, so we can close it immediately:
51
+ self.close
52
+ end
53
+ end
54
+
55
+ def inspect
56
+ "#<#{self.class} closed=#{@closed} error=#{@error}> | #{super}"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -7,7 +7,7 @@
7
7
  # Copyright, 2024, by Anton Zhuravsky.
8
8
 
9
9
  require_relative "connection"
10
- require_relative "../../body/finishable"
10
+ require_relative "finishable"
11
11
 
12
12
  require "console/event/failure"
13
13
 
@@ -16,6 +16,18 @@ module Async
16
16
  module Protocol
17
17
  module HTTP1
18
18
  class Server < Connection
19
+ def initialize(...)
20
+ super
21
+
22
+ @ready = Async::Notification.new
23
+ end
24
+
25
+ def closed(error = nil)
26
+ super
27
+
28
+ @ready.signal
29
+ end
30
+
19
31
  def fail_request(status)
20
32
  @persistent = false
21
33
  write_response(@version, status, {})
@@ -26,8 +38,11 @@ module Async
26
38
  end
27
39
 
28
40
  def next_request
29
- # The default is true.
30
- return unless @persistent
41
+ if closed?
42
+ return nil
43
+ elsif !idle?
44
+ @ready.wait
45
+ end
31
46
 
32
47
  # Read an incoming request:
33
48
  return unless request = Request.read(self)
@@ -49,7 +64,7 @@ module Async
49
64
 
50
65
  while request = next_request
51
66
  if body = request.body
52
- finishable = Body::Finishable.new(body)
67
+ finishable = Finishable.new(body)
53
68
  request.body = finishable
54
69
  end
55
70
 
@@ -73,38 +88,41 @@ module Async
73
88
  # We force a 101 response if the protocol is upgraded - HTTP/2 CONNECT will return 200 for success, but this won't be understood by HTTP/1 clients:
74
89
  write_response(@version, 101, response.headers)
75
90
 
76
- stream = write_upgrade_body(protocol)
77
-
78
91
  # At this point, the request body is hijacked, so we don't want to call #finish below.
79
92
  request = nil
80
93
  response = nil
81
94
 
82
- # We must return here as no further request processing can be done:
83
- return body.call(stream)
95
+ if body.stream?
96
+ return body.call(write_upgrade_body(protocol))
97
+ else
98
+ write_upgrade_body(protocol, body)
99
+ end
84
100
  elsif response.status == 101
85
101
  # 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.
86
102
  write_response(@version, response.status, response.headers)
87
103
 
88
- stream = write_tunnel_body(version)
89
-
90
104
  # Same as above:
91
105
  request = nil
92
106
  response = nil
93
107
 
94
- # We must return here as no further request processing can be done:
95
- return body&.call(stream)
108
+ if body.stream?
109
+ return body.call(write_tunnel_body(version))
110
+ else
111
+ write_tunnel_body(version, body)
112
+ end
96
113
  else
97
114
  write_response(@version, response.status, response.headers)
98
115
 
99
116
  if request.connect? and response.success?
100
- stream = write_tunnel_body(version)
101
-
102
117
  # Same as above:
103
118
  request = nil
104
119
  response = nil
105
120
 
106
- # We must return here as no further request processing can be done:
107
- return body.call(stream)
121
+ if body.stream?
122
+ return body.call(write_tunnel_body(version))
123
+ else
124
+ write_tunnel_body(version, body)
125
+ end
108
126
  else
109
127
  head = request.head?
110
128
 
@@ -126,10 +144,12 @@ module Async
126
144
  request&.finish
127
145
  end
128
146
 
129
- finishable&.wait
130
-
131
- # This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
132
- task.yield
147
+ if finishable
148
+ finishable.wait(@persistent)
149
+ else
150
+ # Do not remove this line or you will unleash the gods of concurrency hell.
151
+ task.yield
152
+ end
133
153
  rescue => error
134
154
  raise
135
155
  ensure
@@ -62,9 +62,9 @@ module Async
62
62
  end
63
63
 
64
64
  # TODO this might need to be in an ensure block:
65
- if @input and frame.end_stream?
66
- @input.close_write
65
+ if input = @input and frame.end_stream?
67
66
  @input = nil
67
+ input.close_write
68
68
  end
69
69
  rescue ::Protocol::HTTP2::HeaderError => error
70
70
  Console.logger.debug(self, error)
@@ -123,6 +123,8 @@ module Async
123
123
 
124
124
  # Called when the output terminates normally.
125
125
  def finish_output(error = nil)
126
+ return if self.closed?
127
+
126
128
  trailer = @output&.trailer
127
129
 
128
130
  @output = nil
@@ -152,14 +154,14 @@ module Async
152
154
  def closed(error)
153
155
  super
154
156
 
155
- if @input
156
- @input.close_write(error)
157
+ if input = @input
157
158
  @input = nil
159
+ input.close_write(error)
158
160
  end
159
161
 
160
- if @output
161
- @output.stop(error)
162
+ if output = @output
162
163
  @output = nil
164
+ output.stop(error)
163
165
  end
164
166
 
165
167
  if pool = @pool and @connection
@@ -41,6 +41,10 @@ module Async
41
41
  def remote_address= value
42
42
  @remote_address = value
43
43
  end
44
+
45
+ def inspect
46
+ "#<#{self.class}:0x#{self.object_id.to_s(16)} method=#{method} path=#{path} version=#{version}>"
47
+ end
44
48
  end
45
49
  end
46
50
  end
@@ -33,6 +33,10 @@ module Async
33
33
  def remote_address= value
34
34
  @remote_address = value
35
35
  end
36
+
37
+ def inspect
38
+ "#<#{self.class}:0x#{self.object_id.to_s(16)} status=#{status}>"
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module HTTP
8
- VERSION = "0.77.0"
8
+ VERSION = "0.79.0"
9
9
  end
10
10
  end
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.77.0
4
+ version: 0.79.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-19 00:00:00.000000000 Z
61
+ date: 2024-09-24 00:00:00.000000000 Z
62
62
  dependencies:
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: async
@@ -136,28 +136,28 @@ dependencies:
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: '0.25'
139
+ version: '0.27'
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.25'
146
+ version: '0.27'
147
147
  - !ruby/object:Gem::Dependency
148
148
  name: protocol-http2
149
149
  requirement: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: '0.18'
153
+ version: '0.19'
154
154
  type: :runtime
155
155
  prerelease: false
156
156
  version_requirements: !ruby/object:Gem::Requirement
157
157
  requirements:
158
158
  - - "~>"
159
159
  - !ruby/object:Gem::Version
160
- version: '0.18'
160
+ version: '0.19'
161
161
  - !ruby/object:Gem::Dependency
162
162
  name: traces
163
163
  requirement: !ruby/object:Gem::Requirement
@@ -182,7 +182,6 @@ 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
186
185
  - lib/async/http/body/hijack.rb
187
186
  - lib/async/http/body/pipe.rb
188
187
  - lib/async/http/body/writable.rb
@@ -198,6 +197,7 @@ files:
198
197
  - lib/async/http/protocol/http1.rb
199
198
  - lib/async/http/protocol/http1/client.rb
200
199
  - lib/async/http/protocol/http1/connection.rb
200
+ - lib/async/http/protocol/http1/finishable.rb
201
201
  - lib/async/http/protocol/http1/request.rb
202
202
  - lib/async/http/protocol/http1/response.rb
203
203
  - lib/async/http/protocol/http1/server.rb
metadata.gz.sig CHANGED
Binary file
@@ -1,56 +0,0 @@
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