polyphony 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +5 -0
  3. data/.gitignore +55 -0
  4. data/.rubocop.yml +49 -0
  5. data/CHANGELOG.md +13 -2
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +31 -0
  8. data/LICENSE +21 -0
  9. data/README.md +35 -18
  10. data/Rakefile +20 -0
  11. data/TODO.md +49 -0
  12. data/docs/getting-started/getting-started.md +10 -0
  13. data/docs/getting-started/tutorial.md +2 -0
  14. data/docs/summary.md +9 -0
  15. data/examples/core/cancel.rb +10 -0
  16. data/examples/core/channel_echo.rb +43 -0
  17. data/examples/core/enumerator.rb +14 -0
  18. data/examples/core/fork.rb +22 -0
  19. data/examples/core/genserver.rb +74 -0
  20. data/examples/core/lock.rb +20 -0
  21. data/examples/core/move_on.rb +11 -0
  22. data/examples/core/move_on_twice.rb +17 -0
  23. data/examples/core/move_on_with_ensure.rb +17 -0
  24. data/examples/core/multiple_async.rb +17 -0
  25. data/examples/core/nested_async.rb +18 -0
  26. data/examples/core/nested_cancel.rb +41 -0
  27. data/examples/core/nested_multiple_async.rb +19 -0
  28. data/examples/core/next_tick.rb +13 -0
  29. data/examples/core/pulse.rb +13 -0
  30. data/examples/core/resource.rb +29 -0
  31. data/examples/core/resource_cancel.rb +34 -0
  32. data/examples/core/resource_delegate.rb +32 -0
  33. data/examples/core/sleep.rb +9 -0
  34. data/examples/core/sleep2.rb +13 -0
  35. data/examples/core/spawn.rb +15 -0
  36. data/examples/core/spawn_cancel.rb +19 -0
  37. data/examples/core/spawn_error.rb +28 -0
  38. data/examples/core/supervisor.rb +22 -0
  39. data/examples/core/supervisor_with_cancel_scope.rb +24 -0
  40. data/examples/core/supervisor_with_error.rb +23 -0
  41. data/examples/core/supervisor_with_manual_move_on.rb +25 -0
  42. data/examples/core/thread.rb +30 -0
  43. data/examples/core/thread_cancel.rb +30 -0
  44. data/examples/core/thread_pool.rb +60 -0
  45. data/examples/core/throttle.rb +17 -0
  46. data/examples/fs/read.rb +37 -0
  47. data/examples/interfaces/pg_client.rb +38 -0
  48. data/examples/interfaces/pg_pool.rb +37 -0
  49. data/examples/interfaces/pg_query.rb +32 -0
  50. data/examples/interfaces/redis_channels.rb +119 -0
  51. data/examples/interfaces/redis_client.rb +21 -0
  52. data/examples/interfaces/redis_pubsub.rb +26 -0
  53. data/examples/interfaces/redis_pubsub_perf.rb +65 -0
  54. data/examples/io/config.ru +3 -0
  55. data/examples/io/echo_client.rb +22 -0
  56. data/examples/io/echo_server.rb +14 -0
  57. data/examples/io/echo_server_with_timeout.rb +33 -0
  58. data/examples/io/echo_stdin.rb +15 -0
  59. data/examples/io/happy_eyeballs.rb +32 -0
  60. data/examples/io/http_client.rb +19 -0
  61. data/examples/io/http_server.js +24 -0
  62. data/examples/io/http_server.rb +16 -0
  63. data/examples/io/http_server_forked.rb +27 -0
  64. data/examples/io/http_server_throttled.rb +16 -0
  65. data/examples/io/http_ws_server.rb +42 -0
  66. data/examples/io/https_client.rb +17 -0
  67. data/examples/io/https_server.rb +23 -0
  68. data/examples/io/https_wss_server.rb +46 -0
  69. data/examples/io/rack_server.rb +19 -0
  70. data/examples/io/rack_server_https.rb +24 -0
  71. data/examples/io/rack_server_https_forked.rb +32 -0
  72. data/examples/io/websocket_server.rb +33 -0
  73. data/examples/io/ws_page.html +34 -0
  74. data/examples/io/wss_page.html +34 -0
  75. data/examples/performance/perf_multi_snooze.rb +21 -0
  76. data/examples/performance/perf_snooze.rb +30 -0
  77. data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
  78. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  79. data/examples/streams/lines.rb +27 -0
  80. data/examples/streams/stdio.rb +18 -0
  81. data/ext/ev/async.c +168 -0
  82. data/ext/ev/child.c +169 -0
  83. data/ext/ev/ev.h +32 -0
  84. data/ext/ev/ev_ext.c +20 -0
  85. data/ext/ev/ev_module.c +222 -0
  86. data/ext/ev/io.c +405 -0
  87. data/ext/ev/libev.h +9 -0
  88. data/ext/ev/signal.c +119 -0
  89. data/ext/ev/timer.c +197 -0
  90. data/ext/libev/Changes +513 -0
  91. data/ext/libev/LICENSE +37 -0
  92. data/ext/libev/README +58 -0
  93. data/ext/libev/README.embed +3 -0
  94. data/ext/libev/ev.c +5214 -0
  95. data/ext/libev/ev.h +849 -0
  96. data/ext/libev/ev_epoll.c +285 -0
  97. data/ext/libev/ev_kqueue.c +218 -0
  98. data/ext/libev/ev_poll.c +151 -0
  99. data/ext/libev/ev_port.c +189 -0
  100. data/ext/libev/ev_select.c +316 -0
  101. data/ext/libev/ev_vars.h +204 -0
  102. data/ext/libev/ev_win32.c +162 -0
  103. data/ext/libev/ev_wrap.h +200 -0
  104. data/ext/libev/test_libev_win32.c +123 -0
  105. data/lib/polyphony.rb +7 -2
  106. data/lib/polyphony/core.rb +1 -1
  107. data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
  108. data/lib/polyphony/core/exceptions.rb +5 -5
  109. data/lib/polyphony/core/supervisor.rb +16 -16
  110. data/lib/polyphony/core/thread.rb +1 -1
  111. data/lib/polyphony/extensions/io.rb +43 -42
  112. data/lib/polyphony/extensions/kernel.rb +10 -34
  113. data/lib/polyphony/extensions/postgres.rb +3 -2
  114. data/lib/polyphony/extensions/redis.rb +1 -1
  115. data/lib/polyphony/extensions/socket.rb +8 -4
  116. data/lib/polyphony/extensions/ssl.rb +0 -54
  117. data/lib/polyphony/http/agent.rb +4 -10
  118. data/lib/polyphony/http/http1.rb +25 -25
  119. data/lib/polyphony/http/http1_request.rb +38 -26
  120. data/lib/polyphony/http/http2.rb +4 -5
  121. data/lib/polyphony/http/http2_request.rb +12 -18
  122. data/lib/polyphony/http/rack.rb +1 -3
  123. data/lib/polyphony/http/server.rb +9 -9
  124. data/lib/polyphony/net.rb +2 -2
  125. data/lib/polyphony/resource_pool.rb +5 -1
  126. data/lib/polyphony/version.rb +1 -1
  127. data/lib/polyphony/websocket.rb +52 -0
  128. data/polyphony.gemspec +31 -0
  129. data/test/test_coprocess.rb +131 -0
  130. data/test/test_core.rb +274 -0
  131. data/test/test_ev.rb +117 -0
  132. data/test/test_io.rb +38 -0
  133. metadata +113 -7
  134. data/lib/polyphony/core/async.rb +0 -36
  135. data/lib/polyphony/net_old.rb +0 -299
