async-http 0.47.0 → 0.48.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
- data/async-http.gemspec +2 -2
- data/lib/async/http/body/pipe.rb +100 -0
- data/lib/async/http/body/stream.rb +21 -0
- data/lib/async/http/endpoint.rb +4 -3
- data/lib/async/http/pool.rb +10 -10
- data/lib/async/http/protocol/http1/client.rb +6 -0
- data/lib/async/http/protocol/http1/server.rb +14 -9
- data/lib/async/http/proxy.rb +70 -0
- data/lib/async/http/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd79f3f1a3f1c5c5283c9e851433bce5833c18f33b231623fb1e414de078282f
|
4
|
+
data.tar.gz: bc27cc3ed8568cdd884e09c0889d8d78c3a879bf0117c94c9845f8ea2bd4e5d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5efa88bd4fb17b26b36b434879ff1e2757fdee205d08ee9b5f6a111d867ace9fd2d06ade0b6f895445fc9d5c09057b047d474d80d70f79022644a2af4b3fba0e
|
7
|
+
data.tar.gz: cd4863983ac3392d88e47c7d5244a5968ea90caa5b906d7335a2ccc42a70f9fd2348bc48a77e9b4e34dad736c83477af39759c6a14d08aff69d1d27775017828
|
data/async-http.gemspec
CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
19
|
spec.add_dependency("async", "~> 1.19")
|
20
|
-
spec.add_dependency("async-io", "~> 1.
|
20
|
+
spec.add_dependency("async-io", "~> 1.25")
|
21
21
|
|
22
22
|
spec.add_dependency("protocol-http", "~> 0.12.0")
|
23
|
-
spec.add_dependency("protocol-http1", "~> 0.
|
23
|
+
spec.add_dependency("protocol-http1", "~> 0.9.0")
|
24
24
|
spec.add_dependency("protocol-http2", "~> 0.9.0")
|
25
25
|
|
26
26
|
# spec.add_dependency("openssl")
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'writable'
|
24
|
+
|
25
|
+
require 'forwardable'
|
26
|
+
|
27
|
+
module Async
|
28
|
+
module HTTP
|
29
|
+
module Body
|
30
|
+
class Pipe
|
31
|
+
extend Forwardable
|
32
|
+
|
33
|
+
def initialize(input, output = Writable.new, task: Task.current)
|
34
|
+
@input = input
|
35
|
+
@output = output
|
36
|
+
|
37
|
+
head, tail = IO::Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM)
|
38
|
+
|
39
|
+
@head = Async::IO::Stream.new(head)
|
40
|
+
@tail = tail
|
41
|
+
|
42
|
+
@reader = nil
|
43
|
+
@writer = nil
|
44
|
+
|
45
|
+
task.async(&self.method(:reader))
|
46
|
+
task.async(&self.method(:writer))
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_io
|
50
|
+
@tail
|
51
|
+
end
|
52
|
+
|
53
|
+
def close
|
54
|
+
@reader&.stop
|
55
|
+
@writer&.stop
|
56
|
+
|
57
|
+
@tail.close
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Read from the @input stream and write to the head of the pipe.
|
63
|
+
def reader(task)
|
64
|
+
@reader = task
|
65
|
+
|
66
|
+
task.annotate "pipe reader"
|
67
|
+
|
68
|
+
while chunk = @input.read
|
69
|
+
@head.write(chunk)
|
70
|
+
@head.flush
|
71
|
+
end
|
72
|
+
|
73
|
+
@head.close_write
|
74
|
+
ensure
|
75
|
+
@reader = nil
|
76
|
+
@input.close($!)
|
77
|
+
|
78
|
+
@head.close if @writer.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Read from the head of the pipe and write to the @output stream.
|
82
|
+
# If the @tail is closed, this will cause chunk to be nil, which in turn will call `@output.close` and `@head.close`
|
83
|
+
def writer(task)
|
84
|
+
@writer = task
|
85
|
+
|
86
|
+
task.annotate "pipe writer"
|
87
|
+
|
88
|
+
while chunk = @head.read_partial
|
89
|
+
@output.write(chunk)
|
90
|
+
end
|
91
|
+
ensure
|
92
|
+
@writer = nil
|
93
|
+
@output.close($!)
|
94
|
+
|
95
|
+
@head.close if @reader.nil?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -74,10 +74,31 @@ module Async
|
|
74
74
|
return buffer
|
75
75
|
end
|
76
76
|
|
77
|
+
# Read at most `size` bytes from the stream. Will avoid reading from the underlying stream if possible.
|
78
|
+
def read_partial(size = nil)
|
79
|
+
if @buffer
|
80
|
+
buffer = @buffer
|
81
|
+
@buffer = nil
|
82
|
+
else
|
83
|
+
buffer = read_next
|
84
|
+
end
|
85
|
+
|
86
|
+
if buffer and size
|
87
|
+
if buffer.bytesize > size
|
88
|
+
@buffer = buffer.byteslice(size, buffer.bytesize)
|
89
|
+
buffer = buffer.byteslice(0, size)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
return buffer
|
94
|
+
end
|
95
|
+
|
77
96
|
def read_nonblock(length, buffer = nil)
|
78
97
|
@buffer ||= read_next
|
79
98
|
chunk = nil
|
80
99
|
|
100
|
+
return nil if @buffer.nil?
|
101
|
+
|
81
102
|
if @buffer.bytesize > length
|
82
103
|
chunk = @buffer.byteslice(0, length)
|
83
104
|
@buffer = @buffer.byteslice(length, @buffer.bytesize)
|
data/lib/async/http/endpoint.rb
CHANGED
@@ -29,10 +29,10 @@ module Async
|
|
29
29
|
module HTTP
|
30
30
|
# Represents a way to connect to a remote HTTP server.
|
31
31
|
class Endpoint < Async::IO::Endpoint
|
32
|
-
def self.parse(string, **options)
|
32
|
+
def self.parse(string, endpoint = nil, **options)
|
33
33
|
url = URI.parse(string).normalize
|
34
34
|
|
35
|
-
return self.new(url,
|
35
|
+
return self.new(url, endpoint, **options)
|
36
36
|
end
|
37
37
|
|
38
38
|
# @option scheme [String] the scheme to use, overrides the URL scheme.
|
@@ -46,7 +46,8 @@ module Async
|
|
46
46
|
raise ArgumentError, "URL must be absolute (include scheme, host): #{url}" unless url.absolute?
|
47
47
|
|
48
48
|
@url = url
|
49
|
-
|
49
|
+
|
50
|
+
@endpoint = self.build_endpoint(endpoint)
|
50
51
|
end
|
51
52
|
|
52
53
|
def to_url
|
data/lib/async/http/pool.rb
CHANGED
@@ -43,13 +43,14 @@ module Async
|
|
43
43
|
@available = Async::Notification.new
|
44
44
|
|
45
45
|
@limit = limit
|
46
|
-
@active = 0
|
47
46
|
|
48
47
|
@constructor = block
|
49
48
|
end
|
50
49
|
|
51
50
|
# The number of allocated resources.
|
52
|
-
|
51
|
+
def active
|
52
|
+
@resources.count
|
53
|
+
end
|
53
54
|
|
54
55
|
# Whether there are resources which are currently in use.
|
55
56
|
def busy?
|
@@ -60,6 +61,11 @@ module Async
|
|
60
61
|
return false
|
61
62
|
end
|
62
63
|
|
64
|
+
# Wait until a pool resource has been freed.
|
65
|
+
def wait
|
66
|
+
@available.wait
|
67
|
+
end
|
68
|
+
|
63
69
|
# All allocated resources.
|
64
70
|
attr :resources
|
65
71
|
|
@@ -92,8 +98,6 @@ module Async
|
|
92
98
|
def close
|
93
99
|
@resources.each_key(&:close)
|
94
100
|
@resources.clear
|
95
|
-
|
96
|
-
@active = 0
|
97
101
|
end
|
98
102
|
|
99
103
|
def to_s
|
@@ -121,8 +125,6 @@ module Async
|
|
121
125
|
|
122
126
|
@resources.delete(resource)
|
123
127
|
|
124
|
-
@active -= 1
|
125
|
-
|
126
128
|
resource.close
|
127
129
|
|
128
130
|
@available.signal
|
@@ -149,7 +151,7 @@ module Async
|
|
149
151
|
end
|
150
152
|
|
151
153
|
def available_resource
|
152
|
-
# This is a linear search... not
|
154
|
+
# TODO This is a linear search... not ideal, but simple for now.
|
153
155
|
@resources.each do |resource, count|
|
154
156
|
if count < resource.multiplex
|
155
157
|
# We want to use this resource... but is it connected?
|
@@ -163,11 +165,9 @@ module Async
|
|
163
165
|
end
|
164
166
|
end
|
165
167
|
|
166
|
-
if !@limit or
|
168
|
+
if !@limit or self.active < @limit
|
167
169
|
Async.logger.debug(self) {"No resources resources, allocating new one..."}
|
168
170
|
|
169
|
-
@active += 1
|
170
|
-
|
171
171
|
return create
|
172
172
|
end
|
173
173
|
|
@@ -48,6 +48,12 @@ module Async
|
|
48
48
|
# If this fails, this connection will be closed.
|
49
49
|
write_upgrade_body(protocol, body)
|
50
50
|
end
|
51
|
+
elsif request.connect?
|
52
|
+
task.async do |subtask|
|
53
|
+
subtask.annotate("Tunnelling body.")
|
54
|
+
|
55
|
+
write_tunnel_body(@version, body)
|
56
|
+
end
|
51
57
|
else
|
52
58
|
task.async do |subtask|
|
53
59
|
subtask.annotate("Streaming body.")
|
@@ -37,7 +37,7 @@ module Async
|
|
37
37
|
# Read an incoming request:
|
38
38
|
return unless request = Request.read(self)
|
39
39
|
|
40
|
-
unless persistent?(request.version, request.headers)
|
40
|
+
unless persistent?(request.version, request.method, request.headers)
|
41
41
|
@persistent = false
|
42
42
|
end
|
43
43
|
|
@@ -58,14 +58,6 @@ module Async
|
|
58
58
|
return if @stream.nil? or @stream.closed?
|
59
59
|
|
60
60
|
if response
|
61
|
-
# Try to avoid holding on to request, to minimse GC overhead:
|
62
|
-
head = request.head?
|
63
|
-
|
64
|
-
unless request.body?
|
65
|
-
# If there is no body, #finish is a no-op.
|
66
|
-
request = nil
|
67
|
-
end
|
68
|
-
|
69
61
|
write_response(@version, response.status, response.headers)
|
70
62
|
|
71
63
|
body = response.body
|
@@ -79,8 +71,21 @@ module Async
|
|
79
71
|
# We also don't want to hold on to the response object:
|
80
72
|
response = nil
|
81
73
|
|
74
|
+
body.call(stream)
|
75
|
+
elsif body and request.connect?
|
76
|
+
stream = write_tunnel_body(request.version)
|
77
|
+
|
78
|
+
# Same as above:
|
79
|
+
request = nil
|
80
|
+
response = nil
|
81
|
+
|
82
82
|
body.call(stream)
|
83
83
|
else
|
84
|
+
head = request.head?
|
85
|
+
|
86
|
+
request = nil unless body
|
87
|
+
response = nil
|
88
|
+
|
84
89
|
write_body(@version, body, head)
|
85
90
|
end
|
86
91
|
else
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'client'
|
22
|
+
require_relative 'endpoint'
|
23
|
+
|
24
|
+
require_relative 'body/pipe'
|
25
|
+
|
26
|
+
module Async
|
27
|
+
module HTTP
|
28
|
+
class Proxy
|
29
|
+
def self.tcp(client, host, port, headers = [])
|
30
|
+
self.new(client, "#{host}:#{port}", headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(client, address, headers = [])
|
34
|
+
@client = client
|
35
|
+
@address = address
|
36
|
+
@headers = headers
|
37
|
+
end
|
38
|
+
|
39
|
+
attr :client
|
40
|
+
|
41
|
+
def close
|
42
|
+
while @client.pool.busy?
|
43
|
+
@client.pool.wait
|
44
|
+
end
|
45
|
+
|
46
|
+
@client.close
|
47
|
+
end
|
48
|
+
|
49
|
+
def connect(&block)
|
50
|
+
input = Body::Writable.new
|
51
|
+
|
52
|
+
response = @client.connect(@address.to_s, @headers, input)
|
53
|
+
|
54
|
+
pipe = Body::Pipe.new(response.body, input)
|
55
|
+
|
56
|
+
return pipe.to_io unless block_given?
|
57
|
+
|
58
|
+
begin
|
59
|
+
yield pipe.to_io
|
60
|
+
ensure
|
61
|
+
pipe.close
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def endpoint(url, **options)
|
66
|
+
Endpoint.parse(url, self, **options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/async/http/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.48.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.25'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.25'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: protocol-http
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.9.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.
|
68
|
+
version: 0.9.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: protocol-http2
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -210,6 +210,7 @@ files:
|
|
210
210
|
- lib/async/http/body.rb
|
211
211
|
- lib/async/http/body/delayed.rb
|
212
212
|
- lib/async/http/body/hijack.rb
|
213
|
+
- lib/async/http/body/pipe.rb
|
213
214
|
- lib/async/http/body/slowloris.rb
|
214
215
|
- lib/async/http/body/stream.rb
|
215
216
|
- lib/async/http/body/writable.rb
|
@@ -236,6 +237,7 @@ files:
|
|
236
237
|
- lib/async/http/protocol/https.rb
|
237
238
|
- lib/async/http/protocol/request.rb
|
238
239
|
- lib/async/http/protocol/response.rb
|
240
|
+
- lib/async/http/proxy.rb
|
239
241
|
- lib/async/http/reference.rb
|
240
242
|
- lib/async/http/relative_location.rb
|
241
243
|
- lib/async/http/server.rb
|