polyphony 0.23 → 0.24

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/Gemfile.lock +4 -10
  4. data/README.md +0 -4
  5. data/TODO.md +5 -56
  6. data/docs/README.md +4 -7
  7. data/examples/core/{channel_echo.rb → xx-channels.rb} +0 -0
  8. data/examples/core/{defer.rb → xx-deferring-an-operation.rb} +0 -0
  9. data/examples/core/{genserver.rb → xx-erlang-style-genserver.rb} +2 -2
  10. data/examples/core/{fork.rb → xx-forking.rb} +2 -0
  11. data/examples/core/xx-move_on.rb +23 -0
  12. data/examples/core/{pulse.rb → xx-recurrent-timer.rb} +3 -2
  13. data/examples/core/{resource_cancel.rb → xx-resource_cancel.rb} +1 -2
  14. data/examples/core/{resource_delegate.rb → xx-resource_delegate.rb} +0 -0
  15. data/examples/core/{wait_for_signal.rb → xx-signals.rb} +0 -0
  16. data/examples/core/{sleep.rb → xx-sleeping.rb} +0 -0
  17. data/examples/core/{spin_error_backtrace.rb → xx-spin_error_backtrace.rb} +5 -2
  18. data/examples/core/{supervisor.rb → xx-supervisors.rb} +0 -0
  19. data/examples/core/{thread_cancel.rb → xx-thread_cancel.rb} +0 -0
  20. data/examples/core/{thread_pool.rb → xx-thread_pool.rb} +0 -0
  21. data/examples/core/{throttle.rb → xx-throttling.rb} +0 -0
  22. data/examples/core/{timeout.rb → xx-timeout.rb} +0 -0
  23. data/examples/core/{lock.rb → xx-using-a-mutex.rb} +0 -0
  24. data/examples/io/{backticks.rb → xx-backticks.rb} +0 -0
  25. data/examples/io/{echo_client.rb → xx-echo_client.rb} +0 -1
  26. data/examples/io/{echo_client_from_stdin.rb → xx-echo_client_from_stdin.rb} +0 -1
  27. data/examples/io/{echo_pipe.rb → xx-echo_pipe.rb} +0 -0
  28. data/examples/io/{echo_server.rb → xx-echo_server.rb} +0 -0
  29. data/examples/io/{echo_server_with_timeout.rb → xx-echo_server_with_timeout.rb} +0 -0
  30. data/examples/io/{echo_stdin.rb → xx-echo_stdin.rb} +0 -0
  31. data/examples/io/xx-httparty.rb +13 -0
  32. data/examples/io/{irb.rb → xx-irb.rb} +0 -0
  33. data/examples/io/{net-http.rb → xx-net-http.rb} +0 -0
  34. data/examples/io/{open.rb → xx-open.rb} +0 -1
  35. data/examples/io/{system.rb → xx-system.rb} +0 -0
  36. data/examples/io/{tcpserver.rb → xx-tcpserver.rb} +0 -0
  37. data/examples/io/{tcpsocket.rb → xx-tcpsocket.rb} +0 -1
  38. data/examples/{fs/read.rb → performance/fs_read.rb} +0 -0
  39. data/examples/{core → performance}/mem-usage.rb +1 -0
  40. data/examples/performance/multi_snooze.rb +2 -0
  41. data/examples/performance/snooze.rb +2 -0
  42. data/examples/performance/thread-vs-fiber/polyphony_server.rb +5 -3
  43. data/examples/performance/thread-vs-fiber/threaded_server.rb +1 -1
  44. data/examples/{io/httparty_multi.rb → performance/thread-vs-fiber/xx-httparty_multi.rb} +16 -13
  45. data/examples/{io/httparty_threaded.rb → performance/thread-vs-fiber/xx-httparty_threaded.rb} +2 -2
  46. data/examples/performance/thread.rb +27 -0
  47. data/examples/{core → performance}/thread_pool_perf.rb +0 -0
  48. data/ext/gyro/extconf.rb +1 -0
  49. data/lib/polyphony/extensions/core.rb +0 -5
  50. data/lib/polyphony/version.rb +1 -1
  51. data/polyphony.gemspec +3 -9
  52. metadata +59 -167
  53. data/bin/poly +0 -11
  54. data/examples/core/cancel.rb +0 -13
  55. data/examples/core/enumerator.rb +0 -15
  56. data/examples/core/error_bubbling.rb +0 -35
  57. data/examples/core/fiber_error.rb +0 -9
  58. data/examples/core/fiber_error_with_backtrace.rb +0 -73
  59. data/examples/core/move_on.rb +0 -11
  60. data/examples/core/move_on_twice.rb +0 -16
  61. data/examples/core/move_on_with_ensure.rb +0 -13
  62. data/examples/core/move_on_with_value.rb +0 -14
  63. data/examples/core/multiple_spin.rb +0 -18
  64. data/examples/core/nested_cancel.rb +0 -40
  65. data/examples/core/nested_multiple_spin.rb +0 -20
  66. data/examples/core/nested_spin.rb +0 -19
  67. data/examples/core/pingpong.rb +0 -21
  68. data/examples/core/resource.rb +0 -30
  69. data/examples/core/sleep_spin.rb +0 -21
  70. data/examples/core/snooze.rb +0 -32
  71. data/examples/core/spin_error.rb +0 -17
  72. data/examples/core/spin_uncaught_error.rb +0 -16
  73. data/examples/core/supervisor_with_cancel_scope.rb +0 -23
  74. data/examples/core/supervisor_with_error.rb +0 -24
  75. data/examples/core/supervisor_with_manual_move_on.rb +0 -23
  76. data/examples/core/suspend.rb +0 -13
  77. data/examples/core/thread.rb +0 -27
  78. data/examples/http/config.ru +0 -7
  79. data/examples/http/cuba.ru +0 -22
  80. data/examples/http/happy_eyeballs.rb +0 -37
  81. data/examples/http/http2_raw.rb +0 -135
  82. data/examples/http/http_client.rb +0 -28
  83. data/examples/http/http_get.rb +0 -33
  84. data/examples/http/http_parse_experiment.rb +0 -123
  85. data/examples/http/http_proxy.rb +0 -83
  86. data/examples/http/http_server.js +0 -24
  87. data/examples/http/http_server.rb +0 -28
  88. data/examples/http/http_server_forked.rb +0 -29
  89. data/examples/http/http_server_graceful.rb +0 -27
  90. data/examples/http/http_server_simple.rb +0 -11
  91. data/examples/http/http_server_throttled.rb +0 -15
  92. data/examples/http/http_ws_server.rb +0 -37
  93. data/examples/http/https_raw_client.rb +0 -12
  94. data/examples/http/https_server.rb +0 -22
  95. data/examples/http/https_wss_server.rb +0 -39
  96. data/examples/http/rack_server.rb +0 -12
  97. data/examples/http/rack_server_https.rb +0 -19
  98. data/examples/http/rack_server_https_forked.rb +0 -27
  99. data/examples/http/websocket_secure_server.rb +0 -27
  100. data/examples/http/websocket_server.rb +0 -24
  101. data/examples/http/ws_page.html +0 -34
  102. data/examples/http/wss_page.html +0 -34
  103. data/examples/io/cat.rb +0 -12
  104. data/examples/io/httparty.rb +0 -10
  105. data/examples/io/io_read.rb +0 -9
  106. data/lib/ev_ext.bundle +0 -0
  107. data/lib/polyphony/http.rb +0 -16
  108. data/lib/polyphony/http/client/agent.rb +0 -131
  109. data/lib/polyphony/http/client/http1.rb +0 -129
  110. data/lib/polyphony/http/client/http2.rb +0 -180
  111. data/lib/polyphony/http/client/response.rb +0 -32
  112. data/lib/polyphony/http/client/site_connection_manager.rb +0 -109
  113. data/lib/polyphony/http/server.rb +0 -49
  114. data/lib/polyphony/http/server/http1.rb +0 -268
  115. data/lib/polyphony/http/server/http2.rb +0 -78
  116. data/lib/polyphony/http/server/http2_stream.rb +0 -136
  117. data/lib/polyphony/http/server/rack.rb +0 -64
  118. data/lib/polyphony/http/server/request.rb +0 -118
  119. data/lib/polyphony/websocket.rb +0 -59
  120. data/test/test_http_server.rb +0 -313
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :Response
4
-
5
- require 'json'
6
-
7
- # HTTP response
8
- class Response
9
- attr_reader :status_code, :headers
10
-
11
- def initialize(adapter, status_code, headers)
12
- @adapter = adapter
13
- @status_code = status_code
14
- @headers = headers
15
- end
16
-
17
- def body
18
- @body ||= @adapter.body
19
- end
20
-
21
- def each_chunk(&block)
22
- @adapter.each_chunk(&block)
23
- end
24
-
25
- def next_body_chunk
26
- @adapter.next_body_chunk
27
- end
28
-
29
- def json
30
- @json ||= ::JSON.parse(body)
31
- end
32
- end
@@ -1,109 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :SiteConnectionManager
4
-
5
- ResourcePool = import '../../core/resource_pool'
6
- HTTP1Adapter = import './http1'
7
- HTTP2Adapter = import './http2'
8
-
9
- # HTTP site connection pool
10
- class SiteConnectionManager < ResourcePool
11
- def initialize(uri_key)
12
- @uri_key = uri_key
13
- super(limit: 4)
14
- end
15
-
16
- # def method_missing(sym, *args)
17
- # raise "Invalid method #{sym}"
18
- # end
19
-
20
- def acquire
21
- Gyro.ref
22
- prepare_first_connection if @size.zero?
23
- super
24
- ensure
25
- Gyro.unref
26
- # The size goes back to 0 only in case existing connections get into an
27
- # error state and then get discarded
28
- @state = nil if @size == 0
29
- end
30
-
31
- def prepare_first_connection
32
- case @state
33
- when nil
34
- @state = :first_connection
35
- create_first_connection
36
- when :first_connection
37
- @first_connection_queue << Fiber.current
38
- suspend
39
- end
40
- end
41
-
42
- def create_first_connection
43
- @first_connection_queue = []
44
- # @first_connection_queue << Fiber.current
45
-
46
- adapter = connect
47
- @state = adapter.protocol
48
- send(:"setup_#{@state}_allocator", adapter)
49
- dequeue_first_connection_waiters
50
- end
51
-
52
- def setup_http1_allocator(adapter)
53
- @size += 1
54
- adapter.extend ResourceExtensions
55
- @stock << adapter
56
- @allocator = proc { connect }
57
- end
58
-
59
- def setup_http2_allocator(adapter)
60
- @adapter = adapter
61
- @limit = 20
62
- @size += 1
63
- stream_adapter = adapter.allocate_stream_adapter
64
- stream_adapter.extend ResourceExtensions
65
- @stock << stream_adapter
66
- @allocator = proc { adapter.allocate_stream_adapter }
67
- end
68
-
69
- def dequeue_first_connection_waiters
70
- return unless @first_connection_queue
71
-
72
- @first_connection_queue.each(&:schedule)
73
- @first_connection_queue = nil
74
- end
75
-
76
- def connect
77
- socket = create_socket
78
- protocol = socket_protocol(socket)
79
- case protocol
80
- when :http1
81
- HTTP1Adapter.new(socket)
82
- when :http2
83
- HTTP2Adapter.new(socket)
84
- else
85
- raise "Unknown protocol #{protocol.inspect}"
86
- end
87
- end
88
-
89
- def socket_protocol(socket)
90
- if socket.is_a?(OpenSSL::SSL::SSLSocket) && socket.alpn_protocol == 'h2'
91
- :http2
92
- else
93
- :http1
94
- end
95
- end
96
-
97
- SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
98
-
99
- def create_socket
100
- case @uri_key[:scheme]
101
- when 'http'
102
- Polyphony::Net.tcp_connect(@uri_key[:host], @uri_key[:port])
103
- when 'https'
104
- Polyphony::Net.tcp_connect(@uri_key[:host], @uri_key[:port], SECURE_OPTS)
105
- else
106
- raise "Invalid scheme #{@uri_key[:scheme].inspect}"
107
- end
108
- end
109
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export :serve, :listen, :accept_loop, :client_loop
4
-
5
- Net = import('../net')
6
- HTTP1 = import('./server/http1')
7
- HTTP2 = import('./server/http2')
8
-
9
- ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
10
- H2_PROTOCOL = 'h2'
11
-
12
- def serve(host, port, opts = {}, &handler)
13
- opts[:alpn_protocols] = ALPN_PROTOCOLS
14
- server = Net.tcp_listen(host, port, opts)
15
- accept_loop(server, opts, &handler)
16
- end
17
-
18
- def listen(host, port, opts = {})
19
- opts[:alpn_protocols] = ALPN_PROTOCOLS
20
- Net.tcp_listen(host, port, opts).tap do |socket|
21
- socket.define_singleton_method(:each) do |&block|
22
- MODULE.accept_loop(socket, opts, &block)
23
- end
24
- end
25
- end
26
-
27
- def accept_loop(server, opts, &handler)
28
- loop do
29
- client = server.accept
30
- spin { client_loop(client, opts, &handler) }
31
- rescue OpenSSL::SSL::SSLError
32
- # disregard
33
- end
34
- end
35
-
36
- def client_loop(client, opts, &handler)
37
- client.no_delay if client.respond_to?(:no_delay)
38
- adapter = protocol_adapter(client, opts)
39
- adapter.each(&handler)
40
- ensure
41
- client.close
42
- end
43
-
44
- def protocol_adapter(socket, opts)
45
- use_http2 = socket.respond_to?(:alpn_protocol) &&
46
- socket.alpn_protocol == H2_PROTOCOL
47
- klass = use_http2 ? HTTP2 : HTTP1
48
- klass.new(socket, opts)
49
- end
@@ -1,268 +0,0 @@
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