polyphony 0.21 → 0.22

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