async-http 0.75.0 → 0.77.0

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