@@ -56,9 +56,10 @@ class ::PG::Connection
56
56
  alias_method :orig_async_exec, :async_exec
57
57
  def async_exec(*args, &block)
58
58
  send_query(*args)
59
- result = get_result(&block)
59
+ get_result(&block)
60
+ ensure
61
+ # cleanup result in order to allow next query
60
62
  while get_result; end
61
- result
62
63
  end
63
64
 
64
65
  def block(timeout = 0)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :Connection
3
+ export :Driver
4
4
 
5
5
  require "redis"
6
6
  require "hiredis/reader"
@@ -5,9 +5,11 @@ require 'socket'
5
5
  import('./io')
6
6
 
7
7
  class ::Socket
8
+ NO_EXCEPTION = { exception: false }.freeze
9
+
8
10
  def accept
9
11
  loop do
10
- result, client_addr = accept_nonblock(::IO::NO_EXCEPTION)
12
+ result, client_addr = accept_nonblock(NO_EXCEPTION)
11
13
  case result
12
14
  when Socket then return result
13
15
  when :wait_readable then read_watcher.await
@@ -21,7 +23,7 @@ class ::Socket
21
23
 
22
24
  def connect(remotesockaddr)
23
25
  loop do
24
- result = connect_nonblock(remotesockaddr, ::IO::NO_EXCEPTION)
26
+ result = connect_nonblock(remotesockaddr, NO_EXCEPTION)
25
27
  case result
