async-http 0.75.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 (52) 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 +5 -5
  7. data/lib/async/http/body/pipe.rb +8 -4
  8. data/lib/async/http/body/writable.rb +4 -95
  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 +33 -13
  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 +3 -3
  29. data/lib/async/http/protocol/http2/output.rb +30 -15
  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 +15 -7
  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 -8
  50. metadata.gz.sig +0 -0
  51. data/lib/async/http/body/delayed.rb +0 -32
  52. data/lib/async/http/body/slowloris.rb +0 -55
@@ -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?
@@ -68,45 +76,57 @@ module Async
68
76
  stream = write_upgrade_body(protocol)
69
77
 
70
78
  # At this point, the request body is hijacked, so we don't want to call #finish below.
71
- request = nil unless request.body
79
+ request = nil
72
80
  response = nil
73
81
 
74
82
  # We must return here as no further request processing can be done:
75
83
  return body.call(stream)
84
+ elsif response.status == 101
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.
86
+ write_response(@version, response.status, response.headers)
87
+
88
+ stream = write_tunnel_body(version)
89
+
90
+ # Same as above:
91
+ request = nil
92
+ response = nil
93
+
94
+ # We must return here as no further request processing can be done:
95
+ return body&.call(stream)
76
96
  else
77
97
  write_response(@version, response.status, response.headers)
78
98
 
79
99
  if request.connect? and response.success?
80
- stream = write_tunnel_body(request.version)
100
+ stream = write_tunnel_body(version)
81
101
 
82
102
  # Same as above:
83
- request = nil unless request.body
103
+ request = nil
84
104
  response = nil
85
105
 
86
106
  # We must return here as no further request processing can be done:
87
107
  return body.call(stream)
88
108
  else
89
109
  head = request.head?
90
- version = request.version
91
110
 
92
111
  # Same as above:
93
- request = nil unless request.body
112
+ request = nil
94
113
  response = nil
95
114
 
96
115
  write_body(version, body, head, trailer)
97
116
  end
98
117
  end
99
118
 
100
- # We are done with the body, you shouldn't need to call close on it:
119
+ # We are done with the body:
101
120
  body = nil
102
121
  else
103
122
  # If the request failed to generate a response, it was an internal server error:
104
123
  write_response(@version, 500, {})
105
- write_body(request.version, nil)
124
+ write_body(version, nil)
125
+
126
+ request&.finish
106
127
  end
107
128
 
108
- # Gracefully finish reading the request body if it was not already done so.
109
- request&.each{}
129
+ finishable&.wait
110
130
 
111
131
  # This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
112
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,16 +1,16 @@
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_relative '../../body/writable'
6
+ require "protocol/http/body/writable"
7
7
 
8
8
  module Async
9
9
  module HTTP
10
10
  module Protocol
11
11
  module HTTP2
12
12
  # A writable body which requests window updates when data is read from it.
13
- class Input < Body::Writable
13
+ class Input < ::Protocol::HTTP::Body::Writable
14
14
  def initialize(stream, length)
15
15
  super(length)
16
16
 
@@ -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
@@ -50,18 +50,25 @@ module Async
50
50
  end
51
51
  end
52
52
 
53
- # This method should only be called from within the context of the output task.
54
- def close(error = nil)
55
- if @stream
56
- @stream.finish_output(error)
53
+ def close_write(error = nil)
54
+ if stream = @stream
57
55
  @stream = nil
56
+ stream.finish_output(error)
58
57
  end
59
58
  end
60
59
 
60
+ # This method should only be called from within the context of the output task.
61
+ def close(error = nil)
62
+ close_write(error)
63
+ stop(error)
64
+ end
65
+
61
66
  # This method should only be called from within the context of the HTTP/2 stream.
62
67
  def stop(error)
63
- @task&.stop
64
- @task = nil
68
+ if task = @task
69
+ @task = nil
70
+ task.stop(error)
71
+ end
65
72
  end
66
73
 
67
74
  private
@@ -70,10 +77,12 @@ module Async
70
77
  task.annotate("Streaming #{@body} to #{@stream}.")
71
78
 
72
79
  input = @stream.wait_for_input
80
+ stream = ::Protocol::HTTP::Body::Stream.new(input, self)
73
81
 
74
- @body.call(::Protocol::HTTP::Body::Stream.new(input, self))
75
- rescue Async::Stop
76
- # Ignore.
82
+ @body.call(stream)
83
+ rescue => error
84
+ self.close(error)
85
+ raise
77
86
  end
78
87
 
79
88
  # Reads chunks from the given body and writes them to the stream as fast as possible.
@@ -86,11 +95,17 @@ module Async
86
95
  # chunk.clear unless chunk.frozen?
87
96
  # GC.start
88
97
  end
89
-
90
- self.close
98
+ rescue => error
99
+ raise
91
100
  ensure
92
- @body&.close($!)
93
- @body = nil
101
+ # Ensure the body we are reading from is fully closed:
102
+ if body = @body
103
+ @body = nil
104
+ body.close(error)
105
+ end
106
+
107
+ # Ensure the output of this body is closed:
108
+ self.close_write(error)
94
109
  end
95
110
 
96
111
  # Send `maximum_size` bytes of data using the specified `stream`. If the buffer has no more chunks, `END_STREAM` will be sent on the final chunk.
@@ -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}!"
@@ -59,7 +63,7 @@ module Async
59
63
 
60
64
  # TODO this might need to be in an ensure block:
61
65
  if @input and frame.end_stream?
62
- @input.close($!)
66
+ @input.close_write
63
67
  @input = nil
64
68
  end
65
69
  rescue ::Protocol::HTTP2::HeaderError => error
@@ -98,7 +102,7 @@ module Async
98
102
  end
99
103
 
100
104
  if frame.end_stream?
101
- @input.close
105
+ @input.close_write
102
106
  @input = nil
103
107
  end
104
108
  end
@@ -149,7 +153,7 @@ module Async
149
153
  super
150
154
 
151
155
  if @input
152
- @input.close(error)
156
+ @input.close_write(error)
153
157
  @input = nil
154
158
  end
155
159
 
@@ -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