async-http 0.47.0 → 0.48.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: 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