26
28
  when 0 then return
27
29
  when :wait_writable then write_watcher.await
@@ -35,7 +37,7 @@ class ::Socket
35
37
  def recvfrom(maxlen, flags = 0)
36
38
  @read_buffer ||= +''
37
39
  loop do
38
- result = recvfrom_nonblock(maxlen, flags, @read_buffer, ::IO::NO_EXCEPTION)
40
+ result = recvfrom_nonblock(maxlen, flags, @read_buffer, NO_EXCEPTION)
39
41
  case result
40
42
  when nil then raise IOError
41
43
  when :wait_readable then read_watcher.await
@@ -69,9 +71,11 @@ class ::Socket
69
71
  end
70
72
 
71
73
  class ::TCPServer
74
+ NO_EXCEPTION = { exception: false }.freeze
75
+
72
76
  def accept
73
77
  loop do
74
- result, client_addr = accept_nonblock(::IO::NO_EXCEPTION)
78
+ result, client_addr = accept_nonblock(NO_EXCEPTION)
75
79
  case result
76
80
  when TCPSocket then return result
77
81
  when :wait_readable then read_watcher.await
@@ -5,60 +5,6 @@ require 'openssl'
5
5
  import('./socket')
6
6
 
7
7
  class ::OpenSSL::SSL::SSLSocket
8
- def accept
9
- loop do
10
- result = accept_nonblock(::IO::NO_EXCEPTION)
11
- case result
12
- when :wait_readable then io.read_watcher.await
13
- when :wait_writable then io.write_watcher.await
14
- else return true
15
- end
16
- end
17
- ensure
18
- io.stop_watchers
19
- end
20
-
21
- def connect
22
- loop do
23
- result = connect_nonblock(::IO::NO_EXCEPTION)
24
- case result
25
- when :wait_readable then io.read_watcher.await
26
- when :wait_writable then io.write_watcher.await
27
- else return true
28
- end
29
- end
30
- ensure
31
- io.stop_watchers
32
- end
33
-
34
- def read(max = 8192)
35
- @read_buffer ||= +''
36
- loop do
37
- result = read_nonblock(max, @read_buffer, ::IO::NO_EXCEPTION)
38
- case result
39
- when nil then raise ::IOError
40
- when :wait_readable then io.read_watcher.await
41
- else return result
42
- end
43
- end
44
- ensure
45
- io.stop_watchers
46
- end
47
-
48
- def write(data)
49
- loop do
50
- result = write_nonblock(data, ::IO::NO_EXCEPTION)
51
- case result
52
- when nil then raise ::IOError
53
- when :wait_writable then io.write_watcher.await
54
- else
55
- (result == data.bytesize) ? (return result) : (data = data[result..-1])
56
- end
57
- end
58
- ensure
59
- io.stop_watchers
60
- end
61
-
62
8
  def dont_linger
63
9
  io.dont_linger
64
10
  end
@@ -47,8 +47,6 @@ class Agent
47
47
  request(url, method: :POST, query: query)
48
48
  end
49
49
 
50
- S_LOCATION = 'Location'
51
-
52
50
  OPTS_DEFAULT = {}.freeze
53
51
 
54
52
  def request(url, opts = OPTS_DEFAULT)
@@ -57,7 +55,7 @@ class Agent
57
55
 
58
56
  case response[:status_code]
59
57
  when 301, 302
60
- request(response[:headers][S_LOCATION])
58
+ request(response[:headers]['Location'])
61
59
  when 200, 204
62
60
  response.extend(ResponseMixin)
