polyphony 0.19 → 0.20
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/.gitignore +1 -1
- data/.rubocop.yml +87 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +17 -6
- data/README.md +200 -139
- data/Rakefile +4 -4
- data/TODO.md +35 -7
- data/bin/poly +11 -0
- data/docs/getting-started/getting-started.md +1 -1
- data/docs/summary.md +3 -0
- data/docs/technical-overview/exception-handling.md +94 -0
- data/docs/technical-overview/fiber-scheduling.md +99 -0
- data/examples/core/cancel.rb +8 -4
- data/examples/core/channel_echo.rb +18 -17
- data/examples/core/defer.rb +12 -0
- data/examples/core/enumerator.rb +4 -4
- data/examples/core/fiber_error.rb +9 -0
- data/examples/core/fiber_error_with_backtrace.rb +73 -0
- data/examples/core/fork.rb +6 -6
- data/examples/core/genserver.rb +16 -8
- data/examples/core/lock.rb +3 -3
- data/examples/core/move_on.rb +4 -3
- data/examples/core/move_on_twice.rb +5 -5
- data/examples/core/move_on_with_ensure.rb +8 -11
- data/examples/core/move_on_with_value.rb +14 -0
- data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
- data/examples/core/nested_cancel.rb +5 -5
- data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
- data/examples/core/nested_spin.rb +17 -0
- data/examples/core/pingpong.rb +21 -0
- data/examples/core/pulse.rb +4 -5
- data/examples/core/resource.rb +6 -4
- data/examples/core/resource_cancel.rb +6 -9
- data/examples/core/resource_delegate.rb +3 -3
- data/examples/core/sleep.rb +3 -3
- data/examples/core/sleep_spin.rb +19 -0
- data/examples/core/snooze.rb +32 -0
- data/examples/core/spin.rb +14 -0
- data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
- data/examples/core/spin_error.rb +17 -0
- data/examples/core/spin_error_backtrace.rb +30 -0
- data/examples/core/spin_uncaught_error.rb +15 -0
- data/examples/core/supervisor.rb +8 -8
- data/examples/core/supervisor_with_cancel_scope.rb +7 -7
- data/examples/core/supervisor_with_error.rb +8 -8
- data/examples/core/supervisor_with_manual_move_on.rb +6 -7
- data/examples/core/suspend.rb +13 -0
- data/examples/core/thread.rb +1 -1
- data/examples/core/thread_cancel.rb +9 -11
- data/examples/core/thread_pool.rb +18 -14
- data/examples/core/throttle.rb +7 -7
- data/examples/core/timeout.rb +3 -3
- data/examples/fs/read.rb +7 -9
- data/examples/http/config.ru +7 -3
- data/examples/http/cuba.ru +22 -0
- data/examples/http/happy_eyeballs.rb +6 -4
- data/examples/http/http_client.rb +1 -1
- data/examples/http/http_get.rb +1 -1
- data/examples/http/http_parse_experiment.rb +21 -16
- data/examples/http/http_proxy.rb +28 -26
- data/examples/http/http_server.rb +10 -10
- data/examples/http/http_server_forked.rb +6 -5
- data/examples/http/http_server_throttled.rb +3 -3
- data/examples/http/http_ws_server.rb +11 -11
- data/examples/http/https_raw_client.rb +1 -1
- data/examples/http/https_server.rb +8 -8
- data/examples/http/https_wss_server.rb +13 -11
- data/examples/http/rack_server.rb +2 -2
- data/examples/http/rack_server_https.rb +4 -4
- data/examples/http/rack_server_https_forked.rb +5 -5
- data/examples/http/websocket_secure_server.rb +6 -6
- data/examples/http/websocket_server.rb +5 -5
- data/examples/interfaces/pg_client.rb +4 -4
- data/examples/interfaces/pg_pool.rb +13 -6
- data/examples/interfaces/pg_transaction.rb +5 -4
- data/examples/interfaces/redis_channels.rb +15 -11
- data/examples/interfaces/redis_client.rb +2 -2
- data/examples/interfaces/redis_pubsub.rb +2 -1
- data/examples/interfaces/redis_pubsub_perf.rb +13 -9
- data/examples/io/backticks.rb +11 -0
- data/examples/io/cat.rb +4 -5
- data/examples/io/echo_client.rb +9 -4
- data/examples/io/echo_client_from_stdin.rb +20 -0
- data/examples/io/echo_pipe.rb +7 -8
- data/examples/io/echo_server.rb +8 -6
- data/examples/io/echo_server_with_timeout.rb +13 -10
- data/examples/io/echo_stdin.rb +3 -3
- data/examples/io/httparty.rb +2 -2
- data/examples/io/httparty_multi.rb +8 -4
- data/examples/io/httparty_threaded.rb +6 -2
- data/examples/io/io_read.rb +2 -2
- data/examples/io/irb.rb +16 -4
- data/examples/io/net-http.rb +3 -3
- data/examples/io/open.rb +17 -0
- data/examples/io/system.rb +3 -3
- data/examples/io/tcpserver.rb +15 -0
- data/examples/io/tcpsocket.rb +6 -5
- data/examples/performance/multi_snooze.rb +29 -0
- data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
- data/examples/performance/snooze_raw.rb +39 -0
- data/ext/gyro/async.c +165 -0
- data/ext/gyro/child.c +167 -0
- data/ext/{ev → gyro}/extconf.rb +4 -3
- data/ext/gyro/gyro.c +316 -0
- data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
- data/ext/gyro/gyro_ext.c +23 -0
- data/ext/{ev → gyro}/io.c +65 -57
- data/ext/{ev → gyro}/libev.h +0 -0
- data/ext/gyro/signal.c +117 -0
- data/ext/{ev → gyro}/socket.c +61 -6
- data/ext/gyro/timer.c +199 -0
- data/ext/libev/Changes +35 -0
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +213 -151
- data/ext/libev/ev.h +95 -88
- data/ext/libev/ev_epoll.c +26 -15
- data/ext/libev/ev_kqueue.c +11 -5
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +13 -8
- data/ext/libev/ev_port.c +5 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/lib/ev_ext.bundle +0 -0
- data/lib/polyphony.rb +46 -50
- data/lib/polyphony/auto_run.rb +12 -0
- data/lib/polyphony/core/cancel_scope.rb +11 -7
- data/lib/polyphony/core/channel.rb +16 -9
- data/lib/polyphony/core/coprocess.rb +101 -51
- data/lib/polyphony/core/exceptions.rb +14 -12
- data/lib/polyphony/core/resource_pool.rb +21 -8
- data/lib/polyphony/core/supervisor.rb +10 -5
- data/lib/polyphony/core/sync.rb +7 -6
- data/lib/polyphony/core/thread.rb +4 -4
- data/lib/polyphony/core/thread_pool.rb +4 -4
- data/lib/polyphony/core/throttler.rb +6 -4
- data/lib/polyphony/extensions/core.rb +253 -0
- data/lib/polyphony/extensions/io.rb +28 -16
- data/lib/polyphony/extensions/openssl.rb +2 -1
- data/lib/polyphony/extensions/socket.rb +47 -52
- data/lib/polyphony/http.rb +4 -3
- data/lib/polyphony/http/agent.rb +68 -57
- data/lib/polyphony/http/server.rb +5 -5
- data/lib/polyphony/http/server/http1.rb +268 -0
- data/lib/polyphony/http/server/http2.rb +62 -0
- data/lib/polyphony/http/server/http2_stream.rb +104 -0
- data/lib/polyphony/http/server/rack.rb +64 -0
- data/lib/polyphony/http/server/request.rb +119 -0
- data/lib/polyphony/net.rb +26 -15
- data/lib/polyphony/postgres.rb +17 -13
- data/lib/polyphony/redis.rb +16 -15
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony/websocket.rb +11 -4
- data/polyphony.gemspec +13 -9
- data/test/eg.rb +27 -0
- data/test/helper.rb +25 -0
- data/test/run.rb +5 -0
- data/test/test_async.rb +33 -0
- data/test/test_coprocess.rb +239 -77
- data/test/test_core.rb +95 -61
- data/test/test_gyro.rb +148 -0
- data/test/test_http_server.rb +313 -0
- data/test/test_io.rb +79 -27
- data/test/test_kernel.rb +22 -12
- data/test/test_signal.rb +36 -0
- data/test/test_timer.rb +24 -0
- metadata +89 -33
- data/examples/core/nested_async.rb +0 -17
- data/examples/core/next_tick.rb +0 -12
- data/examples/core/sleep_spawn.rb +0 -19
- data/examples/core/spawn.rb +0 -14
- data/examples/core/spawn_error.rb +0 -28
- data/examples/performance/perf_multi_snooze.rb +0 -21
- data/ext/ev/async.c +0 -168
- data/ext/ev/child.c +0 -169
- data/ext/ev/ev_ext.c +0 -23
- data/ext/ev/ev_module.c +0 -242
- data/ext/ev/signal.c +0 -119
- data/ext/ev/timer.c +0 -197
- data/lib/polyphony/core/fiber_pool.rb +0 -98
- data/lib/polyphony/extensions/kernel.rb +0 -169
- data/lib/polyphony/http/http1_adapter.rb +0 -254
- data/lib/polyphony/http/http2_adapter.rb +0 -157
- data/lib/polyphony/http/rack.rb +0 -25
- data/lib/polyphony/http/request.rb +0 -66
- data/test/test_ev.rb +0 -110
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
# IO overrides
|
3
6
|
class ::IO
|
4
7
|
class << self
|
5
8
|
alias_method :orig_binread, :binread
|
@@ -18,11 +21,14 @@ class ::IO
|
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
21
|
-
EMPTY_HASH = {}
|
24
|
+
EMPTY_HASH = {}.freeze
|
22
25
|
|
23
26
|
alias_method :orig_foreach, :foreach
|
24
27
|
def foreach(name, sep = $/, limit = nil, getline_args = EMPTY_HASH, &block)
|
25
|
-
|
28
|
+
if sep.is_a?(Integer)
|
29
|
+
sep = $/
|
30
|
+
limit = sep
|
31
|
+
end
|
26
32
|
File.open(name, 'r') do |f|
|
27
33
|
f.each_line(sep, limit, getline_args, &block)
|
28
34
|
end
|
@@ -30,7 +36,10 @@ class ::IO
|
|
30
36
|
|
31
37
|
alias_method :orig_read, :read
|
32
38
|
def read(name, length = nil, offset = nil, opt = EMPTY_HASH)
|
33
|
-
|
39
|
+
if length.is_a?(Hash)
|
40
|
+
opt = length
|
41
|
+
length = nil
|
42
|
+
end
|
34
43
|
File.open(name, opt[:mode] || 'r') do |f|
|
35
44
|
f.seek(offset) if offset
|
36
45
|
length ? f.read(length) : f.read
|
@@ -40,7 +49,6 @@ class ::IO
|
|
40
49
|
# alias_method :orig_readlines, :readlines
|
41
50
|
# def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
|
42
51
|
# File.open(name, 'r') do |f|
|
43
|
-
# puts "readlines(#{sep.inspect}, #{limit.inspect}, #{getline_args.inspect}"
|
44
52
|
# f.readlines(sep, limit, getline_args)
|
45
53
|
# end
|
46
54
|
# end
|
@@ -54,10 +62,10 @@ class ::IO
|
|
54
62
|
end
|
55
63
|
|
56
64
|
alias_method :orig_popen, :popen
|
57
|
-
def popen(
|
58
|
-
|
59
|
-
|
60
|
-
|
65
|
+
def popen(cmd, mode = 'r')
|
66
|
+
return orig_popen(cmd, mode) unless block_given?
|
67
|
+
|
68
|
+
Open3.popen2(cmd) { |_i, o, _t| yield o }
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
@@ -82,8 +90,11 @@ class ::IO
|
|
82
90
|
# end
|
83
91
|
|
84
92
|
alias_method :orig_gets, :gets
|
85
|
-
def gets(sep = $/,
|
86
|
-
|
93
|
+
def gets(sep = $/, _limit = nil, _chomp: nil)
|
94
|
+
if sep.is_a?(Integer)
|
95
|
+
sep = $/
|
96
|
+
_limit = sep
|
97
|
+
end
|
87
98
|
sep_size = sep.bytesize
|
88
99
|
|
89
100
|
@gets_buffer ||= +''
|
@@ -92,12 +103,13 @@ class ::IO
|
|
92
103
|
idx = @gets_buffer.index(sep)
|
93
104
|
return @gets_buffer.slice!(0, idx + sep_size) if idx
|
94
105
|
|
95
|
-
data = readpartial(8192)
|
96
|
-
if data
|
106
|
+
if (data = readpartial(8192))
|
97
107
|
@gets_buffer << data
|
98
108
|
else
|
99
109
|
return nil if @gets_buffer.empty?
|
100
|
-
|
110
|
+
|
111
|
+
line = @gets_buffer.freeze
|
112
|
+
@gets_buffer = +''
|
101
113
|
return line
|
102
114
|
end
|
103
115
|
end
|
@@ -130,6 +142,7 @@ class ::IO
|
|
130
142
|
end
|
131
143
|
end
|
132
144
|
write s
|
145
|
+
nil
|
133
146
|
end
|
134
147
|
|
135
148
|
# def readbyte
|
@@ -144,14 +157,13 @@ class ::IO
|
|
144
157
|
# def readlines(sep = $/, limit = nil, chomp: nil)
|
145
158
|
# end
|
146
159
|
|
147
|
-
def write_nonblock(string,
|
160
|
+
def write_nonblock(string, _options = {})
|
148
161
|
# STDOUT << '>'
|
149
162
|
write(string, 0)
|
150
163
|
end
|
151
164
|
|
152
|
-
def read_nonblock(maxlen, buf = nil,
|
165
|
+
def read_nonblock(maxlen, buf = nil, _options = nil)
|
153
166
|
# STDOUT << '<'
|
154
167
|
buf ? readpartial(maxlen, buf) : readpartial(maxlen)
|
155
168
|
end
|
156
|
-
|
157
169
|
end
|
@@ -4,6 +4,7 @@ require 'openssl'
|
|
4
4
|
|
5
5
|
import('./socket')
|
6
6
|
|
7
|
+
# Open ssl socket helper methods (to make it compatible with Socket API)
|
7
8
|
class ::OpenSSL::SSL::SSLSocket
|
8
9
|
def dont_linger
|
9
10
|
io.dont_linger
|
@@ -16,4 +17,4 @@ class ::OpenSSL::SSL::SSLSocket
|
|
16
17
|
def reuse_addr
|
17
18
|
io.reuse_addr
|
18
19
|
end
|
19
|
-
end
|
20
|
+
end
|
@@ -4,31 +4,16 @@ require 'socket'
|
|
4
4
|
|
5
5
|
import('./io')
|
6
6
|
|
7
|
+
# Socket overrides (eventually rewritten in C)
|
7
8
|
class ::Socket
|
8
9
|
NO_EXCEPTION = { exception: false }.freeze
|
9
10
|
|
10
|
-
def accept
|
11
|
-
loop do
|
12
|
-
result, client_addr = accept_nonblock(NO_EXCEPTION)
|
13
|
-
case result
|
14
|
-
when Socket then return result
|
15
|
-
when :wait_readable then read_watcher.await
|
16
|
-
else
|
17
|
-
raise "failed to accept (#{result.inspect})"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
ensure
|
21
|
-
@read_watcher&.stop
|
22
|
-
end
|
23
|
-
|
24
11
|
def connect(remotesockaddr)
|
25
12
|
loop do
|
26
13
|
result = connect_nonblock(remotesockaddr, NO_EXCEPTION)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
else raise IOError
|
31
|
-
end
|
14
|
+
return if result == 0
|
15
|
+
|
16
|
+
result == :wait_writable ? write_watcher.await : (raise IOError)
|
32
17
|
end
|
33
18
|
ensure
|
34
19
|
@write_watcher&.stop
|
@@ -38,12 +23,9 @@ class ::Socket
|
|
38
23
|
outbuf ||= +''
|
39
24
|
loop do
|
40
25
|
result = recv_nonblock(maxlen, flags, outbuf, NO_EXCEPTION)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
else
|
45
|
-
return result
|
46
|
-
end
|
26
|
+
raise IOError unless result
|
27
|
+
|
28
|
+
result == :wait_readable ? read_watcher.await : (return result)
|
47
29
|
end
|
48
30
|
end
|
49
31
|
|
@@ -51,17 +33,15 @@ class ::Socket
|
|
51
33
|
@read_buffer ||= +''
|
52
34
|
loop do
|
53
35
|
result = recvfrom_nonblock(maxlen, flags, @read_buffer, NO_EXCEPTION)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
else return result
|
58
|
-
end
|
36
|
+
raise IOError unless result
|
37
|
+
|
38
|
+
result == :wait_readable ? read_watcher.await : (return result)
|
59
39
|
end
|
60
40
|
ensure
|
61
41
|
@read_watcher&.stop
|
62
42
|
end
|
63
43
|
|
64
|
-
ZERO_LINGER = [0, 0].pack(
|
44
|
+
ZERO_LINGER = [0, 0].pack('ii').freeze
|
65
45
|
|
66
46
|
def dont_linger
|
67
47
|
setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, ZERO_LINGER)
|
@@ -83,46 +63,61 @@ class ::Socket
|
|
83
63
|
end
|
84
64
|
end
|
85
65
|
|
66
|
+
# Overide stock TCPSocket code by encapsulating a Socket instance
|
86
67
|
class ::TCPSocket
|
87
68
|
NO_EXCEPTION = { exception: false }.freeze
|
88
69
|
|
89
|
-
def
|
90
|
-
|
91
|
-
def initialize(remote_host, remote_port, local_host=nil, local_port=nil)
|
70
|
+
def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
|
92
71
|
@io = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
|
93
72
|
if local_host && local_port
|
94
|
-
|
73
|
+
addr = Addrinfo.tcp(local_host, local_port)
|
74
|
+
@io.bind(addr)
|
95
75
|
end
|
96
|
-
|
76
|
+
|
77
|
+
return unless remote_host && remote_port
|
78
|
+
|
79
|
+
addr = Addrinfo.tcp(remote_host, remote_port)
|
80
|
+
@io.connect(addr)
|
97
81
|
end
|
98
82
|
|
83
|
+
alias_method :orig_close, :close
|
99
84
|
def close
|
100
|
-
@io.close
|
85
|
+
@io ? @io.close : orig_close
|
101
86
|
end
|
102
87
|
|
88
|
+
alias_method :orig_setsockopt, :setsockopt
|
103
89
|
def setsockopt(*args)
|
104
|
-
@io.setsockopt(*args)
|
90
|
+
@io ? @io.setsockopt(*args) : orig_setsockopt(*args)
|
105
91
|
end
|
106
92
|
|
93
|
+
alias_method :orig_closed?, :closed?
|
107
94
|
def closed?
|
108
|
-
@io.closed?
|
95
|
+
@io ? @io.closed? : orig_closed?
|
96
|
+
end
|
97
|
+
|
98
|
+
def dont_linger
|
99
|
+
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_LINGER, ::Socket::ZERO_LINGER)
|
100
|
+
end
|
101
|
+
|
102
|
+
def no_delay
|
103
|
+
setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
|
104
|
+
end
|
105
|
+
|
106
|
+
def reuse_addr
|
107
|
+
setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1)
|
109
108
|
end
|
110
109
|
end
|
111
110
|
|
111
|
+
# Override stock TCPServer code by encapsulating a Socket instance.
|
112
112
|
class ::TCPServer
|
113
|
-
|
113
|
+
def initialize(hostname = nil, port = 0)
|
114
|
+
@io = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
|
115
|
+
@io.bind(Addrinfo.tcp(hostname, port))
|
116
|
+
@io.listen(0)
|
117
|
+
end
|
114
118
|
|
119
|
+
alias_method :orig_accept, :accept
|
115
120
|
def accept
|
116
|
-
|
117
|
-
result, client_addr = accept_nonblock(NO_EXCEPTION)
|
118
|
-
case result
|
119
|
-
when TCPSocket then return result
|
120
|
-
when :wait_readable then read_watcher.await
|
121
|
-
else
|
122
|
-
raise "failed to accept (#{result.inspect})"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
ensure
|
126
|
-
@read_watcher&.stop
|
121
|
+
@io ? @io.accept : orig_accept
|
127
122
|
end
|
128
|
-
end
|
123
|
+
end
|
data/lib/polyphony/http.rb
CHANGED
@@ -3,11 +3,12 @@
|
|
3
3
|
require_relative '../polyphony'
|
4
4
|
|
5
5
|
module Polyphony
|
6
|
+
# HTTP imports (loaded dynamically)
|
6
7
|
module HTTP
|
7
8
|
auto_import(
|
8
|
-
Agent:
|
9
|
-
Rack:
|
10
|
-
Server:
|
9
|
+
Agent: './http/agent',
|
10
|
+
Rack: './http/server/rack',
|
11
|
+
Server: './http/server'
|
11
12
|
)
|
12
13
|
end
|
13
14
|
end
|
data/lib/polyphony/http/agent.rb
CHANGED
@@ -9,6 +9,7 @@ require 'json'
|
|
9
9
|
|
10
10
|
ResourcePool = import('../core/resource_pool')
|
11
11
|
|
12
|
+
# Response mixin
|
12
13
|
module ResponseMixin
|
13
14
|
def body
|
14
15
|
self[:body]
|
@@ -49,7 +50,6 @@ class Agent
|
|
49
50
|
request(url, opts.merge(method: :POST))
|
50
51
|
end
|
51
52
|
|
52
|
-
|
53
53
|
def request(url, opts = OPTS_DEFAULT)
|
54
54
|
ctx = request_ctx(url, opts)
|
55
55
|
|
@@ -66,19 +66,20 @@ class Agent
|
|
66
66
|
|
67
67
|
def redirect(url, ctx, opts)
|
68
68
|
url = case url
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
69
|
+
when /^http(?:s)?\:\/\//
|
70
|
+
url
|
71
|
+
when /^\/\/(.+)$/
|
72
|
+
ctx[:uri].scheme + url
|
73
|
+
when /^\//
|
74
|
+
format(
|
75
|
+
'%<scheme>s://%<host>s%<url>s',
|
76
|
+
scheme: ctx[:uri].scheme,
|
77
|
+
host: ctx[:uri].host,
|
78
|
+
url: url
|
79
|
+
)
|
80
|
+
else
|
81
|
+
ctx[:uri] + url
|
82
|
+
end
|
82
83
|
|
83
84
|
request(url, opts)
|
84
85
|
end
|
@@ -88,14 +89,14 @@ class Agent
|
|
88
89
|
method: opts[:method] || :GET,
|
89
90
|
uri: url_to_uri(url, opts),
|
90
91
|
opts: opts,
|
91
|
-
retry: 0
|
92
|
+
retry: 0
|
92
93
|
}
|
93
94
|
end
|
94
95
|
|
95
96
|
def url_to_uri(url, opts)
|
96
97
|
uri = URI(url)
|
97
98
|
if opts[:query]
|
98
|
-
query = opts[:query].map { |k, v| "#{k}=#{v}" }.join(
|
99
|
+
query = opts[:query].map { |k, v| "#{k}=#{v}" }.join('&')
|
99
100
|
if uri.query
|
100
101
|
v.query = "#{uri.query}&#{query}"
|
101
102
|
else
|
@@ -109,23 +110,22 @@ class Agent
|
|
109
110
|
key = uri_key(ctx[:uri])
|
110
111
|
@pools[key].acquire do |state|
|
111
112
|
cancel_after(10) do
|
112
|
-
state[:socket] ||=
|
113
|
+
state[:socket] ||= connect(key)
|
113
114
|
state[:protocol_method] ||= protocol_method(state[:socket], ctx)
|
114
115
|
send(state[:protocol_method], state, ctx)
|
115
|
-
rescue => e
|
116
|
-
state[:socket]&.close
|
116
|
+
rescue Exception => e
|
117
|
+
state[:socket]&.close
|
117
118
|
state.clear
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
119
|
+
|
120
|
+
raise e unless ctx[:retry] < 3
|
121
|
+
|
122
|
+
ctx[:retry] += 1
|
123
|
+
do_request(ctx)
|
124
124
|
end
|
125
125
|
end
|
126
126
|
end
|
127
127
|
|
128
|
-
def protocol_method(socket,
|
128
|
+
def protocol_method(socket, _ctx)
|
129
129
|
if socket.is_a?(::OpenSSL::SSL::SSLSocket) && (socket.alpn_protocol == 'h2')
|
130
130
|
:do_http2
|
131
131
|
else
|
@@ -142,32 +142,31 @@ class Agent
|
|
142
142
|
request = format_http1_request(ctx)
|
143
143
|
|
144
144
|
state[:socket] << request
|
145
|
-
|
146
|
-
parser << state[:socket].readpartial(8192)
|
147
|
-
end
|
145
|
+
parser << state[:socket].readpartial(8192) until done
|
148
146
|
|
149
147
|
{
|
150
|
-
protocol:
|
151
|
-
status_code:
|
152
|
-
headers:
|
153
|
-
body:
|
148
|
+
protocol: 'http1.1',
|
149
|
+
status_code: parser.status_code,
|
150
|
+
headers: parser.headers,
|
151
|
+
body: body
|
154
152
|
}
|
155
153
|
end
|
156
154
|
|
157
155
|
def do_http2(state, ctx)
|
158
156
|
unless state[:http2_client]
|
159
|
-
socket
|
160
|
-
client
|
157
|
+
socket = state[:socket]
|
158
|
+
client = HTTP2::Client.new
|
159
|
+
client.on(:frame) { |bytes| socket << bytes }
|
161
160
|
state[:http2_client] = client
|
162
161
|
end
|
163
162
|
|
164
163
|
stream = state[:http2_client].new_stream # allocate new stream
|
165
164
|
|
166
165
|
headers = {
|
167
|
-
':method'
|
168
|
-
':scheme'
|
169
|
-
':authority'
|
170
|
-
':path'
|
166
|
+
':method' => ctx[:method].to_s,
|
167
|
+
':scheme' => ctx[:uri].scheme,
|
168
|
+
':authority' => [ctx[:uri].host, ctx[:uri].port].join(':'),
|
169
|
+
':path' => ctx[:uri].request_uri
|
171
170
|
}
|
172
171
|
headers.merge!(ctx[:opts][:headers]) if ctx[:opts][:headers]
|
173
172
|
puts "* proxy request headers: #{headers.inspect}"
|
@@ -185,35 +184,47 @@ class Agent
|
|
185
184
|
|
186
185
|
stream.on(:headers) { |h| headers = h.to_h }
|
187
186
|
stream.on(:data) { |c| body << c }
|
188
|
-
stream.on(:close)
|
187
|
+
stream.on(:close) do
|
189
188
|
done = true
|
190
189
|
return {
|
191
|
-
protocol:
|
192
|
-
status_code:
|
193
|
-
headers:
|
194
|
-
body:
|
190
|
+
protocol: 'http2',
|
191
|
+
status_code: headers && headers[':status'].to_i,
|
192
|
+
headers: headers || {},
|
193
|
+
body: body
|
195
194
|
}
|
196
|
-
|
195
|
+
end
|
197
196
|
|
198
|
-
while data = state[:socket].readpartial(8192)
|
197
|
+
while (data = state[:socket].readpartial(8192))
|
199
198
|
state[:http2_client] << data
|
200
199
|
end
|
201
200
|
ensure
|
202
|
-
|
201
|
+
stream.close unless done
|
203
202
|
end
|
204
203
|
|
205
|
-
HTTP1_REQUEST =
|
204
|
+
HTTP1_REQUEST = <<~HTTP.gsub("\n", "\r\n")
|
205
|
+
%<method>s %<request>s HTTP/1.1
|
206
|
+
Host: %<host>s
|
207
|
+
%<headers>s
|
208
|
+
|
209
|
+
HTTP
|
206
210
|
|
207
211
|
def format_http1_request(ctx)
|
208
|
-
headers = ctx
|
212
|
+
headers = format_headers(ctx)
|
209
213
|
puts "* proxy request headers: #{headers.inspect}"
|
210
214
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
215
|
+
format(
|
216
|
+
HTTP1_REQUEST,
|
217
|
+
method: ctx[:method],
|
218
|
+
request: ctx[:uri].request_uri,
|
219
|
+
host: ctx[:uri].host,
|
220
|
+
headers: headers
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
224
|
+
def format_headers(headers)
|
225
|
+
return nil unless ctx[:opts][:headers]
|
226
|
+
|
227
|
+
headers.map { |k, v| "#{k}: #{v}\r\n" }.join
|
217
228
|
end
|
218
229
|
|
219
230
|
def uri_key(uri)
|
@@ -224,7 +235,7 @@ class Agent
|
|
224
235
|
}
|
225
236
|
end
|
226
237
|
|
227
|
-
SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }
|
238
|
+
SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
|
228
239
|
|
229
240
|
def connect(key)
|
230
241
|
case key[:scheme]
|
@@ -236,4 +247,4 @@ class Agent
|
|
236
247
|
raise "Invalid scheme #{key[:scheme].inspect}"
|
237
248
|
end
|
238
249
|
end
|
239
|
-
end
|
250
|
+
end
|