polyphony 0.21 → 0.22

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile.lock +5 -5
  5. data/TODO.md +83 -20
  6. data/docs/technical-overview/design-principles.md +3 -3
  7. data/docs/technical-overview/faq.md +11 -0
  8. data/docs/technical-overview/fiber-scheduling.md +46 -33
  9. data/examples/core/sleep_spin.rb +2 -2
  10. data/examples/http/http2_raw.rb +135 -0
  11. data/examples/http/http_client.rb +14 -3
  12. data/examples/http/http_get.rb +28 -2
  13. data/examples/http/http_server.rb +3 -1
  14. data/examples/http/http_server_forked.rb +3 -1
  15. data/examples/interfaces/pg_pool.rb +1 -0
  16. data/examples/io/echo_server.rb +1 -0
  17. data/ext/gyro/async.c +7 -9
  18. data/ext/gyro/child.c +5 -8
  19. data/ext/gyro/extconf.rb +2 -0
  20. data/ext/gyro/gyro.c +159 -204
  21. data/ext/gyro/gyro.h +16 -6
  22. data/ext/gyro/io.c +7 -10
  23. data/ext/gyro/signal.c +3 -0
  24. data/ext/gyro/timer.c +9 -18
  25. data/lib/polyphony/auto_run.rb +12 -5
  26. data/lib/polyphony/core/coprocess.rb +1 -1
  27. data/lib/polyphony/core/resource_pool.rb +49 -15
  28. data/lib/polyphony/core/supervisor.rb +3 -2
  29. data/lib/polyphony/extensions/core.rb +16 -3
  30. data/lib/polyphony/extensions/io.rb +2 -0
  31. data/lib/polyphony/extensions/openssl.rb +60 -0
  32. data/lib/polyphony/extensions/socket.rb +0 -4
  33. data/lib/polyphony/http/client/agent.rb +127 -0
  34. data/lib/polyphony/http/client/http1.rb +129 -0
  35. data/lib/polyphony/http/client/http2.rb +180 -0
  36. data/lib/polyphony/http/client/response.rb +32 -0
  37. data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
  38. data/lib/polyphony/http/server/request.rb +0 -1
  39. data/lib/polyphony/http.rb +1 -1
  40. data/lib/polyphony/net.rb +2 -1
  41. data/lib/polyphony/version.rb +1 -1
  42. data/lib/polyphony.rb +4 -4
  43. data/polyphony.gemspec +1 -0
  44. data/test/test_gyro.rb +42 -10
  45. data/test/test_resource_pool.rb +107 -0
  46. metadata +10 -4
  47. data/lib/polyphony/http/agent.rb +0 -250