63
61
  else
@@ -99,10 +97,8 @@ class Agent
99
97
  end
100
98
  end
101
99
 
102
- S_H2 = 'h2'
103
-
104
100
  def protocol_method(socket, ctx)
105
- if socket.is_a?(::OpenSSL::SSL::SSLSocket) && (socket.alpn_protocol == S_H2)
101
+ if socket.is_a?(::OpenSSL::SSL::SSLSocket) && (socket.alpn_protocol == 'h2')
106
102
  :do_http2
107
103
  else
108
104
  :do_http1
@@ -195,15 +191,13 @@ class Agent
195
191
  }
196
192
  end
197
193
 
198
- S_HTTP = 'http'
199
- S_HTTPS = 'https'
200
194
  SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }
201
195
 
202
196
  def connect(key)
203
197
  case key[:scheme]
204
- when S_HTTP
198
+ when 'http'
205
199
  Polyphony::Net.tcp_connect(key[:host], key[:port])
206
- when S_HTTPS
200
+ when 'https'
207
201
  Polyphony::Net.tcp_connect(key[:host], key[:port], SECURE_OPTS).tap do |socket|
208
202
  socket.post_connection_check(key[:host])
209
203
  end
@@ -26,12 +26,13 @@ end
26
26
  # @param socket [Net::Socket] socket
27
27
  # @param handler [Proc] request handler
28
28
  # @return [void]
29
- def run(socket, handler)
30
- ctx = connection_context(socket, handler)
29
+ def run(socket, opts, handler)
30
+ ctx = connection_context(socket, opts, handler)
31
31
  ctx[:parser].on_body = proc { |chunk| handle_body_chunk(ctx, chunk) }
32
32
 
33
33
  loop do
34
- data = socket.read
34
+ data = socket.readpartial(8192)
35
+ break unless data
35
36
  if ctx[:parser].parse(data)
36
37
  break unless handle_request(ctx)
37
38
  EV.snooze
@@ -48,15 +49,15 @@ end
48
49
  # @param socket [Net::Socket] socket
49
50
  # @param handler [Proc] request handler
50
51
  # @return [Hash]
51
- def connection_context(socket, handler)
52
+ def connection_context(socket, opts, handler)
52
53
  {
53
54
  can_upgrade: true,
55
+ upgrade: opts[:upgrade],
54
56
  count: 0,
55
57
  socket: socket,
56
58
  handler: handler,
57
59
  parser: Http::Parser.new.async!,
58
- body: nil,
59
- request: Request.new
60
+ body: nil
60
61
  }
61
62
  end
62
63
 
@@ -77,8 +78,8 @@ def handle_request(ctx)
77
78
 
78
79
  # allow upgrading the connection only on first request
79
80
  ctx[:can_upgrade] = false
80
- ctx[:request].setup(ctx[:socket], ctx[:parser], ctx[:body])
81
- ctx[:handler].(ctx[:request])
81
+ request = Request.new(ctx[:socket], ctx[:parser], ctx[:body])
82
+ ctx[:handler].(request)
82
83
 
83
84
  if ctx[:parser].keep_alive?
84
85
  ctx[:body] = nil
@@ -88,24 +89,23 @@ def handle_request(ctx)
88
89
  end
89
90
  end
90
91
 
91
- S_EMPTY = ''
92
- S_UPGRADE = 'Upgrade'
93
- S_H2C = 'h2c'
94
- S_SCHEME = ':scheme'
95
- S_METHOD = ':method'
96
- S_AUTHORITY = ':authority'
97
- S_PATH = ':path'
98
- S_HTTP = 'http'
99
- S_HOST = 'Host'
100
-
101
- # Upgrades an HTTP 1 connection to HTTP 2 on client request
92
+ # Upgrades an HTTP 1 connection to HTTP/2 or other protocol on client request
102
93
  # @param ctx [Hash] connection context
103
94
  # @return [Boolean] true if connection was upgraded
104
95
  def upgrade_connection(ctx)
105
- return false unless ctx[:parser].headers[S_UPGRADE] == S_H2C
96
+ upgrade_protocol = ctx[:parser].headers['Upgrade']
97
+ return false unless upgrade_protocol
106
98
 
