polyphony 0.13 → 0.14

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 (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)