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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +27 -0
  3. data/.gitignore +56 -0
  4. data/CHANGELOG.md +33 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +50 -0
  7. data/LICENSE +21 -0
  8. data/README.md +23 -0
  9. data/Rakefile +12 -0
  10. data/TODO.md +66 -0
  11. data/bin/tipi +12 -0
  12. data/docs/README.md +62 -0
  13. data/docs/summary.md +60 -0
  14. data/examples/cuba.ru +23 -0
  15. data/examples/hanami-api.ru +23 -0
  16. data/examples/http_server.js +24 -0
  17. data/examples/http_server.rb +21 -0
  18. data/examples/http_server_forked.rb +29 -0
  19. data/examples/http_server_graceful.rb +27 -0
  20. data/examples/http_server_simple.rb +11 -0
  21. data/examples/http_server_throttled.rb +15 -0
  22. data/examples/http_server_timeout.rb +35 -0
  23. data/examples/http_ws_server.rb +37 -0
  24. data/examples/https_server.rb +24 -0
  25. data/examples/https_server_forked.rb +32 -0
  26. data/examples/https_wss_server.rb +39 -0
  27. data/examples/rack_server.rb +12 -0
  28. data/examples/rack_server_https.rb +19 -0
  29. data/examples/rack_server_https_forked.rb +27 -0
  30. data/examples/websocket_secure_server.rb +27 -0
  31. data/examples/websocket_server.rb +24 -0
  32. data/examples/ws_page.html +34 -0
  33. data/examples/wss_page.html +34 -0
  34. data/lib/tipi.rb +54 -0
  35. data/lib/tipi/http1_adapter.rb +268 -0
  36. data/lib/tipi/http2_adapter.rb +74 -0
  37. data/lib/tipi/http2_stream.rb +134 -0
  38. data/lib/tipi/rack_adapter.rb +67 -0
  39. data/lib/tipi/request.rb +118 -0
  40. data/lib/tipi/version.rb +5 -0
  41. data/lib/tipi/websocket.rb +61 -0
  42. data/test/coverage.rb +45 -0
  43. data/test/eg.rb +27 -0
  44. data/test/helper.rb +51 -0
  45. data/test/run.rb +5 -0
  46. data/test/test_http_server.rb +321 -0
  47. data/tipi.gemspec +34 -0
  48. metadata +241 -0
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ puts "pid: #{Process.pid}"
7
+ puts 'Listening on port 1234...'
8
+
9
+ Tipi.serve('0.0.0.0', 1234) do |req|
10
+ req.respond("Hello world!\n")
11
+ end
@@ -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