tipi 0.30

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