polyphony 0.23 → 0.24

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