async-http 0.75.0 → 0.76.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 946e45fa80db4dc1a3416f91a6533d58e7e4c5b8e7db9a687b922e9161f84dcf
4
- data.tar.gz: 7810dfb04b6c120e91660c4cabec884ef531bb4b4078c149558a6190314f88cb
3
+ metadata.gz: bd45f82b4d28a3e9a72bbdd7eaec1877b69cfce59a3d67b49a09b99b59b42fa4
4
+ data.tar.gz: 05ceb7e93478b63e53bc9a16b829cc2c34efdc22953143192a5a60b310aa04b1
5
5
  SHA512:
6
- metadata.gz: 456325793d94251a8b8b117000361e512c23aad9ce03dd2df6c387a35405ac83a7cae1b0de270bf0264485ab98ff5fff3778d720845fe26753950bedd1066b38
7
- data.tar.gz: de2cb3749808740c03bd0d0d6cfe5facb3282d26f8465ddeb734158389cfdd70b2bf5610a01bf1d4f0af44fdc1d6c15249f912942038778e9f38217e96ad9c84
6
+ metadata.gz: 8b293794ba1fb14494a7187a0c1255674ea771df8cc587c6fa031abca25b8bf803f32ba4889d772d53fb8f4dce0705938f1013ab72c22353b4e09c7cc54f4467
7
+ data.tar.gz: b0c407ee2c817bd8436bff520ae09744f44e35ba79bd992e76a783be52911fdedb0b397ab12c3cd1ffb00b3042bfc5d429f11b5252e16edbc2bab178172d747d
checksums.yaml.gz.sig CHANGED
Binary file
@@ -36,7 +36,7 @@ module Async
36
36
  end
37
37
 
38
38
  def call(stream)
39
- return @block.call(stream)
39
+ @block.call(stream)
40
40
  end
41
41
 
42
42
  attr :input
@@ -17,7 +17,7 @@ module Async
17
17
 
18
18
  head, tail = ::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)
19
19
 
20
- @head = ::IO::Stream::Buffered.new(head)
20
+ @head = ::IO::Stream(head)
21
21
  @tail = tail
22
22
 
23
23
  @reader = nil
@@ -52,8 +52,10 @@ module Async
52
52
  end
53
53
 
54
54
  @head.close_write
55
+ rescue => error
56
+ raise
55
57
  ensure
56
- @input.close($!)
58
+ @input.close(error)
57
59
 
58
60
  close_head if @writer&.finished?
59
61
  end
@@ -68,8 +70,10 @@ module Async
68
70
  while chunk = @head.read_partial
69
71
  @output.write(chunk)
70
72
  end
73
+ rescue => error
74
+ raise
71
75
  ensure
72
- @output.close($!)
76
+ @output.close_write(error)
73
77
 
74
78
  close_head if @reader&.finished?
75
79
  end
@@ -3,104 +3,13 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2023, by Samuel Williams.
5
5
 
6
- require 'protocol/http/body/readable'
6
+ require 'protocol/http/body/writable'
7
7
  require 'async/queue'
8
8
 
9
9
  module Async
10
10
  module HTTP
11
11
  module Body
12
- include ::Protocol::HTTP::Body
13
-
14
- # A dynamic body which you can write to and read from.
15
- class Writable < Readable
16
- class Closed < StandardError
17
- end
18
-
19
- # @param [Integer] length The length of the response body if known.
20
- # @param [Async::Queue] queue Specify a different queue implementation, e.g. `Async::LimitedQueue.new(8)` to enable back-pressure streaming.
21
- def initialize(length = nil, queue: Async::Queue.new)
22
- @queue = queue
23
-
24
- @length = length
25
-
26
- @count = 0
27
-
28
- @finished = false
29
-
30
- @closed = false
31
- @error = nil
32
- end
33
-
34
- def length
35
- @length
36
- end
37
-
38
- # Stop generating output; cause the next call to write to fail with the given error.
39
- def close(error = nil)
40
- unless @closed
41
- @queue.enqueue(nil)
42
-
43
- @closed = true
44
- @error = error
45
- end
46
-
47
- super
48
- end
49
-
50
- def closed?
51
- @closed
52
- end
53
-
54
- def ready?
55
- !@queue.empty?
56
- end
57
-
58
- # Has the producer called #finish and has the reader consumed the nil token?
59
- def empty?
60
- @finished
61
- end
62
-
63
- # Read the next available chunk.
64
- def read
65
- return if @finished
66
-
67
- unless chunk = @queue.dequeue
68
- @finished = true
69
- end
70
-
71
- return chunk
72
- end
73
-
74
- # Write a single chunk to the body. Signal completion by calling `#finish`.
75
- def write(chunk)
76
- # If the reader breaks, the writer will break.
77
- # The inverse of this is less obvious (*)
78
- if @closed
79
- raise(@error || Closed)
80
- end
81
-
82
- @count += 1
83
- @queue.enqueue(chunk)
84
- end
85
-
86
- alias << write
87
-
88
- def inspect
89
- "\#<#{self.class} #{@count} chunks written, #{status}>"
90
- end
91
-
92
- private
93
-
94
- def status
95
- if @finished
96
- 'finished'
97
- elsif @closed
98
- 'closing'
99
- else
100
- 'waiting'
101
- end
102
- end
103
- end
12
+ Writable = ::Protocol::HTTP::Body::Writable
104
13
  end
105
14
  end
106
15
  end
@@ -68,11 +68,23 @@ module Async
68
68
  stream = write_upgrade_body(protocol)
69
69
 
