async-http 0.52.4 → 0.54.1

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