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
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :load
4
+
5
+ require 'rack'
6
+
7
+ def run(app)
8
+ ->(req) { respond(req, app.(env(req))) }
9
+ end
10
+
11
+ def load(path)
12
+ src = IO.read(path)
13
+ instance_eval(src, path, 1)
14
+ end
15
+
16
+ # Implements a rack input stream:
17
+ # https://www.rubydoc.info/github/rack/rack/master/file/SPEC#label-The+Input+Stream
18
+ class InputStream
19
+ def initialize(request)
20
+ @request = request
21
+ end
22
+
23
+ def gets; end
24
+
25
+ def read(length = nil, outbuf = nil); end
26
+
27
+ def each(&block)
28
+ @request.each_chunk(&block)
29
+ end
30
+
31
+ def rewind; end
32
+ end
33
+
34
+ def env(request)
35
+ {
36
+ 'REQUEST_METHOD' => request.method,
37
+ 'SCRIPT_NAME' => '',
38
+ 'PATH_INFO' => request.path,
39
+ 'QUERY_STRING' => request.query_string || '',
40
+ 'SERVER_NAME' => request.headers['Host'], # ?
41
+ 'SERVER_PORT' => '80', # ?
42
+ 'rack.version' => Rack::VERSION,
43
+ 'rack.url_scheme' => 'https', # ?
44
+ 'rack.input' => InputStream.new(request),
45
+ 'rack.errors' => STDERR, # ?
46
+ 'rack.multithread' => false,
47
+ 'rack.run_once' => false,
48
+ 'rack.hijack?' => false,
49
+ 'rack.hijack' => nil,
50
+ 'rack.hijack_io' => nil,
51
+ 'rack.session' => nil,
52
+ 'rack.logger' => nil,
53
+ 'rack.multipart.buffer_size' => nil,
54
+ 'rack.multipar.tempfile_factory' => nil
55
+ }.tap do |env|
56
+ request.headers.each { |k, v| env["HTTP_#{k.upcase}"] = v }
57
+ end
58
+ end
59
+
60
+ def respond(request, (status_code, headers, body))
61
+ headers[':status'] = status_code.to_s
62
+ puts "headers: #{headers.inspect}"
63
+ request.respond(body.first, headers)
64
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ export_default :Request
4
+
5
+ require 'uri'
6
+
7
+ # HTTP request
8
+ class Request
9
+ attr_reader :headers, :adapter
10
+ attr_accessor :__next__
11
+
12
+ def initialize(headers, adapter)
13
+ @headers = headers
14
+ @adapter = adapter
15
+ end
16
+
17
+ def protocol
18
+ @protocol = @adapter.protocol
19
+ end
20
+
21
+ def method
22
+ @method ||= @headers[':method']
23
+ end
24
+
25
+ def scheme
26
+ @scheme ||= @headers[':scheme']
27
+ end
28
+
29
+ def uri
30
+ @uri ||= URI.parse(@headers[':path'] || '')
31
+ end
32
+
33
+ def path
34
+ @path ||= uri.path
35
+ end
36
+
37
+ def query_string
38
+ @query_string ||= uri.query
39
+ end
40
+
41
+ def query
42
+ return @query if @query
43
+
44
+ @query = (q = uri.query) ? split_query_string(q) : {}
45
+ end
46
+
47
+ def split_query_string(query)
48
+ query.split('&').each_with_object({}) do |kv, h|
49
+ k, v = kv.split('=')
50
+ h[k.to_sym] = URI.decode_www_form_component(v)
51
+ end
52
+ end
53
+
54
+ def buffer_body_chunk(chunk)
55
+ @buffered_body_chunks ||= []
56
+ @buffered_body_chunks << chunk
57
+ end
58
+
59
+ def each_chunk(&block)
60
+ if @buffered_body_chunks
61
+ puts 'serve buffered body_chunks'
62
+ @buffered_body_chunks.each(&block)
63
+ @buffered_body_chunks = nil
64
+ end
65
+ while !@message_complete && (chunk = @adapter.get_body_chunk)
66
+ yield chunk
67
+ end
68
+ end
69
+
70
+ def complete!(keep_alive = nil)
71
+ @message_complete = true
72
+ @keep_alive = keep_alive
73
+ end
74
+
75
+ def complete?
76
+ @message_complete
77
+ end
78
+
79
+ def consume
80
+ @adapter.consume_request
81
+ end
82
+
83
+ def keep_alive?
84
+ @keep_alive
85
+ end
86
+
87
+ def read
88
+ buf = @buffered_body_chunks ? @buffered_body_chunks.join : +''
89
+ while (chunk = @adapter.get_body_chunk)
90
+ buf << chunk
91
+ end
92
+ buf
93
+ end
94
+
95
+ def respond(body, headers = {})
96
+ @adapter.respond(body, headers)
97
+ @headers_sent = true
98
+ end
99
+
100
+ def send_headers(headers = {}, empty_response = false)
101
+ return if @headers_sent
102
+
103
+ @headers_sent = true
104
+ @adapter.send_headers(headers, empty_response: empty_response)
105
+ end
106
+
107
+ def send_chunk(body, done: false)
108
+ send_headers({}) unless @headers_sent
109
+
110
+ @adapter.send_chunk(body, done: done)
111
+ end
112
+ alias_method :<<, :send_chunk
113
+
114
+ def finish
115
+ send_headers({}) unless @headers_sent
116
+
117
+ @adapter.finish
118
+ end
119
+ end
data/lib/polyphony/net.rb CHANGED
@@ -7,10 +7,10 @@ import('./extensions/socket')
7
7
  import('./extensions/openssl')
