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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +5 -5
- data/TODO.md +83 -20
- data/docs/technical-overview/design-principles.md +3 -3
- data/docs/technical-overview/faq.md +11 -0
- data/docs/technical-overview/fiber-scheduling.md +46 -33
- data/examples/core/sleep_spin.rb +2 -2
- data/examples/http/http2_raw.rb +135 -0
- data/examples/http/http_client.rb +14 -3
- data/examples/http/http_get.rb +28 -2
- data/examples/http/http_server.rb +3 -1
- data/examples/http/http_server_forked.rb +3 -1
- data/examples/interfaces/pg_pool.rb +1 -0
- data/examples/io/echo_server.rb +1 -0
- data/ext/gyro/async.c +7 -9
- data/ext/gyro/child.c +5 -8
- data/ext/gyro/extconf.rb +2 -0
- data/ext/gyro/gyro.c +159 -204
- data/ext/gyro/gyro.h +16 -6
- data/ext/gyro/io.c +7 -10
- data/ext/gyro/signal.c +3 -0
- data/ext/gyro/timer.c +9 -18
- data/lib/polyphony/auto_run.rb +12 -5
- data/lib/polyphony/core/coprocess.rb +1 -1
- data/lib/polyphony/core/resource_pool.rb +49 -15
- data/lib/polyphony/core/supervisor.rb +3 -2
- data/lib/polyphony/extensions/core.rb +16 -3
- data/lib/polyphony/extensions/io.rb +2 -0
- data/lib/polyphony/extensions/openssl.rb +60 -0
- data/lib/polyphony/extensions/socket.rb +0 -4
- data/lib/polyphony/http/client/agent.rb +127 -0
- data/lib/polyphony/http/client/http1.rb +129 -0
- data/lib/polyphony/http/client/http2.rb +180 -0
- data/lib/polyphony/http/client/response.rb +32 -0
- data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
- data/lib/polyphony/http/server/request.rb +0 -1
- data/lib/polyphony/http.rb +1 -1
- data/lib/polyphony/net.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +4 -4
- data/polyphony.gemspec +1 -0
- data/test/test_gyro.rb +42 -10
- data/test/test_resource_pool.rb +107 -0
- metadata +10 -4
- data/lib/polyphony/http/agent.rb +0 -250
data/lib/polyphony/http/agent.rb
DELETED
@@ -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
|