tipi 0.40 → 0.45
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 +5 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +55 -29
- data/README.md +184 -8
- data/Rakefile +1 -3
- 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 +16 -102
- data/df/server_utils.rb +175 -0
- data/examples/full_service.rb +13 -0
- data/examples/hello.rb +5 -0
- data/examples/http1_parser.rb +55 -0
- data/examples/http_server.js +1 -1
- data/examples/http_server.rb +15 -3
- data/examples/http_server_graceful.rb +1 -1
- data/examples/http_server_static.rb +6 -18
- 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 +315 -0
- data/lib/tipi/cli.rb +93 -0
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/{e → lib/tipi/controller/bare_polyphony.rb} +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +351 -0
- data/lib/tipi/controller/web_stock.rb +631 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +10 -8
- data/lib/tipi/digital_fabric/agent_proxy.rb +26 -12
- data/lib/tipi/digital_fabric/executive.rb +7 -3
- data/lib/tipi/digital_fabric/protocol.rb +19 -4
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +84 -56
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +86 -125
- data/lib/tipi/http2_adapter.rb +29 -16
- data/lib/tipi/http2_stream.rb +52 -56
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +2 -2
- 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 +8 -5
- 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 +12 -8
- metadata +88 -28
- data/examples/automatic_certificate.rb +0 -193
data/df/server.rb
CHANGED
@@ -1,111 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'tipi'
|
5
|
-
require 'tipi/digital_fabric'
|
6
|
-
require 'tipi/digital_fabric/executive'
|
7
|
-
require 'json'
|
8
|
-
require 'fileutils'
|
9
|
-
require 'localhost/authority'
|
3
|
+
require_relative 'server_utils'
|
10
4
|
|
11
|
-
|
5
|
+
listeners = [
|
6
|
+
listen_http,
|
7
|
+
listen_https,
|
8
|
+
listen_unix
|
9
|
+
]
|
12
10
|
|
13
|
-
|
14
|
-
executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
|
15
|
-
|
16
|
-
GC.disable
|
17
|
-
Thread.current.backend.idle_gc_period = 60
|
18
|
-
|
19
|
-
# spin_loop(interval: 60) { GC.start }
|
20
|
-
|
21
|
-
class Polyphony::BaseException
|
22
|
-
attr_reader :caller_backtrace
|
23
|
-
end
|
24
|
-
|
25
|
-
puts "pid: #{Process.pid}"
|
26
|
-
|
27
|
-
http_listener = spin do
|
28
|
-
opts = {
|
29
|
-
reuse_addr: true,
|
30
|
-
dont_linger: true,
|
31
|
-
}
|
32
|
-
puts 'Listening for HTTP on localhost:10080'
|
33
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
|
34
|
-
server.accept_loop do |client|
|
35
|
-
spin do
|
36
|
-
service.incr_connection_count
|
37
|
-
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
38
|
-
ensure
|
39
|
-
service.decr_connection_count
|
40
|
-
end
|
41
|
-
rescue Exception => e
|
42
|
-
puts "HTTP accept_loop error: #{e.inspect}"
|
43
|
-
puts e.backtrace.join("\n")
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
|
48
|
-
|
49
|
-
https_listener = spin do
|
50
|
-
private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
|
51
|
-
c = IO.read('../../reality/ssl/cacert.pem')
|
52
|
-
certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
|
53
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
54
|
-
cert = certificates.shift
|
55
|
-
puts "Certificate expires: #{cert.not_after.inspect}"
|
56
|
-
ctx.add_certificate(cert, private_key, certificates)
|
57
|
-
ctx.ciphers = 'ECDH+aRSA'
|
58
|
-
|
59
|
-
# TODO: further limit ciphers
|
60
|
-
# ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
|
61
|
-
# ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/tls.rb
|
62
|
-
|
63
|
-
opts = {
|
64
|
-
reuse_addr: true,
|
65
|
-
dont_linger: true,
|
66
|
-
secure_context: ctx,
|
67
|
-
alpn_protocols: Tipi::ALPN_PROTOCOLS
|
68
|
-
}
|
69
|
-
|
70
|
-
puts 'Listening for HTTPS on localhost:10443'
|
71
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
|
72
|
-
loop do
|
73
|
-
client = server.accept
|
74
|
-
spin do
|
75
|
-
service.incr_connection_count
|
76
|
-
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
77
|
-
rescue Exception => e
|
78
|
-
puts "Exception: #{e.inspect}"
|
79
|
-
puts e.backtrace.join("\n")
|
80
|
-
ensure
|
81
|
-
service.decr_connection_count
|
82
|
-
end
|
83
|
-
rescue Polyphony::BaseException
|
84
|
-
raise
|
85
|
-
rescue OpenSSL::SSL::SSLError, SystemCallError => e
|
86
|
-
puts "HTTPS accept error: #{e.inspect}"
|
87
|
-
rescue Exception => e
|
88
|
-
puts "HTTPS accept error: #{e.inspect}"
|
89
|
-
puts e.backtrace.join("\n")
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
UNIX_SOCKET_PATH = '/tmp/df.sock'
|
94
|
-
unix_listener = spin do
|
95
|
-
puts "Listening on #{UNIX_SOCKET_PATH}"
|
96
|
-
FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
|
97
|
-
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
98
|
-
Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
|
99
|
-
end
|
11
|
+
spin_loop(interval: 60) { GC.compact } if GC.respond_to?(:compact)
|
100
12
|
|
101
13
|
begin
|
102
|
-
|
14
|
+
log('Starting DF server')
|
15
|
+
Fiber.await(*listeners)
|
103
16
|
rescue Interrupt
|
104
|
-
|
105
|
-
service.graceful_shutdown
|
106
|
-
|
17
|
+
log('Got SIGINT, shutting down gracefully')
|
18
|
+
@service.graceful_shutdown
|
19
|
+
rescue SystemExit
|
20
|
+
# ignore
|
107
21
|
rescue Exception => e
|
108
|
-
|
109
|
-
|
110
|
-
|
22
|
+
log("Uncaught exception", error: e, source: e.source_fiber, raising: e.raising_fiber, backtrace: e.backtrace)
|
23
|
+
ensure
|
24
|
+
log('DF server stopped')
|
111
25
|
end
|
data/df/server_utils.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'tipi/digital_fabric'
|
6
|
+
require 'tipi/digital_fabric/executive'
|
7
|
+
require 'json'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'time'
|
10
|
+
require 'polyphony/extensions/debug'
|
11
|
+
|
12
|
+
FileUtils.cd(__dir__)
|
13
|
+
|
14
|
+
@service = DigitalFabric::Service.new(token: 'foobar')
|
15
|
+
@executive = DigitalFabric::Executive.new(@service, { host: '@executive.realiteq.net' })
|
16
|
+
|
17
|
+
@pid = Process.pid
|
18
|
+
|
19
|
+
def log(msg, **ctx)
|
20
|
+
text = format(
|
21
|
+
"%s (%d) %s\n",
|
22
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
|
23
|
+
@pid,
|
24
|
+
msg
|
25
|
+
)
|
26
|
+
STDOUT.orig_write text
|
27
|
+
return if ctx.empty?
|
28
|
+
|
29
|
+
ctx.each { |k, v| STDOUT.orig_write format(" %s: %s\n", k, v.inspect) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def listen_http
|
33
|
+
spin(:http_listener) do
|
34
|
+
opts = {
|
35
|
+
reuse_addr: true,
|
36
|
+
dont_linger: true,
|
37
|
+
}
|
38
|
+
log('Listening for HTTP on localhost:10080')
|
39
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
|
40
|
+
id = 0
|
41
|
+
loop do
|
42
|
+
client = server.accept
|
43
|
+
# log("Accept HTTP connection", client: client)
|
44
|
+
spin("http#{id += 1}") do
|
45
|
+
@service.incr_connection_count
|
46
|
+
Tipi.client_loop(client, opts) { |req| @service.http_request(req) }
|
47
|
+
ensure
|
48
|
+
# log("Done with HTTP connection", client: client)
|
49
|
+
@service.decr_connection_count
|
50
|
+
end
|
51
|
+
rescue Polyphony::BaseException
|
52
|
+
raise
|
53
|
+
rescue Exception => e
|
54
|
+
log 'HTTP accept (unknown) error', error: e, backtrace: e.backtrace
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
|
60
|
+
|
61
|
+
def listen_https
|
62
|
+
spin(:https_listener) do
|
63
|
+
private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
|
64
|
+
c = IO.read('../../reality/ssl/cacert.pem')
|
65
|
+
certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
|
66
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
67
|
+
ctx.security_level = 0
|
68
|
+
cert = certificates.shift
|
69
|
+
log "SSL Certificate expires: #{cert.not_after.inspect}"
|
70
|
+
ctx.add_certificate(cert, private_key, certificates)
|
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
|
75
|
+
|
76
|
+
# TODO: further limit ciphers
|
77
|
+
# ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
|
78
|
+
# ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/tls.rb
|
79
|
+
|
80
|
+
opts = {
|
81
|
+
reuse_addr: true,
|
82
|
+
dont_linger: true,
|
83
|
+
secure_context: ctx,
|
84
|
+
alpn_protocols: Tipi::ALPN_PROTOCOLS
|
85
|
+
}
|
86
|
+
|
87
|
+
log('Listening for HTTPS on localhost:10443')
|
88
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
|
89
|
+
id = 0
|
90
|
+
loop do
|
91
|
+
client = server.accept rescue nil
|
92
|
+
next unless client
|
93
|
+
|
94
|
+
# log('Accept HTTPS client connection', client: client)
|
95
|
+
spin("https#{id += 1}") do
|
96
|
+
@service.incr_connection_count
|
97
|
+
Tipi.client_loop(client, opts) { |req| @service.http_request(req) }
|
98
|
+
rescue => e
|
99
|
+
log('Error while handling HTTPS client', client: client, error: e, backtrace: e.backtrace)
|
100
|
+
ensure
|
101
|
+
# log("Done with HTTP connection", client: client)
|
102
|
+
@service.decr_connection_count
|
103
|
+
end
|
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
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
UNIX_SOCKET_PATH = '/tmp/df.sock'
|
115
|
+
def listen_unix
|
116
|
+
spin(:unix_listener) do
|
117
|
+
log("Listening on #{UNIX_SOCKET_PATH}")
|
118
|
+
FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
|
119
|
+
socket = UNIXServer.new(UNIX_SOCKET_PATH)
|
120
|
+
|
121
|
+
id = 0
|
122
|
+
loop do
|
123
|
+
client = socket.accept
|
124
|
+
# log('Accept Unix connection', client: client)
|
125
|
+
spin("unix#{id += 1}") do
|
126
|
+
Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
|
127
|
+
end
|
128
|
+
rescue Polyphony::BaseException
|
129
|
+
raise
|
130
|
+
rescue Exception => e
|
131
|
+
log 'Unix accept error', error: e, backtrace: e.backtrace
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def listen_df
|
137
|
+
spin(:df_listener) do
|
138
|
+
opts = {
|
139
|
+
reuse_addr: true,
|
140
|
+
reuse_port: true,
|
141
|
+
dont_linger: true,
|
142
|
+
}
|
143
|
+
log('Listening for DF connections on localhost:4321')
|
144
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 4321, opts)
|
145
|
+
|
146
|
+
id = 0
|
147
|
+
loop do
|
148
|
+
client = server.accept
|
149
|
+
# log('Accept DF connection', client: client)
|
150
|
+
spin("df#{id += 1}") do
|
151
|
+
Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
|
152
|
+
end
|
153
|
+
rescue Polyphony::BaseException
|
154
|
+
raise
|
155
|
+
rescue Exception => e
|
156
|
+
log 'DF accept (unknown) error', error: e, backtrace: e.backtrace
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
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
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony'
|
4
|
+
require_relative '../lib/tipi_ext'
|
5
|
+
|
6
|
+
i, o = IO.pipe
|
7
|
+
|
8
|
+
module ::Kernel
|
9
|
+
def trace(*args)
|
10
|
+
STDOUT.orig_write(format_trace(args))
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_trace(args)
|
14
|
+
if args.first.is_a?(String)
|
15
|
+
if args.size > 1
|
16
|
+
format("%s: %p\n", args.shift, args)
|
17
|
+
else
|
18
|
+
format("%s\n", args.first)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
format("%p\n", args.size == 1 ? args.first : args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
f = spin do
|
27
|
+
parser = Tipi::HTTP1Parser.new(i)
|
28
|
+
while true
|
29
|
+
trace '*' * 40
|
30
|
+
headers = parser.parse_headers
|
31
|
+
break unless headers
|
32
|
+
trace headers
|
33
|
+
|
34
|
+
body = parser.read_body
|
35
|
+
trace "body: #{body ? body.bytesize : 0} bytes"
|
36
|
+
trace body if body && body.bytesize < 80
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
o << "GET /a HTTP/1.1\r\n\r\n"
|
41
|
+
|
42
|
+
# o << "GET /a HTTP/1.1\r\nContent-Length: 0\r\n\r\n"
|
43
|
+
|
44
|
+
# o << "GET / HTTP/1.1\r\nHost: localhost:10080\r\nUser-Agent: curl/7.74.0\r\nAccept: */*\r\n\r\n"
|
45
|
+
|
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}"
|
50
|
+
|
51
|
+
o << "get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
|
52
|
+
|
53
|
+
o.close
|
54
|
+
|
55
|
+
f.await
|
data/examples/http_server.js
CHANGED
data/examples/http_server.rb
CHANGED
@@ -9,10 +9,19 @@ opts = {
|
|
9
9
|
}
|
10
10
|
|
11
11
|
puts "pid: #{Process.pid}"
|
12
|
-
puts 'Listening on port
|
12
|
+
puts 'Listening on port 10080...'
|
13
|
+
|
14
|
+
# GC.disable
|
15
|
+
# Thread.current.backend.idle_gc_period = 60
|
16
|
+
|
17
|
+
spin_loop(interval: 10) { p Thread.backend.stats }
|
18
|
+
|
19
|
+
spin_loop(interval: 10) do
|
20
|
+
GC.compact
|
21
|
+
end
|
13
22
|
|
14
23
|
spin do
|
15
|
-
Tipi.serve('0.0.0.0',
|
24
|
+
Tipi.serve('0.0.0.0', 10080, opts) do |req|
|
16
25
|
if req.path == '/stream'
|
17
26
|
req.send_headers('Foo' => 'Bar')
|
18
27
|
sleep 1
|
@@ -20,10 +29,13 @@ spin do
|
|
20
29
|
sleep 1
|
21
30
|
req.send_chunk("bar\n")
|
22
31
|
req.finish
|
32
|
+
elsif req.path == '/upload'
|
33
|
+
body = req.read
|
34
|
+
req.respond("Body: #{body.inspect} (#{body.bytesize} bytes)")
|
23
35
|
else
|
24
36
|
req.respond("Hello world!\n")
|
25
37
|
end
|
26
|
-
p req.transfer_counts
|
38
|
+
# p req.transfer_counts
|
27
39
|
end
|
28
40
|
p 'done...'
|
29
41
|
end.await
|
@@ -16,23 +16,11 @@ root_path = FileUtils.pwd
|
|
16
16
|
|
17
17
|
trap('INT') { exit! }
|
18
18
|
|
19
|
-
|
20
|
-
req.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
req.on('spliced') do
|
29
|
-
path = File.join(root_path, req.route_relative_path)
|
30
|
-
if File.file?(path)
|
31
|
-
req.serve_file(path, respond_from_io: true)
|
32
|
-
else
|
33
|
-
req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
|
34
|
-
end
|
19
|
+
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
20
|
+
path = File.join(root_path, req.path)
|
21
|
+
if File.file?(path)
|
22
|
+
req.serve_file(path)
|
23
|
+
else
|
24
|
+
req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
|
35
25
|
end
|
36
26
|
end
|
37
|
-
|
38
|
-
Tipi.serve('0.0.0.0', 4411, opts, &app)
|
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
|