8
8
 
9
9
  def tcp_connect(host, port, opts = {})
10
- socket = ::Socket.new(:INET, :STREAM).tap { |s|
10
+ socket = ::Socket.new(:INET, :STREAM).tap do |s|
11
11
  addr = ::Socket.sockaddr_in(port, host)
12
12
  s.connect(addr)
13
- }
13
+ end
14
14
  if opts[:secure_context] || opts[:secure]
15
15
  secure_socket(socket, opts[:secure_context], opts.merge(host: host))
16
16
  else
@@ -20,27 +20,30 @@ end
20
20
 
21
21
  def tcp_listen(host = nil, port = nil, opts = {})
22
22
  host ||= '0.0.0.0'
23
- raise "Port number not specified" unless port
24
- socket = ::Socket.new(:INET, :STREAM).tap { |s|
23
+ raise 'Port number not specified' unless port
24
+
25
+ socket = socket_from_options(host, port, opts)
26
+ if opts[:secure_context] || opts[:secure]
27
+ secure_server(socket, opts[:secure_context], opts)
28
+ else
29
+ socket
30
+ end
31
+ end
32
+
33
+ def socket_from_options(host, port, opts)
34
+ ::Socket.new(:INET, :STREAM).tap do |s|
25
35
  s.reuse_addr if opts[:reuse_addr]
26
36
  s.dont_linger if opts[:dont_linger]
27
37
  addr = ::Socket.sockaddr_in(port, host)
28
38
  s.bind(addr)
29
39
  s.listen(0)
30
- }
31
- if opts[:secure_context] || opts[:secure]
32
- secure_server(socket, opts[:secure_context], opts)
33
- else
34
- socket
35
40
  end
36
41
  end
37
42
 
38
43
  def secure_socket(socket, context, opts)
39
44
  setup_alpn(context, opts[:alpn_protocols]) if context && opts[:alpn_protocols]
40
- socket = context ?
41
- OpenSSL::SSL::SSLSocket.new(socket, context) :
42
- OpenSSL::SSL::SSLSocket.new(socket)
43
-
45
+ socket = secure_socket_wrapper(socket, context)
46
+
44
47
  socket.tap do |s|