@@ -1,250 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- export_default :Agent
4
-
5
- require 'uri'
6
- require 'http/parser'
7
- require 'http/2'
8
- require 'json'
9
-
10
- ResourcePool = import('../core/resource_pool')
11
-
12
- # Response mixin
13
- module ResponseMixin
14
- def body
15
- self[:body]
16
- end
17
-
18
- def json
19
- @json ||= ::JSON.parse(self[:body])
20
- end
21
- end
22
-
23
- # Implements an HTTP agent
24
- class Agent
25
- def self.get(*args)
26
- default.get(*args)
27
- end
28
-
29
- def self.post(*args)
30
- default.post(*args)
31
- end
32
-
33
- def self.default
34
- @default ||= new
35
- end
36
-
37
- def initialize(max_conns = 6)
38
- @pools = Hash.new do |h, k|
39
- h[k] = ResourcePool.new(limit: max_conns) { {} }
40
- end
41
- end
42
-
43
- OPTS_DEFAULT = {}.freeze
44
-
45
- def get(url, opts = OPTS_DEFAULT)
46
- request(url, opts.merge(method: :GET))
47
- end
48
-
49
- def post(url, opts = OPTS_DEFAULT)
50
- request(url, opts.merge(method: :POST))
51
- end
52
-
53
- def request(url, opts = OPTS_DEFAULT)
54
- ctx = request_ctx(url, opts)
55
-
56
- response = do_request(ctx)
57
- case response[:status_code]
58
- when 301, 302
59
- redirect(response[:headers]['Location'], ctx, opts)
60
- when 200, 204
61
- response.extend(ResponseMixin)
62
- else
63
- raise "Error received from server: #{response[:status_code]}"
64
- end
65
- end
66
-
67
- def redirect(url, ctx, opts)
68
- url = case url
69
- when /^http(?:s)?\:\/\//
70
- url
71
- when /^\/\/(.+)$/
72
- ctx[:uri].scheme + url
73
- when /^\//
74
- format(
75
- '%<scheme>s://%<host>s%<url>s',
76
- scheme: ctx[:uri].scheme,
77
- host: ctx[:uri].host,
78
- url: url
79
- )
80
- else
81
- ctx[:uri] + url
82
- end
83
-
84
- request(url, opts)
85
- end
86
-
87
- def request_ctx(url, opts)
88
- {
89
- method: opts[:method] || :GET,
90
- uri: url_to_uri(url, opts),
91
- opts: opts,
92
- retry: 0
93
- }
94
- end
95
-
96
- def url_to_uri(url, opts)
97
- uri = URI(url)
98
- if opts[:query]
99
- query = opts[:query].map { |k, v| "#{k}=#{v}" }.join('&')
100
- if uri.query
101
- v.query = "#{uri.query}&#{query}"
102
- else
103
- uri.query = query
104
- end
105
- end
106
- uri
107
- end
108
-
109
- def do_request(ctx)
110
- key = uri_key(ctx[:uri])
111
- @pools[key].acquire do |state|
112
- cancel_after(10) do
113
- state[:socket] ||= connect(key)
114
- state[:protocol_method] ||= protocol_method(state[:socket], ctx)
115
- send(state[:protocol_method], state, ctx)
116
- rescue Exception => e
117
- state[:socket]&.close
118
- state.clear
119
-
120
- raise e unless ctx[:retry] < 3
121
-
122
- ctx[:retry] += 1
123
- do_request(ctx)
124
- end
125
- end
126
- end
127
-
128
- def protocol_method(socket, _ctx)
129
- if socket.is_a?(::OpenSSL::SSL::SSLSocket) && (socket.alpn_protocol == 'h2')
130
- :do_http2
131
- else
132
- :do_http1
133
- end
134
- end
135
-
136
- def do_http1(state, ctx)
137
- done = false
138
- body = +''
139
- parser = HTTP::Parser.new
140
- parser.on_message_complete = proc { done = true }
141
- parser.on_body = proc { |data| body << data }
142
- request = format_http1_request(ctx)
143
-
144
- state[:socket] << request
145
- parser << state[:socket].readpartial(8192) until done
146
-
147
- {
148
- protocol: 'http1.1',
149
- status_code: parser.status_code,
150
- headers: parser.headers,
151
- body: body
152
- }
153
- end
154
-
155
- def do_http2(state, ctx)
156
- unless state[:http2_client]
157
- socket = state[:socket]
158
- client = HTTP2::Client.new
159
- client.on(:frame) { |bytes| socket << bytes }
160
- state[:http2_client] = client
161
- end
162
-
163
- stream = state[:http2_client].new_stream # allocate new stream
164
-
165
- headers = {
166
- ':method' => ctx[:method].to_s,
167
- ':scheme' => ctx[:uri].scheme,
168
- ':authority' => [ctx[:uri].host, ctx[:uri].port].join(':'),
169
- ':path' => ctx[:uri].request_uri
170
- }
171
- headers.merge!(ctx[:opts][:headers]) if ctx[:opts][:headers]
172
- puts "* proxy request headers: #{headers.inspect}"
173
-
174
- if ctx[:opts][:payload]
175
- stream.headers(headers, end_stream: false)
176
- stream.data(ctx[:opts][:payload], end_stream: true)
177
- else
178
- stream.headers(headers, end_stream: true)
179
- end
180
-
181
- headers = nil
182
- body = +''
183
- done = nil
184
-
185
- stream.on(:headers) { |h| headers = h.to_h }
186
- stream.on(:data) { |c| body << c }
187
- stream.on(:close) do
188
- done = true
189
- return {
190
- protocol: 'http2',
191
- status_code: headers && headers[':status'].to_i,
192
- headers: headers || {},
193
- body: body
194
- }
195
- end
196
-
197
- while (data = state[:socket].readpartial(8192))
198
- state[:http2_client] << data
199
- end
200
- ensure
201
- stream.close unless done
202
- end
203
-
204
- HTTP1_REQUEST = <<~HTTP.gsub("\n", "\r\n")
205
- %<method>s %<request>s HTTP/1.1
206
- Host: %<host>s
207
- %<headers>s
208
-
209
- HTTP
210
-
211
- def format_http1_request(ctx)
212
- headers = format_headers(ctx)
213
- puts "* proxy request headers: #{headers.inspect}"
214
-
215
- format(
216
- HTTP1_REQUEST,
217
- method: ctx[:method],
218
- request: ctx[:uri].request_uri,
219
- host: ctx[:uri].host,
220
- headers: headers
221
- )
222
- end
223
-
224
- def format_headers(headers)
225
- return nil unless ctx[:opts][:headers]
226
-
227
- headers.map { |k, v| "#{k}: #{v}\r\n" }.join
228
- end
229
-
230
- def uri_key(uri)
231
- {
232
- scheme: uri.scheme,
233
- host: uri.host,
234
- port: uri.port
235
- }
236
- end
237
-
238
- SECURE_OPTS = { secure: true, alpn_protocols: ['h2', 'http/1.1'] }.freeze
239
-
240
- def connect(key)
241
- case key[:scheme]
242
- when 'http'
243
- Polyphony::Net.tcp_connect(key[:host], key[:port])
244
- when 'https'
245
- Polyphony::Net.tcp_connect(key[:host], key[:port], SECURE_OPTS)
246
- else
247
- raise "Invalid scheme #{key[:scheme].inspect}"
248
- end
249
- end
250
- end