async-http 0.52.4 → 0.54.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/bake/async/http/h2spec.rb +1 -1
  3. data/lib/async/http/body/delayed.rb +2 -2
  4. data/lib/async/http/body/hijack.rb +5 -0
  5. data/lib/async/http/body/pipe.rb +15 -7
  6. data/lib/async/http/body/stream.rb +1 -1
  7. data/lib/async/http/client.rb +3 -3
  8. data/lib/async/http/protocol/http1.rb +2 -2
  9. data/lib/async/http/protocol/http1/connection.rb +0 -5
  10. data/lib/async/http/protocol/http1/server.rb +1 -1
  11. data/lib/async/http/protocol/http10.rb +2 -2
  12. data/lib/async/http/protocol/http11.rb +2 -2
  13. data/lib/async/http/protocol/http2.rb +0 -18
  14. data/lib/async/http/protocol/http2/connection.rb +7 -0
  15. data/lib/async/http/protocol/http2/output.rb +1 -1
  16. data/lib/async/http/protocol/http2/request.rb +0 -38
  17. data/lib/async/http/protocol/http2/response.rb +6 -13
  18. data/lib/async/http/protocol/request.rb +0 -4
  19. data/lib/async/http/proxy.rb +24 -8
  20. data/lib/async/http/server.rb +3 -3
  21. data/lib/async/http/version.rb +1 -1
  22. metadata +22 -66
  23. data/.editorconfig +0 -6
  24. data/.github/workflows/development.yml +0 -52
  25. data/.gitignore +0 -15
  26. data/.rspec +0 -3
  27. data/.travis.yml +0 -35
  28. data/README.md +0 -365
  29. data/async-http.gemspec +0 -39
  30. data/bake.rb +0 -0
  31. data/examples/compare/Gemfile +0 -9
  32. data/examples/compare/benchmark.rb +0 -78
  33. data/examples/download/chunked.rb +0 -86
  34. data/examples/fetch/Gemfile +0 -3
  35. data/examples/fetch/Gemfile.lock +0 -74
  36. data/examples/fetch/README.md +0 -3
  37. data/examples/fetch/config.ru +0 -28
  38. data/examples/fetch/public/index.html +0 -23
  39. data/examples/fetch/public/stream.js +0 -56
  40. data/examples/google/search.rb +0 -47
  41. data/examples/licenses/gemspect.rb +0 -71
  42. data/examples/licenses/list.rb +0 -90
  43. data/examples/request.rb +0 -38
  44. data/examples/stream/stop.rb +0 -28
  45. data/examples/trenni/Gemfile +0 -5
  46. data/examples/trenni/streaming.rb +0 -35
  47. data/examples/upload/client.rb +0 -39
  48. data/examples/upload/data.txt +0 -41
  49. data/examples/upload/server.rb +0 -19
  50. data/examples/upload/upload.rb +0 -26
  51. data/gems.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99b5cbef0cb74dedeea089ac42ff1aa5315e1089079f812ff4120e9147ab22ec
4
- data.tar.gz: dda68448abfa958e9a98814f35482bda367990191f9742292512c8f242745fe8
3
+ metadata.gz: 82df1313e46a49ebe4ba6c345c911dcf0c899e59361f09abaedc5f7c7979e61f
4
+ data.tar.gz: 838d76ab9e99d661ac7be82169d6931325e64d22fab02ca93089d5da2ed13b31
5
5
  SHA512:
