tipi 0.40 → 0.45
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 +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
|