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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27b6378ebb80f14051f9869d2409de28292dac437c14d7b4188edcd45c7d9971
4
- data.tar.gz: a52c8f9553effe5e0e1bdf0e90a9eaa529e3f4cc1e1c53d06e7606fe16966e16
3
+ metadata.gz: bd79f3f1a3f1c5c5283c9e851433bce5833c18f33b231623fb1e414de078282f
4
+ data.tar.gz: bc27cc3ed8568cdd884e09c0889d8d78c3a879bf0117c94c9845f8ea2bd4e5d9
5
5
  SHA512:
6
- metadata.gz: 4ed7257439e6e3829c88cedefc78df137551b9c0183abaa76e59a7d39d0ae399af9a5fec7b66ece4a22648099ef6e29abe9d1c2f93ba83d80622c17edf0f604d
7
- data.tar.gz: 35906d4412cf80f45a3ef074b0f29ec9076f40f73b74c542353d60a2dfb1509aba95c27e5b8fe340d49c0e6a873998adbc49920622f28439b9a4f360f0f4350c
6
+ metadata.gz: 5efa88bd4fb17b26b36b434879ff1e2757fdee205d08ee9b5f6a111d867ace9fd2d06ade0b6f895445fc9d5c09057b047d474d80d70f79022644a2af4b3fba0e
7
+ data.tar.gz: cd4863983ac3392d88e47c7d5244a5968ea90caa5b906d7335a2ccc42a70f9fd2348bc48a77e9b4e34dad736c83477af39759c6a14d08aff69d1d27775017828
@@ -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.24")
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.8.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)
@@ -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, nil, **options)
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
- @endpoint = endpoint
49
+
50
+ @endpoint = self.build_endpoint(endpoint)
50
51
  end
51
52
 
52
53
  def to_url
@@ -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
- attr :active
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 idea, but simple for now.
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 @active < @limit
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
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module HTTP
23
- VERSION = "0.47.0"
23
+ VERSION = "0.48.0"
24
24
  end
25
25
  end
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.47.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-07-21 00:00:00.000000000 Z
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.24'
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.24'
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.8.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.8.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