6
- metadata.gz: 74ddbd13c0e1b3c5bae03ded50129ab73fac1285d480bef0ff8a944c1163651037d6a258f36757b8969f72ea7e63eaf145384b346aa7733f2a51430c85cba642
7
- data.tar.gz: e1036b972800356861167204b1ecfbab6e164dbe51351bd15a2881214d5e437f1cda9bd53a75715bd41c74f91ea6911d438b2ebcc04adceede636d0814e79e1f
6
+ metadata.gz: 0d78d6636a59967ab8de9c76eddad387d6a607c04224563bf6599ab4b637d5fccceffa462d700c7f4d19c4ce1bee6b6f72562dd91cc6f9532802dbe1769f4315
7
+ data.tar.gz: bcdc8bc22dfeb30ed425ad4117c682aca370acfab125a478cba7af03ec83b29eafabd5f8b0194adaf060dbdf03d7c926f348658cd9b89d4b5b12b583e33a9dc6
@@ -29,7 +29,7 @@ def server
29
29
  Async.logger.info(self){"Starting server..."}
30
30
 
31
31
  container.run(count: 1) do
32
- server = Async::HTTP::Server.for(endpoint, Async::HTTP::Protocol::HTTP2, "https") do |request|
32
+ server = Async::HTTP::Server.for(endpoint, protocol: Async::HTTP::Protocol::HTTP2, scheme: "https") do |request|
33
33
  Protocol::HTTP::Response[200, {'content-type' => 'text/plain'}, ["Hello World"]]
34
34
  end
35
35
 
@@ -20,12 +20,12 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require_relative 'wrapper'
23
+ require 'protocol/http/body/wrapper'
24
24
 
25
25
  module Async
26
26
  module HTTP
27
27
  module Body
28
- class Delayed < Async::HTTP::Body::Wrapper
28
+ class Delayed < Protocol::HTTP::Body::Wrapper
29
29
  def initialize(body, delay = 0.01)
30
30
  super(body)
31
31
 
@@ -44,6 +44,11 @@ module Async
44
44
  @stream = nil
45
45
  end
46
46
 
47
+ # We prefer streaming directly as it's the lowest overhead.
48
+ def stream?
49
+ true
50
+ end
51
+
47
52
  def call(stream)
48
53
  return @block.call(stream)
49
54
  end
@@ -20,6 +20,9 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
+ require 'async/io/socket'
24
+ require 'async/io/stream'
25
+
23
26
  require_relative 'writable'
24
27
 
25
28
  module Async
@@ -39,8 +42,8 @@ module Async
39
42
  @reader = nil
40
43
  @writer = nil
41
44
 
42
- task.async(&self.method(:reader))
43
- task.async(&self.method(:writer))
45
+ task.async(transient: true, &self.method(:reader))
46
+ task.async(transient: true, &self.method(:writer))
44
47
  end
45
48
 
46
49
  def to_io
@@ -69,10 +72,9 @@ module Async
69
72
 
70
73
  @head.close_write
71
74
  ensure
72
- @reader = nil
73
75
  @input.close($!)
74
76
 
75
- @head.close if @writer.nil?
77
+ close_head if @writer&.finished?
76
78
  end
77
79
 
78
80
  # Read from the head of the pipe and write to the @output stream.
@@ -86,11 +88,17 @@ module Async
86
88
  @output.write(chunk)
87
89
  end
88
90
  ensure
89
- @writer = nil
90
-
91
91
  @output.close($!)
92
92
 
93
- @head.close if @reader.nil?
93
+ close_head if @reader&.finished?
94
+ end
95
+
96
+ def close_head
97
+ @head.close
98
+
99
+ # Both tasks are done, don't keep references:
100
+ @reader = nil
101
+ @writer = nil
94
102
  end
95
103
  end
96
104
  end
@@ -139,7 +139,7 @@ module Async
139
139
  end
140
140
 
141
141
  # Close the input and output bodies.
142
- def close
142
+ def close(error = nil)
143
143
  self.close_read
144
144
  self.close_write
145
145
  ensure
@@ -25,7 +25,7 @@ require 'async/io/stream'
25
25
 
26
26
  require 'async/pool/controller'
27
27
 
28
- require 'protocol/http/body/streamable'
28
+ require 'protocol/http/body/completable'
29
29
  require 'protocol/http/methods'
30
30
 
31
31
  require_relative 'protocol'
