httpx 0.3.1 → 0.4.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/lib/httpx.rb +8 -2
- data/lib/httpx/adapters/faraday.rb +203 -0
- data/lib/httpx/altsvc.rb +4 -0
- data/lib/httpx/callbacks.rb +1 -4
- data/lib/httpx/chainable.rb +4 -3
- data/lib/httpx/connection.rb +326 -104
- data/lib/httpx/{channel → connection}/http1.rb +29 -15
- data/lib/httpx/{channel → connection}/http2.rb +12 -6
- data/lib/httpx/errors.rb +2 -0
- data/lib/httpx/headers.rb +4 -1
- data/lib/httpx/io/ssl.rb +5 -1
- data/lib/httpx/io/tcp.rb +13 -7
- data/lib/httpx/io/udp.rb +1 -0
- data/lib/httpx/io/unix.rb +1 -0
- data/lib/httpx/loggable.rb +34 -9
- data/lib/httpx/options.rb +57 -31
- data/lib/httpx/parser/http1.rb +8 -0
- data/lib/httpx/plugins/authentication.rb +4 -0
- data/lib/httpx/plugins/basic_authentication.rb +4 -0
- data/lib/httpx/plugins/compression.rb +22 -5
- data/lib/httpx/plugins/cookies.rb +89 -36
- data/lib/httpx/plugins/digest_authentication.rb +45 -26
- data/lib/httpx/plugins/follow_redirects.rb +61 -62
- data/lib/httpx/plugins/h2c.rb +78 -39
- data/lib/httpx/plugins/multipart.rb +5 -0
- data/lib/httpx/plugins/persistent.rb +29 -0
- data/lib/httpx/plugins/proxy.rb +125 -78
- data/lib/httpx/plugins/proxy/http.rb +31 -27
- data/lib/httpx/plugins/proxy/socks4.rb +30 -24
- data/lib/httpx/plugins/proxy/socks5.rb +49 -39
- data/lib/httpx/plugins/proxy/ssh.rb +81 -0
- data/lib/httpx/plugins/push_promise.rb +18 -9
- data/lib/httpx/plugins/retries.rb +43 -15
- data/lib/httpx/pool.rb +159 -0
- data/lib/httpx/registry.rb +2 -0
- data/lib/httpx/request.rb +10 -0
- data/lib/httpx/resolver.rb +2 -1
- data/lib/httpx/resolver/https.rb +62 -56
- data/lib/httpx/resolver/native.rb +48 -37
- data/lib/httpx/resolver/resolver_mixin.rb +16 -11
- data/lib/httpx/resolver/system.rb +11 -7
- data/lib/httpx/response.rb +24 -10
- data/lib/httpx/selector.rb +32 -39
- data/lib/httpx/{client.rb → session.rb} +99 -62
- data/lib/httpx/timeout.rb +7 -15
- data/lib/httpx/transcoder/body.rb +4 -0
- data/lib/httpx/transcoder/chunker.rb +4 -0
- data/lib/httpx/version.rb +1 -1
- metadata +10 -8
- data/lib/httpx/channel.rb +0 -367
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 853641fa479bca4da2dca4d69bf8eddf6b323b12c40096cdb22faff1a8f80176
|
4
|
+
data.tar.gz: 8f74a2b53e2037863b9b66e4184e568f7de33bab9f6a65f27b9b9d8f6f3b145b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 736eb3d84089ac6d98576f6aeb1b20f3cb56cf32bc070c999fd41c0b1685612b89cf397884053e8d103c9464c1998f2651b9a219692b36dee9848a051f13d5d4
|
7
|
+
data.tar.gz: 6c17fbd06732769f09689ccc2f10e0f5fcb8e897ac6c32aa6e96ef707b2bcc26577b4dae8a8b3486e35495e002e833e06593421f99baa26f3c2586e651aaf393
|
data/lib/httpx.rb
CHANGED
@@ -12,12 +12,12 @@ require "httpx/registry"
|
|
12
12
|
require "httpx/transcoder"
|
13
13
|
require "httpx/options"
|
14
14
|
require "httpx/timeout"
|
15
|
-
require "httpx/
|
15
|
+
require "httpx/pool"
|
16
16
|
require "httpx/headers"
|
17
17
|
require "httpx/request"
|
18
18
|
require "httpx/response"
|
19
19
|
require "httpx/chainable"
|
20
|
-
require "httpx/
|
20
|
+
require "httpx/session"
|
21
21
|
|
22
22
|
# Top-Level Namespace
|
23
23
|
#
|
@@ -47,5 +47,11 @@ module HTTPX
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
+
def self.const_missing(const_name)
|
51
|
+
super unless const_name == :Client
|
52
|
+
warn "DEPRECATION WARNING: the class #{self}::Client is deprecated. Use #{self}::Session instead."
|
53
|
+
Session
|
54
|
+
end
|
55
|
+
|
50
56
|
extend Chainable
|
51
57
|
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "httpx"
|
4
|
+
require "faraday"
|
5
|
+
|
6
|
+
module Faraday
|
7
|
+
class Adapter
|
8
|
+
class HTTPX < Faraday::Adapter
|
9
|
+
module RequestMixin
|
10
|
+
private
|
11
|
+
|
12
|
+
def build_request(env)
|
13
|
+
meth = env[:method]
|
14
|
+
|
15
|
+
request_options = {
|
16
|
+
headers: env.request_headers,
|
17
|
+
body: env.body,
|
18
|
+
}
|
19
|
+
[meth, env.url, request_options]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include RequestMixin
|
24
|
+
|
25
|
+
class Session < ::HTTPX::Session
|
26
|
+
plugin(:compression)
|
27
|
+
plugin(:persistent)
|
28
|
+
|
29
|
+
module ReasonPlugin
|
30
|
+
if RUBY_VERSION < "2.5"
|
31
|
+
def self.load_dependencies(*)
|
32
|
+
require "webrick"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
def self.load_dependencies(*)
|
36
|
+
require "net/http/status"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
module ResponseMethods
|
40
|
+
if RUBY_VERSION < "2.5"
|
41
|
+
def reason
|
42
|
+
WEBrick::HTTPStatus::StatusMessage.fetch(@status)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
def reason
|
46
|
+
Net::HTTP::STATUS_CODES.fetch(@status)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
plugin(ReasonPlugin)
|
52
|
+
end
|
53
|
+
|
54
|
+
class ParallelManager
|
55
|
+
class ResponseHandler
|
56
|
+
attr_reader :env
|
57
|
+
|
58
|
+
def initialize(env)
|
59
|
+
@env = env
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_response(&blk)
|
63
|
+
if block_given?
|
64
|
+
@on_response = lambda do |response|
|
65
|
+
blk.call(response)
|
66
|
+
end
|
67
|
+
self
|
68
|
+
else
|
69
|
+
@on_response
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_complete(&blk)
|
74
|
+
if block_given?
|
75
|
+
@on_complete = blk
|
76
|
+
self
|
77
|
+
else
|
78
|
+
@on_complete
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def respond_to_missing?(meth)
|
83
|
+
@env.respond_to?(meth)
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_missing(meth, *args, &blk)
|
87
|
+
if @env && @env.respond_to?(meth)
|
88
|
+
@env.__send__(meth, *args, &blk)
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
include RequestMixin
|
96
|
+
|
97
|
+
def initialize
|
98
|
+
@session = Session.new
|
99
|
+
@handlers = []
|
100
|
+
end
|
101
|
+
|
102
|
+
def enqueue(request)
|
103
|
+
handler = ResponseHandler.new(request)
|
104
|
+
@handlers << handler
|
105
|
+
handler
|
106
|
+
end
|
107
|
+
|
108
|
+
def run
|
109
|
+
requests = @handlers.map { |handler| build_request(handler.env) }
|
110
|
+
env = @handlers.last.env
|
111
|
+
|
112
|
+
timeout_options = {
|
113
|
+
connect_timeout: env.request.open_timeout,
|
114
|
+
operation_timeout: env.request.timeout,
|
115
|
+
}.reject { |_, v| v.nil? }
|
116
|
+
|
117
|
+
options = {
|
118
|
+
ssl: env.ssl,
|
119
|
+
timeout: timeout_options,
|
120
|
+
}
|
121
|
+
|
122
|
+
proxy_options = { uri: env.request.proxy }
|
123
|
+
|
124
|
+
session = @session.with(options)
|
125
|
+
session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
|
126
|
+
|
127
|
+
responses = session.request(requests)
|
128
|
+
responses.each_with_index do |response, index|
|
129
|
+
handler = @handlers[index]
|
130
|
+
handler.on_response.call(response)
|
131
|
+
handler.on_complete.call(handler.env)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
self.supports_parallel = true
|
137
|
+
|
138
|
+
class << self
|
139
|
+
def setup_parallel_manager
|
140
|
+
ParallelManager.new
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def initialize(app)
|
145
|
+
super(app)
|
146
|
+
@session = Session.new
|
147
|
+
end
|
148
|
+
|
149
|
+
def call(env)
|
150
|
+
if parallel?(env)
|
151
|
+
handler = env[:parallel_manager].enqueue(env)
|
152
|
+
handler.on_response do |response|
|
153
|
+
save_response(env, response.status, response.body, response.headers, response.reason) do |response_headers|
|
154
|
+
response_headers.merge!(response.headers)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
return handler
|
158
|
+
end
|
159
|
+
|
160
|
+
request_options = build_request(env)
|
161
|
+
|
162
|
+
timeout_options = {
|
163
|
+
connect_timeout: env.request.open_timeout,
|
164
|
+
operation_timeout: env.request.timeout,
|
165
|
+
}.reject { |_, v| v.nil? }
|
166
|
+
|
167
|
+
options = {
|
168
|
+
ssl: env.ssl,
|
169
|
+
timeout: timeout_options,
|
170
|
+
}
|
171
|
+
|
172
|
+
proxy_options = { uri: env.request.proxy }
|
173
|
+
|
174
|
+
session = @session.with(options)
|
175
|
+
session = session.plugin(:proxy).with_proxy(proxy_options) if env.request.proxy
|
176
|
+
response = session.__send__(*request_options)
|
177
|
+
response.raise_for_status unless response.is_a?(::HTTPX::Response)
|
178
|
+
save_response(env, response.status, response.body, response.headers, response.reason) do |response_headers|
|
179
|
+
response_headers.merge!(response.headers)
|
180
|
+
end
|
181
|
+
@app.call(env)
|
182
|
+
rescue OpenSSL::SSL::SSLError => err
|
183
|
+
raise Error::SSLError, err
|
184
|
+
rescue Errno::ECONNABORTED,
|
185
|
+
Errno::ECONNREFUSED,
|
186
|
+
Errno::ECONNRESET,
|
187
|
+
Errno::EHOSTUNREACH,
|
188
|
+
Errno::EINVAL,
|
189
|
+
Errno::ENETUNREACH,
|
190
|
+
Errno::EPIPE => err
|
191
|
+
raise Error::ConnectionFailed, err
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def parallel?(env)
|
197
|
+
env[:parallel_manager]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
register_middleware httpx: HTTPX
|
202
|
+
end
|
203
|
+
end
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -18,6 +18,7 @@ module HTTPX
|
|
18
18
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
19
19
|
@altsvc_mutex.synchronize do
|
20
20
|
return if @altsvcs[origin].any? { |altsvc| altsvc["origin"] == entry["origin"] }
|
21
|
+
|
21
22
|
entry["TTL"] = Integer(entry["ma"]) + now if entry.key?("ma")
|
22
23
|
@altsvcs[origin] << entry
|
23
24
|
entry
|
@@ -26,6 +27,7 @@ module HTTPX
|
|
26
27
|
|
27
28
|
def lookup(origin, ttl)
|
28
29
|
return [] unless @altsvcs.key?(origin)
|
30
|
+
|
29
31
|
@altsvcs[origin] = @altsvcs[origin].select do |entry|
|
30
32
|
!entry.key?("TTL") || entry["TTL"] > ttl
|
31
33
|
end
|
@@ -35,6 +37,7 @@ module HTTPX
|
|
35
37
|
def emit(request, response)
|
36
38
|
# Alt-Svc
|
37
39
|
return unless response.headers.key?("alt-svc")
|
40
|
+
|
38
41
|
origin = request.origin
|
39
42
|
host = request.uri.host
|
40
43
|
parse(response.headers["alt-svc"]) do |alt_origin, alt_params|
|
@@ -45,6 +48,7 @@ module HTTPX
|
|
45
48
|
|
46
49
|
def parse(altsvc)
|
47
50
|
return enum_for(__method__, altsvc) unless block_given?
|
51
|
+
|
48
52
|
alt_origins, *alt_params = altsvc.split(/ *; */)
|
49
53
|
alt_params = Hash[alt_params.map { |field| field.split("=") }]
|
50
54
|
alt_origins.split(/ *, */).each do |alt_origin|
|
data/lib/httpx/callbacks.rb
CHANGED
@@ -19,12 +19,9 @@ module HTTPX
|
|
19
19
|
|
20
20
|
protected
|
21
21
|
|
22
|
-
def inherit_callbacks(callbackable)
|
23
|
-
@callbacks = callbackable.callbacks
|
24
|
-
end
|
25
|
-
|
26
22
|
def callbacks(type = nil)
|
27
23
|
return @callbacks unless type
|
24
|
+
|
28
25
|
@callbacks ||= Hash.new { |h, k| h[k] = [] }
|
29
26
|
@callbacks[type]
|
30
27
|
end
|
data/lib/httpx/chainable.rb
CHANGED
@@ -29,7 +29,7 @@ module HTTPX
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def plugin(*plugins)
|
32
|
-
klass = is_a?(
|
32
|
+
klass = is_a?(Session) ? self.class : Session
|
33
33
|
klass = Class.new(klass)
|
34
34
|
klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
|
35
35
|
klass.plugins(plugins).new
|
@@ -48,8 +48,9 @@ module HTTPX
|
|
48
48
|
|
49
49
|
# :nodoc:
|
50
50
|
def branch(options, &blk)
|
51
|
-
return self.class.new(options, &blk) if is_a?(
|
52
|
-
|
51
|
+
return self.class.new(options, &blk) if is_a?(Session)
|
52
|
+
|
53
|
+
Session.new(options, &blk)
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
data/lib/httpx/connection.rb
CHANGED
@@ -1,150 +1,372 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
4
|
-
require "
|
5
|
-
require "httpx/
|
3
|
+
require "resolv"
|
4
|
+
require "forwardable"
|
5
|
+
require "httpx/io"
|
6
|
+
require "httpx/buffer"
|
6
7
|
|
7
8
|
module HTTPX
|
9
|
+
# The Connection can be watched for IO events.
|
10
|
+
#
|
11
|
+
# It contains the +io+ object to read/write from, and knows what to do when it can.
|
12
|
+
#
|
13
|
+
# It defers connecting until absolutely necessary. Connection should be triggered from
|
14
|
+
# the IO selector (until then, any request will be queued).
|
15
|
+
#
|
16
|
+
# A connection boots up its parser after connection is established. All pending requests
|
17
|
+
# will be redirected there after connection.
|
18
|
+
#
|
19
|
+
# A connection can be prevented from closing by the parser, that is, if there are pending
|
20
|
+
# requests. This will signal that the connection was prematurely closed, due to a possible
|
21
|
+
# number of conditions:
|
22
|
+
#
|
23
|
+
# * Remote peer closed the connection ("Connection: close");
|
24
|
+
# * Remote peer doesn't support pipelining;
|
25
|
+
#
|
26
|
+
# A connection may also route requests for a different host for which the +io+ was connected
|
27
|
+
# to, provided that the IP is the same and the port and scheme as well. This will allow to
|
28
|
+
# share the same socket to send HTTP/2 requests to different hosts.
|
29
|
+
#
|
8
30
|
class Connection
|
9
|
-
|
31
|
+
extend Forwardable
|
32
|
+
include Registry
|
33
|
+
include Loggable
|
34
|
+
include Callbacks
|
35
|
+
|
36
|
+
using URIExtensions
|
37
|
+
|
38
|
+
require "httpx/connection/http2"
|
39
|
+
require "httpx/connection/http1"
|
40
|
+
|
41
|
+
BUFFER_SIZE = 1 << 14
|
42
|
+
|
43
|
+
def_delegator :@io, :closed?
|
44
|
+
|
45
|
+
def_delegator :@write_buffer, :empty?
|
46
|
+
|
47
|
+
attr_reader :origin, :state, :pending, :options
|
48
|
+
|
49
|
+
attr_reader :timeout
|
50
|
+
|
51
|
+
def initialize(type, uri, options)
|
52
|
+
@type = type
|
53
|
+
@origins = [uri.origin]
|
54
|
+
@origin = URI(uri.origin)
|
10
55
|
@options = Options.new(options)
|
11
|
-
@
|
12
|
-
|
13
|
-
|
14
|
-
@
|
15
|
-
|
16
|
-
@
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
56
|
+
@window_size = @options.window_size
|
57
|
+
@read_buffer = Buffer.new(BUFFER_SIZE)
|
58
|
+
@write_buffer = Buffer.new(BUFFER_SIZE)
|
59
|
+
@pending = []
|
60
|
+
on(:error, &method(:on_error))
|
61
|
+
if @options.io
|
62
|
+
# if there's an already open IO, get its
|
63
|
+
# peer address, and force-initiate the parser
|
64
|
+
transition(:already_open)
|
65
|
+
@io = IO.registry(@type).new(@origin, nil, @options)
|
66
|
+
parser
|
67
|
+
else
|
68
|
+
transition(:idle)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# this is a semi-private method, to be used by the resolver
|
73
|
+
# to initiate the io object.
|
74
|
+
def addresses=(addrs)
|
75
|
+
@io ||= IO.registry(@type).new(@origin, addrs, @options) # rubocop:disable Naming/MemoizedInstanceVariableName
|
76
|
+
end
|
77
|
+
|
78
|
+
def addresses
|
79
|
+
@io && @io.addresses
|
80
|
+
end
|
81
|
+
|
82
|
+
def match?(uri, options)
|
83
|
+
return false if @state == :closing || @state == :closed
|
84
|
+
|
85
|
+
(@origins.include?(uri.origin) || match_altsvcs?(uri)) && @options == options
|
86
|
+
end
|
87
|
+
|
88
|
+
def mergeable?(connection)
|
89
|
+
return false if @state == :closing || @state == :closed || !@io
|
90
|
+
|
91
|
+
!(@io.addresses & connection.addresses).empty? && @options == connection.options
|
92
|
+
end
|
93
|
+
|
94
|
+
# coalescable connections need to be mergeable!
|
95
|
+
# but internally, #mergeable? is called before #coalescable?
|
96
|
+
def coalescable?(connection)
|
97
|
+
if @io.protocol == "h2" && @origin.scheme == "https"
|
98
|
+
@io.verify_hostname(connection.origin.host)
|
99
|
+
else
|
100
|
+
@origin == connection.origin
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def merge(connection)
|
105
|
+
@origins += connection.instance_variable_get(:@origins)
|
106
|
+
pending = connection.instance_variable_get(:@pending)
|
107
|
+
pending.each do |req, args|
|
108
|
+
send(req, args)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def unmerge(connection)
|
113
|
+
@origins -= connection.instance_variable_get(:@origins)
|
114
|
+
purge_pending do |request|
|
115
|
+
request.uri.origin == connection.origin && begin
|
116
|
+
request.transition(:idle)
|
117
|
+
connection.send(request)
|
118
|
+
true
|
34
119
|
end
|
35
120
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
121
|
+
end
|
122
|
+
|
123
|
+
def purge_pending
|
124
|
+
[@parser.pending, @pending].each do |pending|
|
125
|
+
pending.reject! do |request, *args|
|
126
|
+
yield(request, args)
|
127
|
+
end
|
39
128
|
end
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
129
|
+
end
|
130
|
+
|
131
|
+
# checks if this is connection is an alternative service of
|
132
|
+
# +uri+
|
133
|
+
def match_altsvcs?(uri)
|
134
|
+
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
135
|
+
origin = altsvc["origin"]
|
136
|
+
origin.altsvc_match?(uri.origin)
|
45
137
|
end
|
46
138
|
end
|
47
139
|
|
48
|
-
def
|
49
|
-
@
|
50
|
-
@channels.each(&:close)
|
51
|
-
next_tick until @channels.empty?
|
140
|
+
def connecting?
|
141
|
+
@state == :idle
|
52
142
|
end
|
53
143
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
144
|
+
def inflight?
|
145
|
+
@parser && !@parser.empty? && !@write_buffer.empty?
|
146
|
+
end
|
147
|
+
|
148
|
+
def interests
|
149
|
+
return :w if @state == :idle
|
150
|
+
|
151
|
+
readable = !@read_buffer.full?
|
152
|
+
writable = !@write_buffer.empty?
|
153
|
+
if readable
|
154
|
+
writable ? :rw : :r
|
155
|
+
else
|
156
|
+
writable ? :w : :r
|
60
157
|
end
|
61
|
-
|
62
|
-
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_io
|
161
|
+
case @state
|
162
|
+
when :idle
|
163
|
+
transition(:open)
|
63
164
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
165
|
+
@io.to_io
|
166
|
+
end
|
167
|
+
|
168
|
+
def close
|
169
|
+
@parser.close if @parser
|
170
|
+
transition(:closing)
|
171
|
+
end
|
172
|
+
|
173
|
+
def reset
|
174
|
+
transition(:closing)
|
175
|
+
transition(:closed)
|
176
|
+
emit(:close)
|
177
|
+
end
|
178
|
+
|
179
|
+
def send(request)
|
180
|
+
if @error_response
|
181
|
+
emit(:response, request, @error_response)
|
182
|
+
elsif @parser && !@write_buffer.full?
|
183
|
+
request.headers["alt-used"] = @origin.authority if match_altsvcs?(request.uri)
|
184
|
+
parser.send(request)
|
185
|
+
else
|
186
|
+
@pending << request
|
67
187
|
end
|
68
|
-
channel
|
69
188
|
end
|
70
189
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
190
|
+
def call
|
191
|
+
@timeout = @timeout_threshold
|
192
|
+
case @state
|
193
|
+
when :closed
|
194
|
+
return
|
195
|
+
when :closing
|
196
|
+
dwrite
|
197
|
+
transition(:closed)
|
198
|
+
emit(:close)
|
199
|
+
when :open
|
200
|
+
consume
|
78
201
|
end
|
202
|
+
nil
|
79
203
|
end
|
80
204
|
|
81
205
|
private
|
82
206
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
monitor = @selector.register(@resolver, :w)
|
89
|
-
monitor.value = @resolver
|
90
|
-
monitor
|
207
|
+
def consume
|
208
|
+
catch(:called) do
|
209
|
+
dread
|
210
|
+
dwrite
|
211
|
+
parser.consume
|
91
212
|
end
|
92
213
|
end
|
93
214
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
215
|
+
def dread(wsize = @window_size)
|
216
|
+
loop do
|
217
|
+
siz = @io.read(wsize, @read_buffer)
|
218
|
+
unless siz
|
219
|
+
ex = EOFError.new("descriptor closed")
|
220
|
+
ex.set_backtrace(caller)
|
221
|
+
on_error(ex)
|
222
|
+
return
|
223
|
+
end
|
224
|
+
return if siz.zero?
|
225
|
+
|
226
|
+
log { "READ: #{siz} bytes..." }
|
227
|
+
parser << @read_buffer.to_s
|
228
|
+
return if @state == :closing || @state == :closed
|
97
229
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
230
|
+
end
|
231
|
+
|
232
|
+
def dwrite
|
233
|
+
loop do
|
234
|
+
return if @write_buffer.empty?
|
235
|
+
|
236
|
+
siz = @io.write(@write_buffer)
|
237
|
+
unless siz
|
238
|
+
ex = EOFError.new("descriptor closed")
|
239
|
+
ex.set_backtrace(caller)
|
240
|
+
on_error(ex)
|
241
|
+
return
|
104
242
|
end
|
243
|
+
log { "WRITE: #{siz} bytes..." }
|
244
|
+
return if siz.zero?
|
245
|
+
return if @state == :closing || @state == :closed
|
105
246
|
end
|
106
247
|
end
|
107
248
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
249
|
+
def send_pending
|
250
|
+
while !@write_buffer.full? && (req_args = @pending.shift)
|
251
|
+
request = req_args
|
252
|
+
parser.send(request)
|
253
|
+
end
|
112
254
|
end
|
113
255
|
|
114
|
-
def
|
115
|
-
@
|
116
|
-
@_resolver_monitor = nil
|
117
|
-
@resolver.close unless @resolver.closed?
|
256
|
+
def parser
|
257
|
+
@parser ||= build_parser
|
118
258
|
end
|
119
259
|
|
120
|
-
def
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
channel.on(:close) do
|
125
|
-
unregister_channel(channel)
|
126
|
-
end
|
260
|
+
def build_parser(protocol = @io.protocol)
|
261
|
+
parser = registry(protocol).new(@write_buffer, @options)
|
262
|
+
set_parser_callbacks(parser)
|
263
|
+
parser
|
127
264
|
end
|
128
265
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
266
|
+
def set_parser_callbacks(parser)
|
267
|
+
parser.on(:response) do |request, response|
|
268
|
+
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
269
|
+
emit(:altsvc, alt_origin, origin, alt_params)
|
270
|
+
end
|
271
|
+
request.emit(:response, response)
|
272
|
+
end
|
273
|
+
parser.on(:altsvc) do |alt_origin, origin, alt_params|
|
274
|
+
emit(:altsvc, alt_origin, origin, alt_params)
|
275
|
+
end
|
276
|
+
|
277
|
+
parser.on(:promise) do |request, stream|
|
278
|
+
request.emit(:promise, parser, stream)
|
279
|
+
end
|
280
|
+
parser.on(:close) do
|
281
|
+
transition(:closing)
|
282
|
+
end
|
283
|
+
parser.on(:reset) do
|
284
|
+
transition(:closing)
|
285
|
+
unless parser.empty?
|
286
|
+
transition(:closed)
|
287
|
+
emit(:reset)
|
288
|
+
transition(:idle)
|
289
|
+
transition(:open)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
parser.on(:timeout) do |timeout|
|
293
|
+
@timeout = timeout
|
294
|
+
end
|
295
|
+
parser.on(:error) do |request, ex|
|
296
|
+
case ex
|
297
|
+
when MisdirectedRequestError
|
298
|
+
emit(:uncoalesce, request.uri)
|
299
|
+
else
|
300
|
+
response = ErrorResponse.new(ex, @options)
|
301
|
+
request.emit(:response, response)
|
302
|
+
end
|
303
|
+
end
|
133
304
|
end
|
134
305
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
@
|
139
|
-
|
140
|
-
|
306
|
+
def transition(nextstate)
|
307
|
+
case nextstate
|
308
|
+
when :idle
|
309
|
+
@error_response = nil
|
310
|
+
@timeout_threshold = @options.timeout.connect_timeout
|
311
|
+
@timeout = @timeout_threshold
|
312
|
+
when :open
|
313
|
+
return if @state == :closed
|
314
|
+
|
315
|
+
@io.connect
|
316
|
+
return unless @io.connected?
|
317
|
+
|
318
|
+
send_pending
|
319
|
+
@timeout_threshold = @options.timeout.operation_timeout
|
320
|
+
@timeout = @timeout_threshold
|
321
|
+
emit(:open)
|
322
|
+
when :closing
|
323
|
+
return unless @state == :open
|
324
|
+
when :closed
|
325
|
+
return unless @state == :closing
|
326
|
+
return unless @write_buffer.empty?
|
327
|
+
|
328
|
+
@io.close
|
329
|
+
@read_buffer.clear
|
330
|
+
when :already_open
|
331
|
+
nextstate = :open
|
332
|
+
send_pending
|
333
|
+
@timeout_threshold = @options.timeout.operation_timeout
|
334
|
+
@timeout = @timeout_threshold
|
141
335
|
end
|
336
|
+
@state = nextstate
|
337
|
+
rescue Errno::EHOSTUNREACH
|
338
|
+
# at this point, all addresses from the IO object have failed
|
339
|
+
reset
|
340
|
+
emit(:unreachable)
|
341
|
+
throw(:jump_tick)
|
342
|
+
rescue Errno::ECONNREFUSED,
|
343
|
+
Errno::EADDRNOTAVAIL,
|
344
|
+
Errno::EHOSTUNREACH,
|
345
|
+
OpenSSL::SSL::SSLError => e
|
346
|
+
# connect errors, exit gracefully
|
347
|
+
handle_error(e)
|
348
|
+
@state = :closed
|
349
|
+
emit(:close)
|
350
|
+
end
|
351
|
+
|
352
|
+
def on_error(ex)
|
353
|
+
handle_error(ex)
|
354
|
+
reset
|
142
355
|
end
|
143
356
|
|
144
|
-
def
|
145
|
-
|
146
|
-
|
147
|
-
|
357
|
+
def handle_error(e)
|
358
|
+
if e.instance_of?(TimeoutError) && @timeout
|
359
|
+
@timeout -= e.timeout
|
360
|
+
return unless @timeout <= 0
|
361
|
+
|
362
|
+
e = e.to_connection_error if connecting?
|
363
|
+
end
|
364
|
+
|
365
|
+
parser.handle_error(e) if @parser && parser.respond_to?(:handle_error)
|
366
|
+
@error_response = ErrorResponse.new(e, @options)
|
367
|
+
@pending.each do |request, _|
|
368
|
+
request.emit(:response, @error_response)
|
369
|
+
end
|
148
370
|
end
|
149
371
|
end
|
150
372
|
end
|