async-http 0.75.0 → 0.76.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/http/body/hijack.rb +1 -1
- data/lib/async/http/body/pipe.rb +7 -3
- data/lib/async/http/body/writable.rb +2 -93
- data/lib/async/http/protocol/http1/server.rb +14 -2
- data/lib/async/http/protocol/http2/input.rb +2 -2
- data/lib/async/http/protocol/http2/output.rb +28 -13
- data/lib/async/http/protocol/http2/stream.rb +3 -3
- data/lib/async/http/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -6
- metadata.gz.sig +0 -0
- data/lib/async/http/body/delayed.rb +0 -32
- data/lib/async/http/body/slowloris.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd45f82b4d28a3e9a72bbdd7eaec1877b69cfce59a3d67b49a09b99b59b42fa4
|
4
|
+
data.tar.gz: 05ceb7e93478b63e53bc9a16b829cc2c34efdc22953143192a5a60b310aa04b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b293794ba1fb14494a7187a0c1255674ea771df8cc587c6fa031abca25b8bf803f32ba4889d772d53fb8f4dce0705938f1013ab72c22353b4e09c7cc54f4467
|
7
|
+
data.tar.gz: b0c407ee2c817bd8436bff520ae09744f44e35ba79bd992e76a783be52911fdedb0b397ab12c3cd1ffb00b3042bfc5d429f11b5252e16edbc2bab178172d747d
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/async/http/body/pipe.rb
CHANGED
@@ -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
|
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.
|
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/
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
54
|
-
|
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
|
64
|
-
|
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(
|
75
|
-
rescue
|
76
|
-
|
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
|
-
|
98
|
+
rescue => error
|
99
|
+
raise
|
91
100
|
ensure
|
92
|
-
|
93
|
-
|
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.
|
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.
|
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.
|
152
|
+
@input.close_write(error)
|
153
153
|
@input = nil
|
154
154
|
end
|
155
155
|
|
data/lib/async/http/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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
|