99
+ if ctx[:upgrade] && ctx[:upgrade][upgrade_protocol.to_sym]
100
+ ctx[:upgrade][upgrade_protocol.to_sym].(ctx[:socket], ctx[:parser].headers)
101
+ return true
102
+ end
103
+
104
+ return false unless upgrade_protocol == 'h2c'
105
+
106
+ # upgrade to HTTP/2
107
107
  request = http2_upgraded_request(ctx)
108
- body = ctx[:body] || S_EMPTY
108
+ body = ctx[:body] || ''
109
109
  HTTP2.upgrade(ctx[:socket], ctx[:handler], request, body)
110
110
  true
111
111
  end
@@ -116,9 +116,9 @@ end
116
116
  def http2_upgraded_request(ctx)
117
117
  headers = ctx[:parser].headers
118
118
  headers.merge(
119
- S_SCHEME => S_HTTP,
120
- S_METHOD => ctx[:parser].http_method,
121
- S_AUTHORITY => headers[S_HOST],
122
- S_PATH => ctx[:parser].request_url
119
+ ':scheme' => 'http',
120
+ ':method' => ctx[:parser].http_method,
121
+ ':authority' => headers['Host'],
122
+ ':path' => ctx[:parser].request_url
123
123
  )
124
124
  end
@@ -5,35 +5,34 @@ export_default :Request
5
5
  require 'uri'
6
6
 
7
7
  class Request
8
- def setup(conn, parser, body)
8
+ def initialize(conn, parser, body)
9
9
  @conn = conn
10
- @parser = parser
10
+ @parser = parser
11
11
  @method = parser.http_method
12
12
  @request_url = parser.request_url
13
13
  @body = body
14
14
  end
15
15
 
16
+ def http_version
17
+ 1
18
+ end
19
+
16
20
  def method
17
21
  @method ||= @parser.http_method
18
22
  end
19
23
 
20
- S_EMPTY = ''
21
-
22
24
  def path
23
- @uri ||= URI.parse(@parser.request_url || S_EMPTY)
25
+ @uri ||= URI.parse(@parser.request_url || '')
24
26
  @path ||= @uri.path
25
27
  end
26
28
 
27
- S_AMPERSAND = '&'
28
- S_EQUAL = '='
29
-
30
29
  def query
31
- @uri ||= URI.parse(@parser.request_url || S_EMPTY)
30
+ @uri ||= URI.parse(@parser.request_url || '')
32
31
  return @query if @query
33
32
 
34
- if (q = u.query)
35
- @query = q.split(S_AMPERSAND).each_with_object({}) do |kv, h|
36
- k, v = kv.split(S_EQUAL)
33
+ if (q = @uri.query)
34
+ @query = q.split('&').each_with_object({}) do |kv, h|
35
+ k, v = kv.split('=')
37
36
  h[k.to_sym] = URI.decode_www_form_component(v)
38
37
  end
39
38
  else
@@ -45,27 +44,40 @@ class Request
45
44
  @headers ||= @parser.headers
46
45
  end
47
46
 
48
- S_CONTENT_LENGTH = 'Content-Length'
49
- S_STATUS = ':status'
50
- EMPTY_LINE = "\r\n"
47
+ EMPTY_HASH = {}
48
+
49
+ def respond(chunk, headers = EMPTY_HASH)
50
+ status = headers.delete(':status') || 200
51
+ data = format_head(headers)
52
+ if chunk
53
+ data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n0\r\n\r\n"
54
+ end
55
+ @conn << data
56
+ end
51
57
 
52
- def respond(body, headers = {})
53
- status = headers.delete(S_STATUS) || 200
54
- data = +"HTTP/1.1 #{status}\r\n"
55
- headers[S_CONTENT_LENGTH] = body.bytesize if body
58
+ def format_head(headers)
59
+ status = headers[':status'] || 200
60
+ data = +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
56
61
  headers.each do |k, v|
62
+ next if k =~ /^:/
57
63
  if v.is_a?(Array)
58
- v.each { |vv| data << "#{k}: #{vv}\r\n" }
64
+ v.each { |o| data << "#{k}: #{o}\r\n" }
59
65
  else
60
66
  data << "#{k}: #{v}\r\n"
61
67
  end
62
68
  end
