polyphony 0.19 → 0.20

Sign up to get free protection for your applications and to get access to all the features.
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