45
48
  s.hostname = opts[:host] if opts[:host]
46
49
  s.connect
@@ -48,6 +51,14 @@ def secure_socket(socket, context, opts)
48
51
  end
49
52
  end
50
53
 
54
+ def secure_socket_wrapper(socket, context)
55
+ if context
56
+ OpenSSL::SSL::SSLSocket.new(socket, context)
57
+ else
58
+ OpenSSL::SSL::SSLSocket.new(socket)
59
+ end
60
+ end
61
+
51
62
  def secure_server(socket, context, opts)
52
63
  setup_alpn(context, opts[:alpn_protocols]) if opts[:alpn_protocols]
53
64
  OpenSSL::SSL::SSLServer.new(socket, context)
@@ -55,7 +66,7 @@ end
55
66
 
56
67
  def setup_alpn(context, protocols)
57
68
  context.alpn_protocols = protocols
58
- context.alpn_select_cb = ->(peer_protocols) {
69
+ context.alpn_select_cb = lambda do |peer_protocols|
59
70
  (protocols & peer_protocols).first
60
- }
71
+ end
61
72
  end
@@ -3,21 +3,20 @@
3
3
  require_relative '../polyphony'
4
4
  require 'pg'
5
5
 
6
+ # PG overrides
6
7
  module ::PG
7
8
  def self.connect(*args)
8
9
  Connection.connect_start(*args).tap(&method(:connect_async))
9
10
  end
10
-
11
+
11
12
  def self.connect_async(conn)
12
13
  loop do
13
14
  res = conn.connect_poll
14
15
  case res
15
- when PGRES_POLLING_FAILED then raise Error.new(conn.error_message)
16
+ when PGRES_POLLING_FAILED then raise Error, conn.error_message
16
17
  when PGRES_POLLING_READING then conn.socket_io.read_watcher.await
17
18
  when PGRES_POLLING_WRITING then conn.socket_io.write_watcher.await
18
- when PGRES_POLLING_OK then
19
- conn.setnonblocking(true)
20
- return
19
+ when PGRES_POLLING_OK then return conn.setnonblocking(true)
21
20
  end
22
21
  end
23
22
  end
@@ -26,8 +25,9 @@ module ::PG
26
25
  loop do
27
26
  res = conn.connect_poll
28
27
  case res
29
- when PGRES_POLLING_FAILED then raise Error.new(conn.error_message)
30
- when PGRES_POLLING_OK then
28
+ when PGRES_POLLING_FAILED
29
+ raise Error, conn.error_message
30
+ when PGRES_POLLING_OK
31
31
  conn.setnonblocking(true)
32
32
  return
33
33
  end
@@ -35,9 +35,10 @@ module ::PG
35
35
  end
36
36
  end
37
37
 
38
+ # Overrides for PG connection
38
39
  class ::PG::Connection
39
40
  alias_method :orig_get_result, :get_result
40
-
41
+
41
42
  def get_result(&block)
42
43
  while is_busy
43
44
  socket_io.read_watcher.await
@@ -55,7 +56,7 @@ class ::PG::Connection
55
56
  while get_result; end
56
57
  end
57
58
 
58
- def block(timeout = 0)
59
+ def block(_timeout = 0)
59
60
  while is_busy
60
61
  socket_io.read_watcher.await
61
62
  consume_input
@@ -70,20 +71,23 @@ class ::PG::Connection
70
71
  # error is raised, the transaction is rolled back and the error is raised
71
72
  # again.
72
73
  # @return [void]
73
- def transaction
74
- began = false
74
+ def transaction(&block)
75
75
  return yield if @transaction # allow nesting of calls to #transactions
76
76
 
77
+ perform_transaction(&block)
78
+ end
79
+
80
+ def perform_transaction
77
81
  query(SQL_BEGIN)
78
82
  began = true
79
83
  @transaction = true
80
84
  yield
