tipi 0.30
Sign up to get free protection for your applications and to get access to all the features.
- 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
|