tipi 0.41 → 0.46
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/.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
|