@@ -45,7 +45,7 @@ module Async
45
45
  # @param protocol [Protocol::HTTP1 | Protocol::HTTP2 | Protocol::HTTPS] the protocol to use.
46
46
  # @param scheme [String] The default scheme to set to requests.
47
47
  # @param authority [String] The default authority to set to requests.
48
- def initialize(endpoint, protocol = endpoint.protocol, scheme = endpoint.scheme, authority = endpoint.authority, retries: DEFAULT_RETRIES, connection_limit: DEFAULT_CONNECTION_LIMIT)
48
+ def initialize(endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme, authority: endpoint.authority, retries: DEFAULT_RETRIES, connection_limit: DEFAULT_CONNECTION_LIMIT)
49
49
  @endpoint = endpoint
50
50
  @protocol = protocol
51
51
 
@@ -143,7 +143,7 @@ module Async
143
143
  response = request.call(connection)
144
144
 
145
145
  # The connection won't be released until the body is completely read/released.
146
- ::Protocol::HTTP::Body::Streamable.wrap(response) do
146
+ ::Protocol::HTTP::Body::Completable.wrap(response) do
147
147
  @pool.release(connection)
148
148
  end
149
149
 
@@ -38,13 +38,13 @@ module Async
38
38
  end
39
39
 
40
40
  def self.client(peer)
41
- stream = IO::Stream.new(peer, sync: false)
41
+ stream = IO::Stream.new(peer, sync: true)
42
42
 
43
43
  return HTTP1::Client.new(stream, VERSION)
44
44
  end
45
45
 
46
46
  def self.server(peer)
47
- stream = IO::Stream.new(peer, sync: false)
47
+ stream = IO::Stream.new(peer, sync: true)
48
48
 
49
49
  return HTTP1::Server.new(stream, VERSION)
50
50
  end
@@ -74,11 +74,6 @@ module Async
74
74
  def reusable?
75
75
  @persistent && @stream && !@stream.closed?
76
76
  end
77
-
78
- def close
79
- Async.logger.debug(self) {"Closing connection"}
80
- super
81
- end
82
77
  end
83
78
  end
84
79
  end
@@ -80,7 +80,7 @@ module Async
80
80
  response = nil
81
81
 
82
82
  body.call(stream)
83
- elsif body and request.connect?
83
+ elsif request.connect? and response.success?
84
84
  stream = write_tunnel_body(request.version)
85
85
 
86
86
  # Same as above:
@@ -37,13 +37,13 @@ module Async
37
37
  end
38
38
 
39
39
  def self.client(peer)
40
- stream = IO::Stream.new(peer, sync: false)
40
+ stream = IO::Stream.new(peer, sync: true)
41
41
 
42
42
  return HTTP1::Client.new(stream, VERSION)
43
43
  end
44
44
 
45
45
  def self.server(peer)
46
- stream = IO::Stream.new(peer, sync: false)
46
+ stream = IO::Stream.new(peer, sync: true)
47
47
 
48
48
  return HTTP1::Server.new(stream, VERSION)
49
49
  end
@@ -37,13 +37,13 @@ module Async
37
37
  end
38
38
 
39
39
  def self.client(peer)
40
- stream = IO::Stream.new(peer, sync: false)
40
+ stream = IO::Stream.new(peer, sync: true)
41
41
 
42
42
  return HTTP1::Client.new(stream, VERSION)
43
43
  end
44
44
 
45
45
  def self.server(peer)
46
- stream = IO::Stream.new(peer, sync: false)
46
+ stream = IO::Stream.new(peer, sync: true)
47
47
 
48
48
  return HTTP1::Server.new(stream, VERSION)
49
49
  end
@@ -76,24 +76,6 @@ module Async
76
76
  def self.names
77
77
  ["h2"]
78
78
  end
