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.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +87 -1
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile.lock +17 -6
  6. data/README.md +200 -139
  7. data/Rakefile +4 -4
  8. data/TODO.md +35 -7
  9. data/bin/poly +11 -0
  10. data/docs/getting-started/getting-started.md +1 -1
  11. data/docs/summary.md +3 -0
  12. data/docs/technical-overview/exception-handling.md +94 -0
  13. data/docs/technical-overview/fiber-scheduling.md +99 -0
  14. data/examples/core/cancel.rb +8 -4
  15. data/examples/core/channel_echo.rb +18 -17
  16. data/examples/core/defer.rb +12 -0
  17. data/examples/core/enumerator.rb +4 -4
  18. data/examples/core/fiber_error.rb +9 -0
  19. data/examples/core/fiber_error_with_backtrace.rb +73 -0
  20. data/examples/core/fork.rb +6 -6
  21. data/examples/core/genserver.rb +16 -8
  22. data/examples/core/lock.rb +3 -3
  23. data/examples/core/move_on.rb +4 -3
  24. data/examples/core/move_on_twice.rb +5 -5
  25. data/examples/core/move_on_with_ensure.rb +8 -11
  26. data/examples/core/move_on_with_value.rb +14 -0
  27. data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
  28. data/examples/core/nested_cancel.rb +5 -5
  29. data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
  30. data/examples/core/nested_spin.rb +17 -0
  31. data/examples/core/pingpong.rb +21 -0
  32. data/examples/core/pulse.rb +4 -5
  33. data/examples/core/resource.rb +6 -4
  34. data/examples/core/resource_cancel.rb +6 -9
  35. data/examples/core/resource_delegate.rb +3 -3
  36. data/examples/core/sleep.rb +3 -3
  37. data/examples/core/sleep_spin.rb +19 -0
  38. data/examples/core/snooze.rb +32 -0
  39. data/examples/core/spin.rb +14 -0
  40. data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
  41. data/examples/core/spin_error.rb +17 -0
  42. data/examples/core/spin_error_backtrace.rb +30 -0
  43. data/examples/core/spin_uncaught_error.rb +15 -0
  44. data/examples/core/supervisor.rb +8 -8
  45. data/examples/core/supervisor_with_cancel_scope.rb +7 -7
  46. data/examples/core/supervisor_with_error.rb +8 -8
  47. data/examples/core/supervisor_with_manual_move_on.rb +6 -7
  48. data/examples/core/suspend.rb +13 -0
  49. data/examples/core/thread.rb +1 -1
  50. data/examples/core/thread_cancel.rb +9 -11
  51. data/examples/core/thread_pool.rb +18 -14
  52. data/examples/core/throttle.rb +7 -7
  53. data/examples/core/timeout.rb +3 -3
  54. data/examples/fs/read.rb +7 -9
  55. data/examples/http/config.ru +7 -3
  56. data/examples/http/cuba.ru +22 -0
  57. data/examples/http/happy_eyeballs.rb +6 -4
  58. data/examples/http/http_client.rb +1 -1
  59. data/examples/http/http_get.rb +1 -1
  60. data/examples/http/http_parse_experiment.rb +21 -16
  61. data/examples/http/http_proxy.rb +28 -26
  62. data/examples/http/http_server.rb +10 -10
  63. data/examples/http/http_server_forked.rb +6 -5
  64. data/examples/http/http_server_throttled.rb +3 -3
  65. data/examples/http/http_ws_server.rb +11 -11
  66. data/examples/http/https_raw_client.rb +1 -1
  67. data/examples/http/https_server.rb +8 -8
  68. data/examples/http/https_wss_server.rb +13 -11
  69. data/examples/http/rack_server.rb +2 -2
  70. data/examples/http/rack_server_https.rb +4 -4
  71. data/examples/http/rack_server_https_forked.rb +5 -5
  72. data/examples/http/websocket_secure_server.rb +6 -6
  73. data/examples/http/websocket_server.rb +5 -5
  74. data/examples/interfaces/pg_client.rb +4 -4
  75. data/examples/interfaces/pg_pool.rb +13 -6
  76. data/examples/interfaces/pg_transaction.rb +5 -4
  77. data/examples/interfaces/redis_channels.rb +15 -11
  78. data/examples/interfaces/redis_client.rb +2 -2
  79. data/examples/interfaces/redis_pubsub.rb +2 -1
  80. data/examples/interfaces/redis_pubsub_perf.rb +13 -9
  81. data/examples/io/backticks.rb +11 -0
  82. data/examples/io/cat.rb +4 -5
  83. data/examples/io/echo_client.rb +9 -4
  84. data/examples/io/echo_client_from_stdin.rb +20 -0
  85. data/examples/io/echo_pipe.rb +7 -8
  86. data/examples/io/echo_server.rb +8 -6
  87. data/examples/io/echo_server_with_timeout.rb +13 -10
  88. data/examples/io/echo_stdin.rb +3 -3
  89. data/examples/io/httparty.rb +2 -2
  90. data/examples/io/httparty_multi.rb +8 -4
  91. data/examples/io/httparty_threaded.rb +6 -2
  92. data/examples/io/io_read.rb +2 -2
  93. data/examples/io/irb.rb +16 -4
  94. data/examples/io/net-http.rb +3 -3
  95. data/examples/io/open.rb +17 -0
  96. data/examples/io/system.rb +3 -3
  97. data/examples/io/tcpserver.rb +15 -0
  98. data/examples/io/tcpsocket.rb +6 -5
  99. data/examples/performance/multi_snooze.rb +29 -0
  100. data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
  101. data/examples/performance/snooze_raw.rb +39 -0
  102. data/ext/gyro/async.c +165 -0
  103. data/ext/gyro/child.c +167 -0
  104. data/ext/{ev → gyro}/extconf.rb +4 -3
  105. data/ext/gyro/gyro.c +316 -0
  106. data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
  107. data/ext/gyro/gyro_ext.c +23 -0
  108. data/ext/{ev → gyro}/io.c +65 -57
  109. data/ext/{ev → gyro}/libev.h +0 -0
  110. data/ext/gyro/signal.c +117 -0
  111. data/ext/{ev → gyro}/socket.c +61 -6
  112. data/ext/gyro/timer.c +199 -0
  113. data/ext/libev/Changes +35 -0
  114. data/ext/libev/README +2 -1
  115. data/ext/libev/ev.c +213 -151
  116. data/ext/libev/ev.h +95 -88
  117. data/ext/libev/ev_epoll.c +26 -15
  118. data/ext/libev/ev_kqueue.c +11 -5
  119. data/ext/libev/ev_linuxaio.c +642 -0
  120. data/ext/libev/ev_poll.c +13 -8
  121. data/ext/libev/ev_port.c +5 -2
  122. data/ext/libev/ev_vars.h +14 -3
  123. data/ext/libev/ev_wrap.h +16 -0
  124. data/lib/ev_ext.bundle +0 -0
  125. data/lib/polyphony.rb +46 -50
  126. data/lib/polyphony/auto_run.rb +12 -0
  127. data/lib/polyphony/core/cancel_scope.rb +11 -7
  128. data/lib/polyphony/core/channel.rb +16 -9
  129. data/lib/polyphony/core/coprocess.rb +101 -51
  130. data/lib/polyphony/core/exceptions.rb +14 -12
  131. data/lib/polyphony/core/resource_pool.rb +21 -8
  132. data/lib/polyphony/core/supervisor.rb +10 -5
  133. data/lib/polyphony/core/sync.rb +7 -6
  134. data/lib/polyphony/core/thread.rb +4 -4
  135. data/lib/polyphony/core/thread_pool.rb +4 -4
  136. data/lib/polyphony/core/throttler.rb +6 -4
  137. data/lib/polyphony/extensions/core.rb +253 -0
  138. data/lib/polyphony/extensions/io.rb +28 -16
  139. data/lib/polyphony/extensions/openssl.rb +2 -1
  140. data/lib/polyphony/extensions/socket.rb +47 -52
  141. data/lib/polyphony/http.rb +4 -3
  142. data/lib/polyphony/http/agent.rb +68 -57
  143. data/lib/polyphony/http/server.rb +5 -5
  144. data/lib/polyphony/http/server/http1.rb +268 -0
  145. data/lib/polyphony/http/server/http2.rb +62 -0
  146. data/lib/polyphony/http/server/http2_stream.rb +104 -0
  147. data/lib/polyphony/http/server/rack.rb +64 -0
  148. data/lib/polyphony/http/server/request.rb +119 -0
  149. data/lib/polyphony/net.rb +26 -15
  150. data/lib/polyphony/postgres.rb +17 -13
  151. data/lib/polyphony/redis.rb +16 -15
  152. data/lib/polyphony/version.rb +1 -1
  153. data/lib/polyphony/websocket.rb +11 -4
  154. data/polyphony.gemspec +13 -9
  155. data/test/eg.rb +27 -0
  156. data/test/helper.rb +25 -0
  157. data/test/run.rb +5 -0
  158. data/test/test_async.rb +33 -0
  159. data/test/test_coprocess.rb +239 -77
  160. data/test/test_core.rb +95 -61
  161. data/test/test_gyro.rb +148 -0
  162. data/test/test_http_server.rb +313 -0
  163. data/test/test_io.rb +79 -27
  164. data/test/test_kernel.rb +22 -12
  165. data/test/test_signal.rb +36 -0
  166. data/test/test_timer.rb +24 -0
  167. metadata +89 -33
  168. data/examples/core/nested_async.rb +0 -17
  169. data/examples/core/next_tick.rb +0 -12
  170. data/examples/core/sleep_spawn.rb +0 -19
  171. data/examples/core/spawn.rb +0 -14
  172. data/examples/core/spawn_error.rb +0 -28
  173. data/examples/performance/perf_multi_snooze.rb +0 -21
  174. data/ext/ev/async.c +0 -168
  175. data/ext/ev/child.c +0 -169
  176. data/ext/ev/ev_ext.c +0 -23
  177. data/ext/ev/ev_module.c +0 -242
  178. data/ext/ev/signal.c +0 -119
  179. data/ext/ev/timer.c +0 -197
  180. data/lib/polyphony/core/fiber_pool.rb +0 -98
  181. data/lib/polyphony/extensions/kernel.rb +0 -169
  182. data/lib/polyphony/http/http1_adapter.rb +0 -254
  183. data/lib/polyphony/http/http2_adapter.rb +0 -157
  184. data/lib/polyphony/http/rack.rb +0 -25
  185. data/lib/polyphony/http/request.rb +0 -66
  186. 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