70
70
  # At this point, the request body is hijacked, so we don't want to call #finish below.
71
- request = nil unless request.body
71
+ request = nil
72
72
  response = nil
73
73
 
74
74
  # We must return here as no further request processing can be done:
75
75
  return body.call(stream)
76
+ elsif response.status == 101
77
+ # 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.
78
+ write_response(@version, response.status, response.headers)
79
+
80
+ stream = write_tunnel_body(request.version)
81
+
82
+ # Same as above:
83
+ request = nil
84
+ response = nil
85
+
86
+ # We must return here as no further request processing can be done:
87
+ return body&.call(stream)
76
88
  else
77
89
  write_response(@version, response.status, response.headers)
78
90
 
@@ -80,7 +92,7 @@ module Async
80
92
  stream = write_tunnel_body(request.version)
81
93
 
82
94
  # Same as above:
83
- request = nil unless request.body
95
+ request = nil
84
96
  response = nil
85
97
 
86
98
  # We must return here as no further request processing can be done:
@@ -3,14 +3,14 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2020-2023, 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
 
@@ -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.
@@ -59,7 +59,7 @@ module Async
59
59
 
60
60
  # TODO this might need to be in an ensure block:
61
61
  if @input and frame.end_stream?
62
- @input.close($!)
62
+ @input.close_write
63
63
  @input = nil
64
64
  end
65
65
  rescue ::Protocol::HTTP2::HeaderError => error
@@ -98,7 +98,7 @@ module Async
98
98
  end
99
99
 
100
100
  if frame.end_stream?
101
- @input.close
101
+ @input.close_write
102
102
  @input = nil
103
103
  end
104
104
  end
@@ -149,7 +149,7 @@ module Async
149
149
  super
150
150
 
151
151
  if @input
152
- @input.close(error)
152
+ @input.close_write(error)
153
153
  @input = nil
154
154
  end
155
155
 
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module HTTP
8
- VERSION = "0.75.0"
8
+ VERSION = "0.76.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.75.0
4
+ version: 0.76.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-04 00:00:00.000000000 Z
61
+ date: 2024-09-10 00:00:00.000000000 Z
62
62
  dependencies:
63
63
  - !ruby/object:Gem::Dependency
64
64
  name: async
@@ -122,14 +122,14 @@ dependencies:
122
122
  requirements:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
- version: '0.30'
125
+ version: '0.34'
126
126
  type: :runtime
127
127
  prerelease: false
128
128
  version_requirements: !ruby/object:Gem::Requirement
129
129
  requirements:
130
130
  - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: '0.30'
132
+ version: '0.34'
133
133
  - !ruby/object:Gem::Dependency
134
134
  name: protocol-http1
135
135
  requirement: !ruby/object:Gem::Requirement
@@ -182,10 +182,8 @@ 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/delayed.rb
186
185
  - lib/async/http/body/hijack.rb
187
186
  - lib/async/http/body/pipe.rb
188
- - lib/async/http/body/slowloris.rb
189
187
  - lib/async/http/body/writable.rb
190
188
  - lib/async/http/client.rb
191
189
  - lib/async/http/endpoint.rb
metadata.gz.sig CHANGED
Binary file
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2018-2023, by Samuel Williams.
5
- # Copyright, 2020, by Bruno Sutic.
6
- # Copyright, 2023, by Thomas Morgan.
7
-
8
- require 'protocol/http/body/wrapper'
9
-
10
- module Async
11
- module HTTP
12
- module Body
13
- class Delayed < ::Protocol::HTTP::Body::Wrapper
14
- def initialize(body, delay = 0.01)
15
- super(body)
16
-
17
- @delay = delay
18
- end
19
-
20
- def ready?
21
- false
22
- end
23
-
24
- def read
25
- Async::Task.current.sleep(@delay)
26
-
27
- return super
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
5
-
6
- require_relative 'writable'
7
-
8
- require 'async/clock'
9
-
10
- module Async
11
- module HTTP
12
- module Body
13
- # A dynamic body which you can write to and read from.
14
- class Slowloris < Writable
15
- class ThroughputError < StandardError
16
- def initialize(throughput, minimum_throughput, time_since_last_write)
17
- super("Slow write: #{throughput.round(1)}bytes/s less than required #{minimum_throughput.round}bytes/s.")
18
- end
19
- end
20
-
21
- # In order for this implementation to work correctly, you need to use a LimitedQueue.
22
- # @param minimum_throughput [Integer] the minimum bytes per second otherwise this body will be forcefully closed.
23
- def initialize(*arguments, minimum_throughput: 1024, **options)
24
- super(*arguments, **options)
25
-
26
- @minimum_throughput = minimum_throughput
27
-
28
- @last_write_at = nil
29
- @last_chunk_size = nil
30
- end
31
-
32
- attr :minimum_throughput
33
-
34
- # If #read is called regularly to maintain throughput, that is good. If #read is not called, that is a problem. Throughput is dependent on data being available, from #write, so it doesn't seem particularly problimatic to do this check in #write.
35
- def write(chunk)
36
- if @last_chunk_size
37
- time_since_last_write = Async::Clock.now - @last_write_at
38
- throughput = @last_chunk_size / time_since_last_write
39
-
40
- if throughput < @minimum_throughput
41
- error = ThroughputError.new(throughput, @minimum_throughput, time_since_last_write)
42
-
43
- self.close(error)
44
- end
45
- end
46
-
47
- super.tap do
48
- @last_write_at = Async::Clock.now
49
- @last_chunk_size = chunk&.bytesize
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end