81
85
  query(SQL_COMMIT)
82
86
  rescue StandardError => e
83
- (query(SQL_ROLLBACK) rescue nil) if began
87
+ query(SQL_ROLLBACK) if began
84
88
  raise e
85
89
  ensure
86
- @transaction = false if began
90
+ @transaction = false
87
91
  end
88
92
 
89
93
  self.async_api = true
@@ -2,21 +2,22 @@
2
2
 
3
3
  require_relative '../polyphony'
4
4
 
5
- require "redis"
6
- require "hiredis/reader"
5
+ require 'redis'
6
+ require 'hiredis/reader'
7
7
 
8
+ # Polyphony-based Redis driver
8
9
  class Driver
9
10
  def self.connect(config)
10
- if config[:scheme] == "unix"
11
- raise "unix sockets not supported"
12
- # connection.connect_unix(config[:path], connect_timeout)
13
- elsif config[:scheme] == "rediss" || config[:ssl]
14
- raise "ssl not supported"
15
- # raise NotImplementedError, "SSL not supported by hiredis driver"
16
- else
17
- new(config[:host], config[:port])
18
- # connection.connect(config[:host], config[:port], connect_timeout)
19
- end
11
+ raise 'unix sockets not supported' if config[:scheme] == 'unix'
12
+
13
+ # connection.connect_unix(config[:path], connect_timeout)
14
+
15
+ raise 'ssl not supported' if config[:scheme] == 'rediss' || config[:ssl]
16
+
17
+ # raise NotImplementedError, "SSL not supported by hiredis driver"
18
+
19
+ new(config[:host], config[:port])
20
+ # connection.connect(config[:host], config[:port], connect_timeout)
20
21
  end
21
22
 
22
23
  def initialize(host, port)
@@ -53,13 +54,13 @@ class Driver
53
54
  def read
54
55
  reply = @reader.gets
55
56
  return reply if reply
56
-
57
+
57
58
  while (data = @connection.readpartial(8192))
58
59
  @reader.feed(data)
59
60
  reply = @reader.gets
60
- return reply if reply
61
+ return reply unless reply == false
61
62
  end
62
63
  end
63
64
  end
64
65
 
65
- Redis::Connection.drivers << Driver
66
+ Redis::Connection.drivers << Driver
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.19'
4
+ VERSION = '0.20'
5
5
  end
@@ -5,6 +5,7 @@ export :handler
5
5
  require 'digest/sha1'
6
6
  require 'websocket'
7
7
 
8
+ # Websocket connection
8
9
  class WebsocketConnection
9
10
  def initialize(client, headers)
10
11
  @client = client
@@ -13,24 +14,30 @@ class WebsocketConnection
13
14
  end
14
15
 
15
16
  S_WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
16
- UPGRADE_RESPONSE = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n"
17
+ UPGRADE_RESPONSE = <<~HTTP.gsub("\n", "\r\n")
18
+ HTTP/1.1 101 Switching Protocols
19
+ Upgrade: websocket
20
+ Connection: Upgrade
21
+ Sec-WebSocket-Accept: %<accept>s
22
+
23
+ HTTP
17
24
 
18
25
  def setup(headers)
19
26
  key = headers['Sec-WebSocket-Key']
20
27
  @version = headers['Sec-WebSocket-Version'].to_i
21
28
  accept = Digest::SHA1.base64digest([key, S_WS_GUID].join)
22
- @client << UPGRADE_RESPONSE % accept
29
+ @client << format(UPGRADE_RESPONSE, accept: accept)
23
30
 
24
31
  @reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
25
32
  end
26
33
 
27
34
  def recv
28
- while true
35
+ loop do
29
36
  data = @client.readpartial(8192)
30
37
  break nil unless data
31
38
 
32
39
  @reader << data
33
- if msg = @reader.next
40
+ if (msg = @reader.next)
34
41
  break msg.to_s
35
42
  end
36
43
  end