- sep, limit = $/, sep if sep.is_a?(Integer)
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
- opt, length = length, nil if length.is_a?(Hash)
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(*args)
58
- Open3.popen2(*args) do |i, o, t|
59
- yield o
60
- end
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 = $/, limit = nil, chomp: nil)
86
- sep, limit = $/, sep if sep.is_a?(Integer)
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
- line, @gets_buffer = @gets_buffer.freeze, +''
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, options = {})
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, options = 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
- case result
28
- when 0 then return
29
- when :wait_writable then write_watcher.await
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
- case result
42
- when :wait_readable
43
- read_watcher.await
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
- case result
55
- when nil then raise IOError
56
- when :wait_readable then read_watcher.await
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("ii")
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 foo; :bar; end
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
- @io.bind(Addrinfo.tcp(local_host, local_port))
73
+ addr = Addrinfo.tcp(local_host, local_port)
74
+ @io.bind(addr)
95
75
  end
96
- @io.connect(Addrinfo.tcp(remote_host, remote_port))
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
- NO_EXCEPTION = { exception: false }.freeze
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
- loop do
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
@@ -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: './http/agent',
9
- Rack: './http/rack',
10
- Server: './http/server',
9
+ Agent: './http/agent',
10
+ Rack: './http/server/rack',
11
+ Server: './http/server'
11
12
  )
