tipi 0.30
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 +7 -0
- data/.github/workflows/test.yml +27 -0
- data/.gitignore +56 -0
- data/CHANGELOG.md +33 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +50 -0
- data/LICENSE +21 -0
- data/README.md +23 -0
- data/Rakefile +12 -0
- data/TODO.md +66 -0
- data/bin/tipi +12 -0
- data/docs/README.md +62 -0
- data/docs/summary.md +60 -0
- data/examples/cuba.ru +23 -0
- data/examples/hanami-api.ru +23 -0
- data/examples/http_server.js +24 -0
- data/examples/http_server.rb +21 -0
- data/examples/http_server_forked.rb +29 -0
- data/examples/http_server_graceful.rb +27 -0
- data/examples/http_server_simple.rb +11 -0
- data/examples/http_server_throttled.rb +15 -0
- data/examples/http_server_timeout.rb +35 -0
- data/examples/http_ws_server.rb +37 -0
- data/examples/https_server.rb +24 -0
- data/examples/https_server_forked.rb +32 -0
- data/examples/https_wss_server.rb +39 -0
- data/examples/rack_server.rb +12 -0
- data/examples/rack_server_https.rb +19 -0
- data/examples/rack_server_https_forked.rb +27 -0
- data/examples/websocket_secure_server.rb +27 -0
- data/examples/websocket_server.rb +24 -0
- data/examples/ws_page.html +34 -0
- data/examples/wss_page.html +34 -0
- data/lib/tipi.rb +54 -0
- data/lib/tipi/http1_adapter.rb +268 -0
- data/lib/tipi/http2_adapter.rb +74 -0
- data/lib/tipi/http2_stream.rb +134 -0
- data/lib/tipi/rack_adapter.rb +67 -0
- data/lib/tipi/request.rb +118 -0
- data/lib/tipi/version.rb +5 -0
- data/lib/tipi/websocket.rb +61 -0
- data/test/coverage.rb +45 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +51 -0
- data/test/run.rb +5 -0
- data/test/test_http_server.rb +321 -0
- data/tipi.gemspec +34 -0
- metadata +241 -0
data/examples/cuba.ru
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuba'
|
4
|
+
require 'cuba/safe'
|
5
|
+
require 'delegate' # See https://github.com/rack/rack/pull/1610
|
6
|
+
|
7
|
+
Cuba.use Rack::Session::Cookie, secret: '__a_very_long_string__'
|
8
|
+
|
9
|
+
Cuba.plugin Cuba::Safe
|
10
|
+
|
11
|
+
Cuba.define do
|
12
|
+
on get do
|
13
|
+
on 'hello' do
|
14
|
+
res.write 'Hello world!'
|
15
|
+
end
|
16
|
+
|
17
|
+
on root do
|
18
|
+
res.redirect '/hello'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
run Cuba
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hanami/api'
|
4
|
+
|
5
|
+
class ExampleApi < Hanami::API
|
6
|
+
get '/hello' do
|
7
|
+
'Hello world!'
|
8
|
+
end
|
9
|
+
|
10
|
+
get '/' do
|
11
|
+
redirect '/hello'
|
12
|
+
end
|
13
|
+
|
14
|
+
get '/404' do
|
15
|
+
404
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/500' do
|
19
|
+
500
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
run ExampleApi.new
|
@@ -0,0 +1,24 @@
|
|
1
|
+
// For the sake of comparing performance, here's a node.js-based HTTP server
|
2
|
+
// doing roughly the same thing as http_server. Preliminary benchmarking shows
|
3
|
+
// the ruby version has a throughput (req/s) of about 2/3 of the JS version.
|
4
|
+
|
5
|
+
const http = require('http');
|
6
|
+
|
7
|
+
const MSG = 'Hello World';
|
8
|
+
|
9
|
+
const server = http.createServer((req, res) => {
|
10
|
+
// let requestCopy = {
|
11
|
+
// method: req.method,
|
12
|
+
// request_url: req.url,
|
13
|
+
// headers: req.headers
|
14
|
+
// };
|
15
|
+
|
16
|
+
// res.writeHead(200, { 'Content-Type': 'application/json' });
|
17
|
+
// res.end(JSON.stringify(requestCopy));
|
18
|
+
|
19
|
+
res.writeHead(200);
|
20
|
+
res.end(MSG)
|
21
|
+
});
|
22
|
+
|
23
|
+
server.listen(1235);
|
24
|
+
console.log('Listening on port 1235');
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
opts = {
|
7
|
+
reuse_addr: true,
|
8
|
+
dont_linger: true
|
9
|
+
}
|
10
|
+
|
11
|
+
spin do
|
12
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
13
|
+
req.respond("Hello world!\n")
|
14
|
+
rescue Exception => e
|
15
|
+
p e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
puts "pid: #{Process.pid}"
|
20
|
+
puts 'Listening on port 1234...'
|
21
|
+
suspend
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
opts = {
|
9
|
+
reuse_addr: true,
|
10
|
+
dont_linger: true
|
11
|
+
}
|
12
|
+
|
13
|
+
server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
|
14
|
+
|
15
|
+
puts 'Listening on port 1234'
|
16
|
+
|
17
|
+
child_pids = []
|
18
|
+
8.times do
|
19
|
+
pid = Polyphony.fork do
|
20
|
+
puts "forked pid: #{Process.pid}"
|
21
|
+
server.each do |req|
|
22
|
+
req.respond("Hello world! from pid: #{Process.pid}\n")
|
23
|
+
end
|
24
|
+
rescue Interrupt
|
25
|
+
end
|
26
|
+
child_pids << pid
|
27
|
+
end
|
28
|
+
|
29
|
+
child_pids.each { |pid| Thread.current.agent.waitpid(pid) }
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
require 'tipi'
|
6
|
+
|
7
|
+
opts = {
|
8
|
+
reuse_addr: true,
|
9
|
+
dont_linger: true
|
10
|
+
}
|
11
|
+
|
12
|
+
server = spin do
|
13
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
14
|
+
req.respond("Hello world!\n")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
trap('SIGHUP') do
|
19
|
+
puts 'got hup'
|
20
|
+
server.interrupt
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "pid: #{Process.pid}"
|
24
|
+
puts 'Send HUP to stop gracefully'
|
25
|
+
puts 'Listening on port 1234...'
|
26
|
+
|
27
|
+
suspend
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
$throttler = throttle(1000)
|
7
|
+
opts = { reuse_addr: true, dont_linger: true }
|
8
|
+
spin do
|
9
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
10
|
+
$throttler.call { req.respond("Hello world!\n") }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "pid: #{Process.pid}"
|
15
|
+
puts 'Listening on port 1234...'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
|
6
|
+
opts = {
|
7
|
+
reuse_addr: true,
|
8
|
+
dont_linger: true
|
9
|
+
}
|
10
|
+
|
11
|
+
def timeout_handler(timeout, &handler)
|
12
|
+
->(req) do
|
13
|
+
cancel_after(timeout) { handler.(req) }
|
14
|
+
rescue Polyphony::Cancel
|
15
|
+
req.respond("timeout\n", ':status' => 502)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
sleep 0
|
20
|
+
|
21
|
+
spin do
|
22
|
+
Tipi.serve(
|
23
|
+
'0.0.0.0',
|
24
|
+
1234,
|
25
|
+
opts,
|
26
|
+
&timeout_handler(0.1) do |req|
|
27
|
+
sleep rand(0.01..0.2)
|
28
|
+
req.respond("Hello timeout world!\n")
|
29
|
+
end
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
puts "pid: #{Process.pid}"
|
34
|
+
puts 'Listening on port 1234...'
|
35
|
+
suspend
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'tipi/websocket'
|
6
|
+
|
7
|
+
def ws_handler(conn)
|
8
|
+
timer = spin do
|
9
|
+
throttled_loop(1) do
|
10
|
+
conn << Time.now.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
while (msg = conn.recv)
|
14
|
+
conn << "you said: #{msg}"
|
15
|
+
end
|
16
|
+
ensure
|
17
|
+
timer.stop
|
18
|
+
end
|
19
|
+
|
20
|
+
opts = {
|
21
|
+
reuse_addr: true,
|
22
|
+
dont_linger: true,
|
23
|
+
upgrade: {
|
24
|
+
websocket: Polyphony::Websocket.handler(&method(:ws_handler))
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
HTML = IO.read(File.join(__dir__, 'ws_page.html'))
|
29
|
+
|
30
|
+
spin do
|
31
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
32
|
+
req.respond(HTML, 'Content-Type' => 'text/html')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "pid: #{Process.pid}"
|
37
|
+
puts 'Listening on port 1234...'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
8
|
+
|
9
|
+
authority = Localhost::Authority.fetch
|
10
|
+
opts = {
|
11
|
+
reuse_addr: true,
|
12
|
+
dont_linger: true,
|
13
|
+
secure_context: authority.server_context
|
14
|
+
}
|
15
|
+
|
16
|
+
puts "pid: #{Process.pid}"
|
17
|
+
puts 'Listening on port 1234...'
|
18
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
19
|
+
req.respond("Hello world!\n")
|
20
|
+
# req.send_headers
|
21
|
+
# req.send_chunk("Method: #{req.method}\n")
|
22
|
+
# req.send_chunk("Path: #{req.path}\n")
|
23
|
+
# req.send_chunk("Query: #{req.query.inspect}\n", done: true)
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
::Exception.__disable_sanitized_backtrace__ = true
|
8
|
+
|
9
|
+
authority = Localhost::Authority.fetch
|
10
|
+
opts = {
|
11
|
+
reuse_addr: true,
|
12
|
+
dont_linger: true,
|
13
|
+
secure_context: authority.server_context
|
14
|
+
}
|
15
|
+
|
16
|
+
server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
|
17
|
+
|
18
|
+
puts 'Listening on port 1234'
|
19
|
+
|
20
|
+
child_pids = []
|
21
|
+
4.times do
|
22
|
+
pid = Polyphony.fork do
|
23
|
+
puts "forked pid: #{Process.pid}"
|
24
|
+
server.each do |req|
|
25
|
+
req.respond("Hello world!\n")
|
26
|
+
end
|
27
|
+
rescue Interrupt
|
28
|
+
end
|
29
|
+
child_pids << pid
|
30
|
+
end
|
31
|
+
|
32
|
+
child_pids.each { |pid| Thread.current.agent.waitpid(pid) }
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
def ws_handler(conn)
|
8
|
+
timer = spin do
|
9
|
+
throttled_loop(1) do
|
10
|
+
conn << Time.now.to_s
|
11
|
+
rescue StandardError
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
while (msg = conn.recv)
|
16
|
+
puts "msg: #{msg}"
|
17
|
+
# conn << "you said: #{msg}"
|
18
|
+
end
|
19
|
+
ensure
|
20
|
+
timer.stop
|
21
|
+
end
|
22
|
+
|
23
|
+
authority = Localhost::Authority.fetch
|
24
|
+
opts = {
|
25
|
+
reuse_addr: true,
|
26
|
+
dont_linger: true,
|
27
|
+
secure_context: authority.server_context,
|
28
|
+
upgrade: {
|
29
|
+
websocket: Polyphony::Websocket.handler(&method(:ws_handler))
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
HTML = IO.read(File.join(__dir__, 'wss_page.html'))
|
34
|
+
|
35
|
+
puts "pid: #{Process.pid}"
|
36
|
+
puts 'Listening on port 1234...'
|
37
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
38
|
+
req.respond(HTML, 'Content-Type' => 'text/html')
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
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
|
+
app = Polyphony::HTTP::Server::RackAdapter.load(app_path)
|
8
|
+
opts = { reuse_addr: true, dont_linger: true }
|
9
|
+
|
10
|
+
puts 'listening on port 1234'
|
11
|
+
puts "pid: #{Process.pid}"
|
12
|
+
Tipi.serve('0.0.0.0', 1234, opts, &app)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
|
8
|
+
app = Polyphony::HTTP::Server::RackAdapter.load(app_path)
|
9
|
+
|
10
|
+
authority = Localhost::Authority.fetch
|
11
|
+
opts = {
|
12
|
+
reuse_addr: true,
|
13
|
+
dont_linger: true,
|
14
|
+
secure_context: authority.server_context
|
15
|
+
}
|
16
|
+
|
17
|
+
puts 'listening on port 1234'
|
18
|
+
puts "pid: #{Process.pid}"
|
19
|
+
Tipi.serve('0.0.0.0', 1234, opts, &app)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
|
8
|
+
app = Polyphony::HTTP::Server::RackAdapter.load(app_path)
|
9
|
+
|
10
|
+
authority = Localhost::Authority.fetch
|
11
|
+
opts = {
|
12
|
+
reuse_addr: true,
|
13
|
+
dont_linger: true,
|
14
|
+
secure_context: authority.server_context
|
15
|
+
}
|
16
|
+
server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
|
17
|
+
puts 'Listening on port 1234'
|
18
|
+
|
19
|
+
child_pids = []
|
20
|
+
4.times do
|
21
|
+
child_pids << Polyphony.fork do
|
22
|
+
puts "forked pid: #{Process.pid}"
|
23
|
+
server.each(&app)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
child_pids.each { |pid| EV::Child.new(pid).await }
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'tipi'
|
5
|
+
require 'localhost/authority'
|
6
|
+
|
7
|
+
def ws_handler(conn)
|
8
|
+
while (msg = conn.recv)
|
9
|
+
conn << "you said: #{msg}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
authority = Localhost::Authority.fetch
|
14
|
+
opts = {
|
15
|
+
reuse_addr: true,
|
16
|
+
dont_linger: true,
|
17
|
+
upgrade: {
|
18
|
+
websocket: Polyphony::Websocket.handler(&method(:ws_handler))
|
19
|
+
},
|
20
|
+
secure_context: authority.server_context
|
21
|
+
}
|
22
|
+
|
23
|
+
puts "pid: #{Process.pid}"
|
24
|
+
puts 'Listening on port 1234...'
|
25
|
+
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
26
|
+
req.respond("Hello world!\n")
|
27
|
+
end
|