tipi 0.41 → 0.46
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +3 -1
- data/.gitignore +3 -1
- data/CHANGELOG.md +34 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +53 -33
- data/README.md +184 -8
- data/Rakefile +1 -7
- data/benchmarks/bm_http1_parser.rb +85 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/bm.png +0 -0
- data/df/agent.rb +1 -1
- data/df/sample_agent.rb +2 -2
- data/df/server.rb +3 -1
- data/df/server_utils.rb +48 -46
- data/examples/full_service.rb +13 -0
- data/examples/hello.rb +5 -0
- data/examples/hello.ru +3 -3
- data/examples/http1_parser.rb +10 -8
- data/examples/http_server.js +1 -1
- data/examples/http_server.rb +4 -1
- data/examples/http_server_graceful.rb +1 -1
- data/examples/https_server.rb +41 -15
- data/examples/rack_server_forked.rb +26 -0
- data/examples/rack_server_https_forked.rb +1 -1
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +1 -1
- data/lib/tipi/acme.rb +320 -0
- data/lib/tipi/cli.rb +93 -0
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/lib/tipi/controller/bare_polyphony.rb +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/extensions.rb +37 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +353 -0
- data/lib/tipi/controller/web_stock.rb +635 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +5 -5
- data/lib/tipi/digital_fabric/agent_proxy.rb +15 -8
- data/lib/tipi/digital_fabric/executive.rb +7 -3
- data/lib/tipi/digital_fabric/protocol.rb +3 -3
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +17 -18
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +85 -124
- data/lib/tipi/http2_adapter.rb +29 -16
- data/lib/tipi/http2_stream.rb +52 -57
- data/lib/tipi/rack_adapter.rb +2 -2
- data/lib/tipi/response_extensions.rb +1 -1
- data/lib/tipi/supervisor.rb +75 -0
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +3 -3
- data/lib/tipi.rb +9 -7
- data/test/coverage.rb +2 -2
- data/test/helper.rb +60 -12
- data/test/test_http_server.rb +14 -41
- data/test/test_request.rb +2 -29
- data/tipi.gemspec +10 -10
- metadata +80 -54
- data/examples/automatic_certificate.rb +0 -193
- data/ext/tipi/extconf.rb +0 -12
- data/ext/tipi/http1_parser.c +0 -534
- data/ext/tipi/http1_parser.h +0 -18
- data/ext/tipi/tipi_ext.c +0 -5
- data/lib/tipi/http1_adapter_new.rb +0 -293
data/df/server_utils.rb
CHANGED
@@ -7,24 +7,7 @@ require 'tipi/digital_fabric/executive'
|
|
7
7
|
require 'json'
|
8
8
|
require 'fileutils'
|
9
9
|
require 'time'
|
10
|
-
|
11
|
-
module ::Kernel
|
12
|
-
def trace(*args)
|
13
|
-
STDOUT.orig_write(format_trace(args))
|
14
|
-
end
|
15
|
-
|
16
|
-
def format_trace(args)
|
17
|
-
if args.first.is_a?(String)
|
18
|
-
if args.size > 1
|
19
|
-
format("%s: %p\n", args.shift, args)
|
20
|
-
else
|
21
|
-
format("%s\n", args.first)
|
22
|
-
end
|
23
|
-
else
|
24
|
-
format("%p\n", args.size == 1 ? args.first : args)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
10
|
+
require 'polyphony/extensions/debug'
|
28
11
|
|
29
12
|
FileUtils.cd(__dir__)
|
30
13
|
|
@@ -65,8 +48,10 @@ def listen_http
|
|
65
48
|
# log("Done with HTTP connection", client: client)
|
66
49
|
@service.decr_connection_count
|
67
50
|
end
|
68
|
-
rescue
|
69
|
-
|
51
|
+
rescue Polyphony::BaseException
|
52
|
+
raise
|
53
|
+
rescue Exception => e
|
54
|
+
log 'HTTP accept (unknown) error', error: e, backtrace: e.backtrace
|
70
55
|
end
|
71
56
|
end
|
72
57
|
end
|
@@ -74,15 +59,19 @@ end
|
|
74
59
|
CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
|
75
60
|
|
76
61
|
def listen_https
|
77
|
-
spin(
|
62
|
+
spin(:https_listener) do
|
78
63
|
private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
|
79
64
|
c = IO.read('../../reality/ssl/cacert.pem')
|
80
65
|
certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
|
81
66
|
ctx = OpenSSL::SSL::SSLContext.new
|
67
|
+
ctx.security_level = 0
|
82
68
|
cert = certificates.shift
|
83
69
|
log "SSL Certificate expires: #{cert.not_after.inspect}"
|
84
70
|
ctx.add_certificate(cert, private_key, certificates)
|
85
|
-
ctx.ciphers = 'ECDH+aRSA'
|
71
|
+
# ctx.ciphers = 'ECDH+aRSA'
|
72
|
+
ctx.max_version = OpenSSL::SSL::TLS1_3_VERSION
|
73
|
+
ctx.min_version = OpenSSL::SSL::SSL3_VERSION
|
74
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
86
75
|
|
87
76
|
# TODO: further limit ciphers
|
88
77
|
# ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
|
@@ -99,7 +88,9 @@ def listen_https
|
|
99
88
|
server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
|
100
89
|
id = 0
|
101
90
|
loop do
|
102
|
-
client = server.accept
|
91
|
+
client = server.accept rescue nil
|
92
|
+
next unless client
|
93
|
+
|
103
94
|
# log('Accept HTTPS client connection', client: client)
|
104
95
|
spin("https#{id += 1}") do
|
105
96
|
@service.incr_connection_count
|
@@ -110,10 +101,12 @@ def listen_https
|
|
110
101
|
# log("Done with HTTP connection", client: client)
|
111
102
|
@service.decr_connection_count
|
112
103
|
end
|
113
|
-
rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError => e
|
114
|
-
|
115
|
-
rescue
|
116
|
-
|
104
|
+
# rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError => e
|
105
|
+
# log('HTTPS accept error', error: e)
|
106
|
+
rescue Polyphony::BaseException
|
107
|
+
raise
|
108
|
+
rescue Exception => e
|
109
|
+
log 'HTTPS listener error: ', error: e, backtrace: e.backtrace
|
117
110
|
end
|
118
111
|
end
|
119
112
|
end
|
@@ -126,13 +119,16 @@ def listen_unix
|
|
126
119
|
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
127
120
|
|
128
121
|
id = 0
|
129
|
-
|
122
|
+
loop do
|
123
|
+
client = socket.accept
|
130
124
|
# log('Accept Unix connection', client: client)
|
131
125
|
spin("unix#{id += 1}") do
|
132
126
|
Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
|
133
127
|
end
|
134
|
-
rescue
|
135
|
-
|
128
|
+
rescue Polyphony::BaseException
|
129
|
+
raise
|
130
|
+
rescue Exception => e
|
131
|
+
log 'Unix accept error', error: e, backtrace: e.backtrace
|
136
132
|
end
|
137
133
|
end
|
138
134
|
end
|
@@ -141,33 +137,39 @@ def listen_df
|
|
141
137
|
spin(:df_listener) do
|
142
138
|
opts = {
|
143
139
|
reuse_addr: true,
|
140
|
+
reuse_port: true,
|
144
141
|
dont_linger: true,
|
145
142
|
}
|
146
143
|
log('Listening for DF connections on localhost:4321')
|
147
144
|
server = Polyphony::Net.tcp_listen('0.0.0.0', 4321, opts)
|
148
145
|
|
149
146
|
id = 0
|
150
|
-
|
147
|
+
loop do
|
148
|
+
client = server.accept
|
151
149
|
# log('Accept DF connection', client: client)
|
152
150
|
spin("df#{id += 1}") do
|
153
151
|
Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
|
154
152
|
end
|
155
|
-
rescue
|
156
|
-
|
153
|
+
rescue Polyphony::BaseException
|
154
|
+
raise
|
155
|
+
rescue Exception => e
|
156
|
+
log 'DF accept (unknown) error', error: e, backtrace: e.backtrace
|
157
157
|
end
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
161
|
+
if ENV['TRACE'] == '1'
|
162
|
+
Thread.backend.trace_proc = proc do |event, fiber, value, pri|
|
163
|
+
fiber_id = fiber.tag || fiber.inspect
|
164
|
+
case event
|
165
|
+
when :fiber_schedule
|
166
|
+
log format("=> %s %s %s %s", event, fiber_id, value.inspect, pri ? '(priority)' : '')
|
167
|
+
when :fiber_run
|
168
|
+
log format("=> %s %s %s", event, fiber_id, value.inspect)
|
169
|
+
when :fiber_create, :fiber_terminate
|
170
|
+
log format("=> %s %s", event, fiber_id)
|
171
|
+
else
|
172
|
+
log format("=> %s", event)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
certificate_db_path = File.expand_path('certificate_store.db', __dir__)
|
9
|
+
certificate_store = Tipi::ACME::SQLiteCertificateStore.new(certificate_db_path)
|
10
|
+
|
11
|
+
Tipi.full_service(
|
12
|
+
certificate_store: certificate_store
|
13
|
+
) { |req| req.respond('Hello, world!') }
|
data/examples/hello.rb
ADDED
data/examples/hello.ru
CHANGED
data/examples/http1_parser.rb
CHANGED
@@ -31,23 +31,25 @@ f = spin do
|
|
31
31
|
break unless headers
|
32
32
|
trace headers
|
33
33
|
|
34
|
-
body = parser.read_body
|
34
|
+
body = parser.read_body
|
35
35
|
trace "body: #{body ? body.bytesize : 0} bytes"
|
36
|
+
trace body if body && body.bytesize < 80
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
40
|
o << "GET /a HTTP/1.1\r\n\r\n"
|
40
|
-
|
41
|
+
|
42
|
+
# o << "GET /a HTTP/1.1\r\nContent-Length: 0\r\n\r\n"
|
41
43
|
|
42
44
|
# o << "GET / HTTP/1.1\r\nHost: localhost:10080\r\nUser-Agent: curl/7.74.0\r\nAccept: */*\r\n\r\n"
|
43
45
|
|
44
|
-
|
46
|
+
o << "post /?q=time&blah=blah HTTP/1\r\nTransfer-Encoding: chunked\r\n\r\na\r\nabcdefghij\r\n0\r\n\r\n"
|
47
|
+
|
48
|
+
data = " " * 4000000
|
49
|
+
o << "get /?q=time HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
|
45
50
|
|
46
|
-
|
47
|
-
# o << "get /?q=time HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
|
51
|
+
o << "get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
|
48
52
|
|
49
|
-
|
53
|
+
o.close
|
50
54
|
|
51
|
-
# o.close
|
52
|
-
|
53
55
|
f.await
|
data/examples/http_server.js
CHANGED
data/examples/http_server.rb
CHANGED
@@ -14,7 +14,7 @@ puts 'Listening on port 10080...'
|
|
14
14
|
# GC.disable
|
15
15
|
# Thread.current.backend.idle_gc_period = 60
|
16
16
|
|
17
|
-
spin_loop(interval: 10) { p Thread.
|
17
|
+
spin_loop(interval: 10) { p Thread.backend.stats }
|
18
18
|
|
19
19
|
spin_loop(interval: 10) do
|
20
20
|
GC.compact
|
@@ -29,6 +29,9 @@ spin do
|
|
29
29
|
sleep 1
|
30
30
|
req.send_chunk("bar\n")
|
31
31
|
req.finish
|
32
|
+
elsif req.path == '/upload'
|
33
|
+
body = req.read
|
34
|
+
req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
|
32
35
|
else
|
33
36
|
req.respond("Hello world!\n")
|
34
37
|
end
|
data/examples/https_server.rb
CHANGED
@@ -10,24 +10,50 @@ authority = Localhost::Authority.fetch
|
|
10
10
|
opts = {
|
11
11
|
reuse_addr: true,
|
12
12
|
dont_linger: true,
|
13
|
-
secure_context: authority.server_context
|
14
13
|
}
|
15
14
|
|
16
15
|
puts "pid: #{Process.pid}"
|
17
16
|
puts 'Listening on port 1234...'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
|
18
|
+
ctx = authority.server_context
|
19
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 1234, opts)
|
20
|
+
loop do
|
21
|
+
socket = server.accept
|
22
|
+
client = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
23
|
+
client.sync_close = true
|
24
|
+
spin do
|
25
|
+
state = {}
|
26
|
+
accept_thread = Thread.new do
|
27
|
+
puts "call client accept"
|
28
|
+
client.accept
|
29
|
+
state[:result] = :ok
|
30
|
+
rescue Exception => e
|
31
|
+
puts error: e
|
32
|
+
state[:result] = e
|
33
|
+
end
|
34
|
+
"wait for accept thread"
|
35
|
+
accept_thread.join
|
36
|
+
"accept thread done"
|
37
|
+
if state[:result].is_a?(Exception)
|
38
|
+
puts "Exception in SSL handshake: #{state[:result].inspect}"
|
39
|
+
next
|
40
|
+
end
|
41
|
+
Tipi.client_loop(client, opts) do |req|
|
42
|
+
p path: req.path
|
43
|
+
if req.path == '/stream'
|
44
|
+
req.send_headers('Foo' => 'Bar')
|
45
|
+
sleep 0.5
|
46
|
+
req.send_chunk("foo\n")
|
47
|
+
sleep 0.5
|
48
|
+
req.send_chunk("bar\n", done: true)
|
49
|
+
elsif req.path == '/upload'
|
50
|
+
body = req.read
|
51
|
+
req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
|
52
|
+
else
|
53
|
+
req.respond("Hello world!\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
client ? client.close : socket.close
|
28
58
|
end
|
29
|
-
# req.send_headers
|
30
|
-
# req.send_chunk("Method: #{req.method}\n")
|
31
|
-
# req.send_chunk("Path: #{req.path}\n")
|
32
|
-
# req.send_chunk("Query: #{req.query.inspect}\n", done: true)
|
33
59
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
|
7
|
+
unless File.file?(app_path)
|
8
|
+
STDERR.puts "Please provide rack config file (there are some in the examples directory.)"
|
9
|
+
exit!
|
10
|
+
end
|
11
|
+
|
12
|
+
app = Tipi::RackAdapter.load(app_path)
|
13
|
+
opts = { reuse_addr: true, dont_linger: true }
|
14
|
+
|
15
|
+
server = Tipi.listen('0.0.0.0', 1234, opts)
|
16
|
+
puts 'listening on port 1234'
|
17
|
+
|
18
|
+
child_pids = []
|
19
|
+
4.times do
|
20
|
+
child_pids << Polyphony.fork do
|
21
|
+
puts "forked pid: #{Process.pid}"
|
22
|
+
server.each(&app)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'fiber'
|
5
|
+
|
6
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
7
|
+
|
8
|
+
f = Fiber.new { |peer| loop { p peer: peer; _name, peer = peer.transfer nil } }
|
9
|
+
ctx.servername_cb = proc { |_socket, name|
|
10
|
+
p servername_cb: name
|
11
|
+
f.transfer([name, Fiber.current]).tap { |r| p result: r }
|
12
|
+
}
|
13
|
+
|
14
|
+
socket = Socket.new(:INET, :STREAM).tap do |s|
|
15
|
+
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
|
16
|
+
s.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
|
17
|
+
s.bind(Socket.sockaddr_in(12345, '0.0.0.0'))
|
18
|
+
s.listen(Socket::SOMAXCONN)
|
19
|
+
end
|
20
|
+
server = OpenSSL::SSL::SSLServer.new(socket, ctx)
|
21
|
+
|
22
|
+
Thread.new do
|
23
|
+
sleep 0.5
|
24
|
+
socket = TCPSocket.new('127.0.0.1', 12345)
|
25
|
+
client = OpenSSL::SSL::SSLSocket.new(socket)
|
26
|
+
client.hostname = 'example.com'
|
27
|
+
p client: client
|
28
|
+
client.connect
|
29
|
+
rescue => e
|
30
|
+
p client_error: e
|
31
|
+
end
|
32
|
+
|
33
|
+
while true
|
34
|
+
conn = server.accept
|
35
|
+
p accepted: conn
|
36
|
+
break
|
37
|
+
end
|