12
13
  end
13
14
  end
@@ -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
- when /^http(?:s)?\:\/\//
70
- url
71
- when /^\/\/(.+)$/
72
- ctx[:uri].scheme + url
73
- when /^\//
74
- "%s://%s%s" % [
75
- ctx[:uri].scheme,
76
- ctx[:uri].host,
77
- url
78
- ]
79
- else
80
- ctx[:uri] + url
81
- end
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] ||= connect(key)
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 rescue nil
116
+ rescue Exception => e
117
+ state[:socket]&.close
117
118
  state.clear
118
- if ctx[:retry] < 3
119
- ctx[:retry] += 1
120
- do_request(ctx)
121
- else
122
- raise e
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, ctx)
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
- while !done
146
- parser << state[:socket].readpartial(8192)
147
- end
145
+ parser << state[:socket].readpartial(8192) until done
148
146
 
149
147
  {
150
- protocol: 'http1.1',
151
- status_code: parser.status_code,
152
- headers: parser.headers,
153
- body: 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, client = state[:socket], HTTP2::Client.new
160
- client.on(:frame) {|bytes| socket << bytes }
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' => ctx[:method].to_s,
168
- ':scheme' => ctx[:uri].scheme,
169
- ':authority' => [ctx[:uri].host, ctx[:uri].port].join(':'),
170
- ':path' => ctx[:uri].request_uri,
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: 'http2',
192
- status_code: headers && headers[':status'].to_i,
193
- headers: headers || {},
194
- body: 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
- (stream.close rescue nil) unless done
201
+ stream.close unless done
203
202
  end
204
203
 
205
- HTTP1_REQUEST = "%<method>s %<request>s HTTP/1.1\r\nHost: %<host>s\r\n%<headers>s\r\n"
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[:opts][:headers] ? ctx[:opts][:headers].map { |k, v| "#{k}: #{v}\r\n"}.join : nil
212
+ headers = format_headers(ctx)
209
213
  puts "* proxy request headers: #{headers.inspect}"
210
214
 
211
- HTTP1_REQUEST % {
212
- method: ctx[:method],
213
- request: ctx[:uri].request_uri,
214
- host: ctx[:uri].host,
215
- headers: headers
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