63
- if body
64
- data << "\r\n#{body}"
65
- else
66
- data << EMPTY_LINE
67
- end
69
+ data << "\r\n"
70
+ end
68
71
 
69
- @conn << data
72
+ def write_head(headers = EMPTY_HASH)
73
+ @conn << format_head(headers)
74
+ end
75
+
76
+ def write(chunk)
77
+ data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
78
+ end
79
+
80
+ def finish
81
+ @conn << "0\r\n\r\n"
70
82
  end
71
83
  end
@@ -6,8 +6,6 @@ require 'http/2'
6
6
 
7
7
  Request = import('./http2_request')
8
8
 
9
- S_HTTP2_SETTINGS = 'HTTP2-Settings'
10
-
11
9
  UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
12
10
  HTTP/1.1 101 Switching Protocols
13
11
  Connection: Upgrade
@@ -17,7 +15,7 @@ HTTP
17
15
 
18
16
  def upgrade(socket, handler, request, body)
19
17
  interface = prepare(socket, handler)
20
- settings = request[S_HTTP2_SETTINGS]
18
+ settings = request['HTTP2-Settings']
21
19
  socket.write(UPGRADE_MESSAGE)
22
20
  interface.upgrade(settings, request, body)
23
21
  client_loop(socket, interface)
@@ -30,14 +28,15 @@ def prepare(socket, handler)
30
28
  end
31
29
  end
32
30
 
33
- def run(socket, handler)
31
+ def run(socket, opts, handler)
34
32
  interface = prepare(socket, handler)
35
33
  client_loop(socket, interface)
36
34
  end
37
35
 
38
36
  def client_loop(socket, interface)
39
37
  loop do
40
- data = socket.read
38
+ data = socket.readpartial(8192)
39
+ break unless data
41
40
  interface << data
42
41
  EV.snooze
43
42
  end
@@ -9,6 +9,10 @@ class Request
9
9
  @stream = stream
10
10
  end
11
11
 
12
+ def http_version
13
+ 2
14
+ end
15
+
12
16
  def set_headers(headers)
13
17
  @headers = Hash[*headers.flatten]
14
18
  end
@@ -21,33 +25,26 @@ class Request
21
25
  end
22
26
  end
23
27
 
24
- S_METHOD = ':method'
25
-
26
28
  def method
27
- @method ||= @headers[S_METHOD]
29
+ @method ||= @headers[':method']
28
30
  end
29
31
 
30
32
  def scheme
31
33
  @scheme ||= @headers[':scheme']
32
34
  end
33
35
 
34
- S_EMPTY = ''
35
-
36
36
  def path
37
- @uri ||= URI.parse(@headers[':path'] || S_EMPTY)
37
+ @uri ||= URI.parse(@headers[':path'] || '')
38
38
  @path ||= @uri.path
39
39
  end
40
40
 
41
- S_AMPERSAND = '&'
42
- S_EQUAL = '='
43
-
44
41
  def query
45
- @uri ||= URI.parse(@headers[':path'] || S_EMPTY)
42
+ @uri ||= URI.parse(@headers[':path'] || '')
46
43
  return @query if @query
47
44
 
48
45
  if (q = u.query)
49
- @query = q.split(S_AMPERSAND).each_with_object({}) do |kv, h|
50
- k, v = kv.split(S_EQUAL)
46
+ @query = q.split('&').each_with_object({}) do |kv, h|
47
+ k, v = kv.split('=')
51
48
  h[k.to_sym] = URI.decode_www_form_component(v)
52
49
  end
53
50
  else
@@ -55,13 +52,10 @@ class Request
55
52
  end
56
53
  end
57
54
 
58
- S_CONTENT_LENGTH = 'Content-Length'
59
- S_STATUS = ':status'
60
- S_STATUS_200 = '200'
61
- EMPTY_LINE = "\r\n"
55
+ EMPTY_HASH = {}
62
56
 
63
- def respond(body, headers = {})
64
- headers[S_STATUS] ||= S_STATUS_200
57
+ def respond(body, headers = EMPTY_HASH)
58
+ headers[':status'] ||= '200'
65
59
 
66
60
  @stream.headers(headers, end_stream: false)
67
61
  @stream.data(body, end_stream: true)