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,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :serve, :listen, :accept_loop
3
+ export :serve, :listen, :accept_loop, :client_loop
4
4
 
5
5
  Net = import('../net')
6
- HTTP1 = import('./http1_adapter')
7
- HTTP2 = import('./http2_adapter')
6
+ HTTP1 = import('./server/http1')
7
+ HTTP2 = import('./server/http2')
8
8
 
9
9
  ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
10
10
  H2_PROTOCOL = 'h2'
@@ -34,11 +34,11 @@ def accept_loop(server, opts, &handler)
34
34
  end
35
35
 
36
36
  def client_loop(client, opts, &handler)
37
- client.no_delay rescue nil
37
+ client.no_delay if client.respond_to?(:no_delay)
38
38
  adapter = protocol_adapter(client, opts)
39
39
  adapter.each(&handler)
40
40
  ensure
41
- client.close rescue nil
41
+ client.close
42
42
  end
43
43
 
44
44
  def protocol_adapter(socket, opts)
@@ -0,0 +1,268 @@
1
+ # frozen_string_literal: true
2
+
3
+ export_default :HTTP1Adapter
4
+
5
+ require 'http/parser'
6
+
7
+ Request = import('./request')
8
+ HTTP2 = import('./http2')
9
+ Exceptions = import('../../core/exceptions')
10
+
11
+ # HTTP1 protocol implementation
12
+ class HTTP1Adapter
13
+ # Initializes a protocol adapter instance
14
+ def initialize(conn, opts)
15
+ @conn = conn
16
+ @opts = opts
17
+ @parser = HTTP::Parser.new(self)
18
+ end
19
+
20
+ def each(&block)
21
+ while (data = @conn.readpartial(8192))
22
+ return if handle_incoming_data(data, &block)
23
+ end
24
+ rescue SystemCallError, IOError
25
+ # ignore
26
+ ensure
27
+ finalize_client_loop
28
+ end
29
+
30
+ # return [Boolean] true if client loop should stop
31
+ def handle_incoming_data(data, &block)
32
+ @parser << data
33
+ snooze
34
+ while (request = @requests_head)
35
+ return true if upgrade_connection(request.headers, &block)
36
+
37
+ @requests_head = request.__next__
38
+ block.call(request)
39
+ return true unless request.keep_alive?
40
+ end
41
+ nil
42
+ end
43
+
44
+ def finalize_client_loop
45
+ # release references to various objects
46
+ @requests_head = @requests_tail = nil
47
+ @parser = nil
48
+ @conn.close
49
+ end
50
+
51
+ # Reads a body chunk for the current request. Transfers control to the parse
52
+ # loop, and resumes once the parse_loop has fired the on_body callback
53
+ def get_body_chunk
54
+ @waiting_for_body_chunk = true
55
+ @next_chunk = nil
56
+ while !@requests_tail.complete? && (data = @conn.readpartial(8192))
57
+ @parser << data
58
+ return @next_chunk if @next_chunk
59
+
60
+ snooze
61
+ end
62
+ nil
63
+ ensure
64
+ @waiting_for_body_chunk = nil
65
+ end
66
+
67
+ # Waits for the current request to complete. Transfers control to the parse
68
+ # loop, and resumes once the parse_loop has fired the on_message_complete
69
+ # callback
70
+ def consume_request
71
+ request = @requests_head
72
+ while (data = @conn.readpartial(8192))
73
+ @parser << data
74
+ return if request.complete?
75
+
76
+ snooze
77
+ end
78
+ end
79
+
80
+ def protocol
81
+ version = @parser.http_version
82
+ "HTTP #{version.join('.')}"
83
+ end
84
+
85
+ def on_headers_complete(headers)
86
+ headers[':path'] = @parser.request_url
87
+ headers[':method'] = @parser.http_method
88
+ queue_request(Request.new(headers, self))
89
+ end
90
+
91
+ def queue_request(request)
92
+ if @requests_head
93
+ @requests_tail.__next__ = request
94
+ @requests_tail = request
95
+ else
96
+ @requests_head = @requests_tail = request
97
+ end
98
+ end
99
+
100
+ def on_body(chunk)
101
+ if @waiting_for_body_chunk
102
+ @next_chunk = chunk
103
+ @waiting_for_body_chunk = nil
104
+ else
105
+ @requests_tail.buffer_body_chunk(chunk)
106
+ end
107
+ end
108
+
109
+ def on_message_complete
110
+ @waiting_for_body_chunk = nil
111
+ @requests_tail.complete!(@parser.keep_alive?)
112
+ end
113
+
114
+ # Upgrades the connection to a different protocol, if the 'Upgrade' header is
115
+ # given. By default the only supported upgrade protocol is HTTP2. Additional
116
+ # protocols, notably WebSocket, can be specified by passing a hash to the
117
+ # :upgrade option when starting a server:
118
+ #
119
+ # opts = {
120
+ # upgrade: {
121
+ # websocket: Polyphony::Websocket.handler(&method(:ws_handler))
122
+ # }
123
+ # }
124
+ # Polyphony::HTTP::Server.serve('0.0.0.0', 1234, opts) { |req| ... }
125
+ #
126
+ # @param headers [Hash] request headers
127
+ # @return [boolean] truthy if the connection has been upgraded
128
+ def upgrade_connection(headers, &block)
129
+ upgrade_protocol = headers['Upgrade']
130
+ return nil unless upgrade_protocol
131
+
132
+ upgrade_protocol = upgrade_protocol.downcase.to_sym
133
+ upgrade_handler = @opts[:upgrade] && @opts[:upgrade][upgrade_protocol]
134
+ return upgrade_with_handler(upgrade_handler, headers) if upgrade_handler
135
+ return upgrade_to_http2(headers, &block) if upgrade_protocol == :h2c
136
+
137
+ nil
138
+ end
139
+
140
+ def upgrade_with_handler(handler, headers)
141
+ @parser = @requests_head = @requests_tail = nil
142
+ handler.(@conn, headers)
143
+ true
144
+ end
145
+
146
+ def upgrade_to_http2(headers, &block)
147
+ @parser = @requests_head = @requests_tail = nil
148
+ HTTP2.upgrade_each(@conn, @opts, http2_upgraded_headers(headers), &block)
149
+ true
150
+ end
151
+
152
+ # Returns headers for HTTP2 upgrade
153
+ # @param headers [Hash] request headers
154
+ # @return [Hash] headers for HTTP2 upgrade
155
+ def http2_upgraded_headers(headers)
156
+ headers.merge(
157
+ ':scheme' => 'http',
158
+ ':authority' => headers['Host']
159
+ )
160
+ end
161
+
162
+ # response API
163
+
164
+ # Sends response including headers and body. Waits for the request to complete
165
+ # if not yet completed. The body is sent using chunked transfer encoding.
166
+ # @param body [String] response body
167
+ # @param headers
168
+ def respond(body, headers)
169
+ consume_request if @parsing
170
+ data = format_headers(headers, body)
171
+ if body
172
+ data << if @parser.http_minor == 0
173
+ body
174
+ else
175
+ "#{body.bytesize.to_s(16)}\r\n#{body}\r\n0\r\n\r\n"
176
+ end
177
+ end
178
+ @conn << data
179
+ end
180
+
181
+ DEFAULT_HEADERS_OPTS = {
182
+ empty_response: false,
183
+ consume_request: true
184
+ }.freeze
185
+
186
+ # Sends response headers. If empty_response is truthy, the response status
187
+ # code will default to 204, otherwise to 200.
188
+ # @param headers [Hash] response headers
189
+ # @param empty_response [boolean] whether a response body will be sent
190
+ # @return [void]
191
+ def send_headers(headers, opts = DEFAULT_HEADERS_OPTS)
192
+ @conn << format_headers(headers, !opts[:empty_response])
193
+ end
194
+
195
+ # Sends a response body chunk. If no headers were sent, default headers are
196
+ # sent using #send_headers. if the done option is true(thy), an empty chunk
197
+ # will be sent to signal response completion to the client.
198
+ # @param chunk [String] response body chunk
199
+ # @param done [boolean] whether the response is completed
200
+ # @return [void]
201
+ def send_chunk(chunk, done: false)
202
+ data = +"#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
203
+ data << "0\r\n\r\n" if done
204
+ @conn << data
205
+ end
206
+
207
+ # Finishes the response to the current request. If no headers were sent,
208
+ # default headers are sent using #send_headers.
209
+ # @return [void]
210
+ def finish
211
+ @conn << "0\r\n\r\n"
212
+ end
213
+
214
+ def close
215
+ @conn.close
216
+ end
217
+
218
+ private
219
+
220
+ # Formats response headers. If empty_response is true(thy), the response
221
+ # status code will default to 204, otherwise to 200.
222
+ # @param headers [Hash] response headers
223
+ # @param empty_response [boolean] whether a response body will be sent
224
+ # @return [String] formatted response headers
225
+ def format_headers(headers, body)
226
+ status = headers[':status'] || (body ? 200 : 204)
227
+ data = format_status_line(body, status)
228
+
229
+ headers.each do |k, v|
230
+ next if k =~ /^:/
231
+
232
+ format_header_lines(k, v)
233
+ end
234
+ data << "\r\n"
235
+ end
236
+
237
+ def format_header_lines(key, value)
238
+ if value.is_a?(Array)
239
+ value.each { |item| data << "#{key}: #{item}\r\n" }
240
+ else
241
+ data << "#{key}: #{value}\r\n"
242
+ end
243
+ end
244
+
245
+ def format_status_line(body, status)
246
+ if !body
247
+ empty_status_line(status)
248
+ else
249
+ with_body_status_line(status, body)
250
+ end
251
+ end
252
+
253
+ def empty_status_line(status)
254
+ if status == 204
255
+ +"HTTP/1.1 #{status}\r\n"
256
+ else
257
+ +"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
258
+ end
259
+ end
260
+
261
+ def with_body_status_line(status, body)
262
+ if @parser.http_minor == 0
263
+ +"HTTP/1.0 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
264
+ else
265
+ +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ export_default :Protocol
4
+
5
+ require 'http/2'
6
+
7
+ Stream = import './http2_stream'
8
+
9
+ # HTTP2 API
10
+ class Protocol
11
+ def self.upgrade_each(socket, opts, headers, &block)
12
+ adapter = new(socket, opts, headers)
13
+ adapter.each(&block)
14
+ end
15
+
16
+ def initialize(conn, opts, upgrade_headers = nil)
17
+ @conn = conn
18
+ @opts = opts
19
+ @upgrade_headers = upgrade_headers
20
+
21
+ @interface = ::HTTP2::Server.new
22
+ @interface.on(:frame) { |bytes| @conn << bytes }
23
+ end
24
+
25
+ # request API
26
+
27
+ UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
28
+ HTTP/1.1 101 Switching Protocols
29
+ Connection: Upgrade
30
+ Upgrade: h2c
31
+
32
+ HTTP
33
+
34
+ def upgrade
35
+ @conn << UPGRADE_MESSAGE
36
+ settings = @upgrade_headers['HTTP2-Settings']
37
+ @interface.upgrade(settings, @upgrade_headers, '')
38
+ ensure
39
+ @upgrade_headers = nil
40
+ end
41
+
42
+ # Iterates over incoming requests
43
+ def each(&block)
44
+ @interface.on(:stream) { |stream| Stream.new(stream, &block) }
45
+ upgrade if @upgrade_headers
46
+
47
+ while (data = @conn.readpartial(8192))
48
+ @interface << data
49
+ snooze
50
+ end
51
+ rescue SystemCallError, IOError
52
+ # ignore
53
+ ensure
54
+ # release references to various objects
55
+ @interface = nil
56
+ @conn.close
57
+ end
58
+
59
+ def close
60
+ @conn.close
61
+ end
62
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ export_default :StreamHandler
4
+
5
+ require 'http/2'
6
+
7
+ Request = import './request'
8
+
9
+ # Manages an HTTP 2 stream
10
+ class StreamHandler
11
+ attr_accessor :__next__
12
+
13
+ def initialize(stream, &block)
14
+ @stream = stream
15
+ @stream_fiber = Fiber.new(&block)
16
+
17
+ # stream callbacks occur on connection fiber
18
+ stream.on(:headers, &method(:on_headers))
19
+ stream.on(:data, &method(:on_data))
20
+ stream.on(:half_close, &method(:on_half_close))
21
+ end
22
+
23
+ def on_headers(headers)
24
+ @request = Request.new(headers.to_h, self)
25
+ @stream_fiber.transfer(@request)
26
+ end
27
+
28
+ def on_data(data)
29
+ if @waiting_for_body_chunk
30
+ @waiting_for_body_chunk = nil
31
+ @stream_fiber.transfer(data)
32
+ else
33
+ @request.buffer_body_chunk(data)
34
+ end
35
+ end
36
+
37
+ def on_half_close
38
+ if @waiting_for_body_chunk
39
+ @waiting_for_body_chunk = nil
40
+ @stream_fiber.transfer(nil)
41
+ elsif @waiting_for_half_close
42
+ @waiting_for_half_close = nil
43
+ @stream_fiber.transfer(nil)
44
+ else
45
+ @request.complete!
46
+ end
47
+ end
48
+
49
+ def protocol
50
+ 'h2'
51
+ end
52
+
53
+ def get_body_chunk
54
+ # called in the context of the stream fiber
55
+ return nil if @request.complete?
56
+
57
+ @waiting_for_body_chunk = true
58
+ # the chunk (or an exception) will be returned once the stream fiber is
59
+ # resumed
60
+ suspend
61
+ ensure
62
+ @waiting_for_body_chunk = nil
63
+ end
64
+
65
+ # Wait for request to finish
66
+ def consume_request
67
+ return if @request.complete?
68
+
69
+ @waiting_for_half_close = true
70
+ suspend
71
+ ensure
72
+ @waiting_for_half_close = nil
73
+ end
74
+
75
+ # response API
76
+ def respond(chunk, headers)
77
+ headers[':status'] ||= '200'
78
+ @stream.headers(headers, end_stream: false)
79
+ @stream.data(chunk, end_stream: true)
80
+ @headers_sent = true
81
+ end
82
+
83
+ def send_headers(headers, empty_response = false)
84
+ return if @headers_sent
85
+
86
+ headers[':status'] ||= (empty_response ? 204 : 200).to_s
87
+ @stream.headers(headers, end_stream: false)
88
+ @headers_sent = true
89
+ end
90
+
91
+ def send_chunk(chunk, done: false)
92
+ send_headers({}, false) unless @headers_sent
93
+ @stream.data(chunk, end_stream: done)
94
+ end
95
+
96
+ def finish
97
+ if @headers_sent
98
+ @stream.close
99
+ else
100
+ headers[':status'] ||= '204'
101
+ @stream.headers(headers, end_stream: true)
102
+ end
103
+ end
104
+ end