79
-
80
- module WithPush
81
- CLIENT_SETTINGS = HTTP2::CLIENT_SETTINGS.merge(
82
- ::Protocol::HTTP2::Settings::ENABLE_PUSH => 1,
83
- )
84
-
85
- def self.client(peer, settings = CLIENT_SETTINGS)
86
- HTTP2.client(peer, settings)
87
- end
88
-
89
- def self.server(peer, settings = SERVER_SETTINGS)
90
- HTTP2.server(peer, settings)
91
- end
92
-
93
- def self.names
94
- HTTP2.names
95
- end
96
- end
97
79
  end
98
80
  end
99
81
  end
@@ -107,6 +107,13 @@ module Async
107
107
  end
108
108
  rescue SocketError, IOError, EOFError, Errno::ECONNRESET, Errno::EPIPE, Async::Wrapper::Cancelled
109
109
  # Ignore.
110
+ rescue ::Protocol::HTTP2::GoawayError => error
111
+ # Error is raised if a response is actively reading from the
112
+ # connection. The connection is silently closed if GOAWAY is
113
+ # received outside the request/response cycle.
114
+ if @reader
115
+ self.close(error)
116
+ end
110
117
  ensure
111
118
  # Don't call #close twice.
112
119
  if @reader
@@ -42,7 +42,7 @@ module Async
42
42
  def start(parent: Task.current)
43
43
  raise "Task already started!" if @task
44
44
 
45
- if @body.respond_to?(:call)
45
+ if @body.stream?
46
46
  @task = parent.async(&self.method(:stream))
47
47
  else
48
48
  @task = parent.async(&self.method(:passthrough))
@@ -39,18 +39,6 @@ module Async
39
39
 
40
40
  attr :request
41
41
 
42
- # Create a fake request on the server, with the given headers.
43
- def create_push_promise_stream(headers)
44
- stream = @connection.create_push_promise_stream(&Stream.method(:create))
45
-
46
- stream.headers = ::Protocol::HTTP::Headers.new
47
-
48
- # This will ultimately enqueue the request to be processed by the server:
49
- stream.receive_initial_headers(headers, false)
50
-
51
- return stream
52
- end
53
-
54
42
  def receive_initial_headers(headers, end_stream)
55
43
  headers.each do |key, value|
56
44
  if key == SCHEME
@@ -133,32 +121,6 @@ module Async
133
121
  false
134
122
  end
135
123
 
136
- def push?
137
- @stream.connection.enable_push?
138
- end
139
-
140
- # @return [Stream] the promised stream, on which to send data.
141
- def push(path, headers = nil, scheme = @scheme, authority = @authority)
142
- raise ArgumentError, "Missing scheme!" unless scheme
143
- raise ArgumentError, "Missing authority!" unless authority
144
-
145
- push_headers = [
146
- [SCHEME, scheme],
147
- [METHOD, ::Protocol::HTTP::Methods::GET],
148
- [PATH, path],
149
- [AUTHORITY, authority]
150
- ]
151
-
152
- if headers
153
- push_headers = Headers::Merged.new(
154
- push_headers,
155
- headers
156
- )
157
- end
158
-
159
- @stream.send_push_promise(push_headers)
160
- end
161
-
162
124
  NO_RESPONSE = [
163
125
  [STATUS, '500'],
164
126
  ]
@@ -50,13 +50,7 @@ module Async
50
50
  end
51
51
 
52
52
  def accept_push_promise_stream(promised_stream_id, headers)
53
- stream = @connection.accept_push_promise_stream(promised_stream_id, &Stream.method(:create))
54
-
55
- stream.response.build_request(headers)
56
-
57
- @response.promises.enqueue(stream.response)
58
-
59
- return stream
53
+ raise ProtocolError, "Cannot accept push promise stream!"
60
54
  end
61
55
 
62
56
  # This should be invoked from the background reader, and notifies the task waiting for the headers that we are done.
@@ -113,7 +107,6 @@ module Async
113
107
  super
114
108
 
115
109
  if @response
116
- @response.promises.enqueue nil
117
110
  @response = nil
118
111
  end
119
112
 
@@ -128,7 +121,6 @@ module Async
128
121
 
129
122
  @stream = stream
130
123
  @request = nil
131
- @promises = nil
132
124
  end
133
125
 
134
126
  attr :stream
@@ -150,10 +142,6 @@ module Async
150
142
  !!@status
151
143
  end
152
144
 
153
- def promises
154
- @promises ||= Async::Queue.new
155
- end
156
-
157
145
  def build_request(headers)
158
146
  request = ::Protocol::HTTP::Request.new
159
147
  request.headers = ::Protocol::HTTP::Headers.new
@@ -215,6 +203,11 @@ module Async
215
203
  if request.body.nil?
216
204
  @stream.send_headers(nil, headers, ::Protocol::HTTP2::END_STREAM)
217
205
  else
206
+ if length = request.body.length
207
+ # This puts it at the end of the pseudo-headers:
208
+ pseudo_headers << [CONTENT_LENGTH, length]
209
+ end
210
+
218
211
  # This function informs the headers object that any subsequent headers are going to be trailers. Therefore, it must be called *before* sending the headers, to avoid any race conditions.
219
212
  trailers = request.headers.trailers!
220
213
 
@@ -42,10 +42,6 @@ module Async
42
42
  false
43
43
  end
44
44
 
45
- def push?
46
- false
47
- end
48
-
49
45
  def peer
50
46
  if connection = self.connection
51
47
  connection.peer
@@ -30,6 +30,15 @@ module Async
30
30
  # Wraps a client, address and headers required to initiate a connectio to a remote host using the CONNECT verb.
31
31
  # Behaves like a TCP endpoint for the purposes of connecting to a remote host.
32
32
  class Proxy
33
+ class ConnectFailure < StandardError
34
+ def initialize(response)
35
+ super "Failed to connect: #{response.status}"
36
+ @response = response
37
+ end
38
+
39
+ attr :response
40
+ end
41
+
33
42
  module Client
34
43
  def proxy(endpoint, headers = nil)
35
44
  Proxy.new(self, endpoint.authority(false), headers)
@@ -92,14 +101,21 @@ module Async
92
101
 
93
102
  response = @client.connect(@address.to_s, @headers, input)
94
103
 
95
- pipe = Body::Pipe.new(response.body, input)
96
-
97
- return pipe.to_io unless block_given?
98
-
99
- begin
100
- yield pipe.to_io
101
- ensure
102
- pipe.close
104
+ if response.success?
105
+ pipe = Body::Pipe.new(response.body, input)
106
+
107
+ return pipe.to_io unless block_given?
108
+
109
+ begin
110
+ yield pipe.to_io
111
+ ensure
112
+ pipe.close
113
+ end
114
+ else
115
+ # This ensures we don't leave a response dangling:
116
+ response.close
117
+
118
+ raise ConnectFailure, response
103
119
  end
104
120
  end
105
121
 
@@ -29,11 +29,11 @@ require 'protocol/http/middleware'
29
29
  module Async
30
30
  module HTTP
31
31
  class Server < ::Protocol::HTTP::Middleware
32
- def self.for(*arguments, &block)
33
- self.new(block, *arguments)
32
+ def self.for(*arguments, **options, &block)
33
+ self.new(block, *arguments, **options)
34
34
  end
35
35
 
36
- def initialize(app, endpoint, protocol = endpoint.protocol, scheme = endpoint.scheme)
36
+ def initialize(app, endpoint, protocol: endpoint.protocol, scheme: endpoint.scheme)
37
37
  super(app)
38
38
 
39
39
  @endpoint = endpoint
@@ -22,6 +22,6 @@
22
22
 
23
23
  module Async
24
24
  module HTTP
25
- VERSION = "0.52.4"
25
+ VERSION = "0.54.1"
26
26
  end
27
27
  end