tipi 0.36 → 0.39
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +49 -28
- data/Gemfile.lock +5 -5
- data/TODO.md +79 -5
- data/df/sample_agent.rb +1 -1
- data/df/server.rb +46 -4
- data/e +0 -0
- data/examples/http_request_ws_server.rb +2 -1
- data/examples/http_server.rb +11 -3
- data/examples/http_server_forked.rb +2 -0
- data/examples/http_server_throttled.rb +3 -2
- data/examples/https_server.rb +10 -1
- data/examples/https_wss_server.rb +2 -1
- data/examples/rack_server.rb +5 -0
- data/examples/rack_server_https.rb +1 -1
- data/examples/rack_server_https_forked.rb +4 -3
- data/examples/routing_server.rb +5 -4
- data/lib/tipi/digital_fabric/agent.rb +16 -13
- data/lib/tipi/digital_fabric/agent_proxy.rb +79 -27
- data/lib/tipi/digital_fabric/protocol.rb +71 -14
- data/lib/tipi/digital_fabric/request_adapter.rb +7 -7
- data/lib/tipi/digital_fabric/service.rb +10 -8
- data/lib/tipi/http1_adapter.rb +63 -35
- data/lib/tipi/http2_adapter.rb +35 -4
- data/lib/tipi/http2_stream.rb +64 -21
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +9 -22
- data/test/test_http_server.rb +22 -37
- data/test/test_request.rb +4 -4
- data/tipi.gemspec +2 -2
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02c5ce4798e1961ca59798b75bafe158570e777661f6fc8667169553df3cd3f4
|
4
|
+
data.tar.gz: 405bfa3526efaad394d34840764dc131230a366574cfc52b63344a6d5cdfe6dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 754bd35136e4e004e6bd782eb2060d933b2439c437a9c2a966529e0465c42096b97fa08b1dae04b7a3c8aa0c77c367fd5395d215cca73fd7cf1aa80c297b3178
|
7
|
+
data.tar.gz: 24425d798c076ad43b4fa45b641a90a5e801dfe35acd6217e8a8fc97ea546f18457fc9a8653e47f7bd5f696da885ef86db8db36385dcaa79ccbcf9d42d64081c
|
data/.github/workflows/test.yml
CHANGED
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,68 +1,89 @@
|
|
1
|
+
## 0.39 2021-06-20
|
2
|
+
|
3
|
+
- More work on DF server
|
4
|
+
- Fix HTTP2StreamHandler#send_headers
|
5
|
+
- Various fixes to HTTP/2 adapter
|
6
|
+
- Fix host detection for HTTP/2 connections
|
7
|
+
- Fix HTTP/1 adapter #respond with nil body
|
8
|
+
- Fix HTTP1Adapter#send_headers
|
9
|
+
|
10
|
+
## 0.38 2021-03-09
|
11
|
+
|
12
|
+
- Don't use chunked transfer encoding for non-streaming responses
|
13
|
+
|
14
|
+
## 0.37.2 2021-03-08
|
15
|
+
|
16
|
+
- Fix header formatting when header value is an array
|
17
|
+
|
18
|
+
## 0.37 2021-02-15
|
19
|
+
|
20
|
+
- Update upgrade mechanism to work with updated Qeweney API
|
21
|
+
|
1
22
|
## 0.36 2021-02-12
|
2
23
|
|
3
|
-
|
24
|
+
- Use `Qeweney::Status` constants
|
4
25
|
|
5
26
|
## 0.35 2021-02-10
|
6
27
|
|
7
|
-
|
28
|
+
- Extract Request class into separate [qeweney](https://github.com/digital-fabric/qeweney) gem
|
8
29
|
|
9
30
|
## 0.34 2021-02-07
|
10
31
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
32
|
+
- Implement digital fabric service and agents
|
33
|
+
- Add multipart and urlencoded form data parsing
|
34
|
+
- Improve request body reading behaviour
|
35
|
+
- Add more `Request` information methods
|
36
|
+
- Add access to connection for HTTP2 requests
|
37
|
+
- Allow calling `Request#send_chunk` with empty chunk
|
38
|
+
- Add support for handling protocol upgrades from within request handler
|
18
39
|
|
19
40
|
## 0.33 2020-11-20
|
20
41
|
|
21
|
-
|
22
|
-
|
42
|
+
- Update code for Polyphony 0.47.5
|
43
|
+
- Add support for Rack::File body to Tipi::RackAdapter
|
23
44
|
|
24
45
|
## 0.32 2020-08-14
|
25
46
|
|
26
|
-
|
27
|
-
|
28
|
-
|
47
|
+
- Respond with array of strings instead of concatenating for HTTP 1
|
48
|
+
- Use read_loop instead of readpartial
|
49
|
+
- Fix http upgrade test
|
29
50
|
|
30
51
|
## 0.31 2020-07-28
|
31
52
|
|
32
|
-
|
33
|
-
|
34
|
-
|
53
|
+
- Fix websocket server code
|
54
|
+
- Implement configuration layer (WIP)
|
55
|
+
- Improve performance of rack adapter
|
35
56
|
|
36
57
|
## 0.30 2020-07-15
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
59
|
+
- Rename project to Tipi
|
60
|
+
- Rearrange source code
|
61
|
+
- Remove HTTP client code (to be developed eventually into a separate gem)
|
62
|
+
- Fix header rendering in rack adapter (#2)
|
42
63
|
|
43
64
|
## 0.29 2020-07-06
|
44
65
|
|
45
|
-
|
66
|
+
- Use IO#read_loop
|
46
67
|
|
47
68
|
## 0.28 2020-07-03
|
48
69
|
|
49
|
-
|
70
|
+
- Update with API changes from Polyphony >= 0.41
|
50
71
|
|
51
72
|
## 0.27 2020-04-14
|
52
73
|
|
53
|
-
|
74
|
+
- Remove modulation dependency
|
54
75
|
|
55
76
|
## 0.26 2020-03-03
|
56
77
|
|
57
|
-
|
78
|
+
- Fix `Server#listen`
|
58
79
|
|
59
80
|
## 0.25 2020-02-19
|
60
81
|
|
61
|
-
|
62
|
-
|
82
|
+
- Ensure server socket is closed upon stopping loop
|
83
|
+
- Fix `Request#format_header_lines`
|
63
84
|
|
64
85
|
## 0.24 2020-01-08
|
65
86
|
|
66
|
-
|
87
|
+
- Move HTTP to separate polyphony-http gem
|
67
88
|
|
68
89
|
For earlier changes look at the Polyphony changelog.
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
tipi (0.
|
4
|
+
tipi (0.39)
|
5
5
|
http-2 (~> 0.10.0)
|
6
6
|
http_parser.rb (~> 0.6.0)
|
7
7
|
msgpack (~> 1.4.2)
|
8
|
-
polyphony (~> 0.
|
9
|
-
qeweney (~> 0.
|
8
|
+
polyphony (~> 0.55.0)
|
9
|
+
qeweney (~> 0.9.1)
|
10
10
|
rack (>= 2.0.8, < 2.3.0)
|
11
11
|
websocket (~> 1.2.8)
|
12
12
|
|
@@ -28,8 +28,8 @@ GEM
|
|
28
28
|
minitest (>= 5.0)
|
29
29
|
ruby-progressbar
|
30
30
|
msgpack (1.4.2)
|
31
|
-
polyphony (0.
|
32
|
-
qeweney (0.
|
31
|
+
polyphony (0.55.0)
|
32
|
+
qeweney (0.9.1)
|
33
33
|
escape_utils (~> 1.2.1)
|
34
34
|
rack (2.2.3)
|
35
35
|
rake (12.3.3)
|
data/TODO.md
CHANGED
@@ -1,8 +1,82 @@
|
|
1
|
-
|
1
|
+
## Add an API for reading a request body chunk into an IO (pipe)
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
# currently
|
5
|
+
chunk = req.next_chunk
|
6
|
+
# or
|
7
|
+
req.each_chunk { |c| do_something(c) }
|
8
|
+
|
9
|
+
# what we'd like to do
|
10
|
+
r, w = IO.pipe
|
11
|
+
len = req.splice_chunk(w)
|
12
|
+
sock << "Here comes a chunk of #{len} bytes\n"
|
13
|
+
sock.splice(r, len)
|
14
|
+
|
15
|
+
# or:
|
16
|
+
r, w = IO.pipe
|
17
|
+
req.splice_each_chunk(w) do |len|
|
18
|
+
sock << "Here comes a chunk of #{len} bytes\n"
|
19
|
+
sock.splice(r, len)
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
# HTTP/1.1 parser
|
24
|
+
|
25
|
+
- httparser.rb is not actively updated
|
26
|
+
- the httparser.rb C parser code comes originally from https://github.com/nodejs/llhttp
|
27
|
+
- there's a Ruby gem https://github.com/metabahn/llhttp, but its API is too low-level
|
28
|
+
(lots of callbacks, headers need to be retained across callbacks)
|
29
|
+
- the basic idea is to import the C-code, then build a parser object with the following
|
30
|
+
callbacks:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
on_headers_complete(headers)
|
34
|
+
on_body_chunk(chunk)
|
35
|
+
on_message_complete
|
36
|
+
```
|
37
|
+
|
38
|
+
- The llhttp gem's C-code is here: https://github.com/metabahn/llhttp/tree/main/mri
|
39
|
+
|
40
|
+
- Actually, if you do a C extension, instead of a callback-based API, we can
|
41
|
+
design a blocking API:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
parser = Tipi::HTTP1::Parser.new
|
45
|
+
parser.each_request(socket) do |headers|
|
46
|
+
request = Request.new(normalize_headers(headers))
|
47
|
+
handle_request(request)
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
# What about HTTP/2?
|
52
|
+
|
53
|
+
It would be a nice exercise in converting a callback-based API to a blocking
|
54
|
+
one:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
parser = Tipi::HTTP2::Parser.new(socket)
|
58
|
+
parser.each_stream(socket) do |stream|
|
59
|
+
spin { handle_stream(stream) }
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
# DF
|
66
|
+
|
67
|
+
- Add attack protection for IP-address HTTP host:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
IPV4_REGEXP = /^\d+\.\d+\.\d+\.\d+$/.freeze
|
71
|
+
|
72
|
+
def is_attack_request?(req)
|
73
|
+
return true if req.host =~ IPV4_REGEXP && req.query[:q] != 'ping'
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
- Add attack route to Qeweney routing API
|
2
78
|
|
3
|
-
Problems to fix:
|
4
79
|
|
5
|
-
- Memory leak (in server? multi agent? multi client?)
|
6
80
|
|
7
81
|
# Roadmap
|
8
82
|
|
@@ -14,7 +88,7 @@ Problems to fix:
|
|
14
88
|
- https://gitlab.com/honeyryderchuck/http-2-next
|
15
89
|
- Open an issue there, ask what's the difference between the two gems?
|
16
90
|
|
17
|
-
## 0.
|
91
|
+
## 0.38
|
18
92
|
|
19
93
|
- Add more poly CLI commands and options:
|
20
94
|
|
@@ -26,7 +100,7 @@ Problems to fix:
|
|
26
100
|
- set port to bind to
|
27
101
|
- set forking process count
|
28
102
|
|
29
|
-
## 0.
|
103
|
+
## 0.39 Working Sinatra application
|
30
104
|
|
31
105
|
- app with database access (postgresql)
|
32
106
|
- benchmarks!
|
data/df/sample_agent.rb
CHANGED
@@ -13,7 +13,7 @@ class SampleAgent < DigitalFabric::Agent
|
|
13
13
|
HTML_SSE = IO.read(File.join(__dir__, 'sse_page.html'))
|
14
14
|
|
15
15
|
def http_request(req)
|
16
|
-
path = req
|
16
|
+
path = req.headers[':path']
|
17
17
|
case path
|
18
18
|
when '/agent'
|
19
19
|
send_df_message(Protocol.http_response(
|
data/df/server.rb
CHANGED
@@ -6,6 +6,8 @@ require 'tipi/digital_fabric'
|
|
6
6
|
require 'tipi/digital_fabric/executive'
|
7
7
|
require 'json'
|
8
8
|
require 'fileutils'
|
9
|
+
require 'localhost/authority'
|
10
|
+
|
9
11
|
FileUtils.cd(__dir__)
|
10
12
|
|
11
13
|
service = DigitalFabric::Service.new(token: 'foobar')
|
@@ -19,13 +21,46 @@ end
|
|
19
21
|
|
20
22
|
puts "pid: #{Process.pid}"
|
21
23
|
|
22
|
-
|
24
|
+
http_listener = spin do
|
23
25
|
opts = {
|
24
26
|
reuse_addr: true,
|
25
27
|
dont_linger: true,
|
26
28
|
}
|
27
|
-
puts 'Listening on localhost:
|
28
|
-
server = Polyphony::Net.tcp_listen('0.0.0.0',
|
29
|
+
puts 'Listening for HTTP on localhost:10080'
|
30
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
|
31
|
+
server.accept_loop do |client|
|
32
|
+
spin do
|
33
|
+
service.incr_connection_count
|
34
|
+
Tipi.client_loop(client, opts) { |req| service.http_request(req) }
|
35
|
+
ensure
|
36
|
+
service.decr_connection_count
|
37
|
+
end
|
38
|
+
rescue Exception => e
|
39
|
+
puts "HTTP accept_loop error: #{e.inspect}"
|
40
|
+
puts e.backtrace.join("\n")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
|
45
|
+
|
46
|
+
https_listener = spin do
|
47
|
+
private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
|
48
|
+
c = IO.read('../../reality/ssl/cacert.pem')
|
49
|
+
certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
|
50
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
51
|
+
cert = certificates.shift
|
52
|
+
puts "Certificate expires: #{cert.not_after.inspect}"
|
53
|
+
ctx.add_certificate(cert, private_key, certificates)
|
54
|
+
# ctx = Localhost::Authority.fetch.server_context
|
55
|
+
opts = {
|
56
|
+
reuse_addr: true,
|
57
|
+
dont_linger: true,
|
58
|
+
secure_context: ctx,
|
59
|
+
alpn_protocols: Tipi::ALPN_PROTOCOLS
|
60
|
+
}
|
61
|
+
|
62
|
+
puts 'Listening for HTTPS on localhost:10443'
|
63
|
+
server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
|
29
64
|
server.accept_loop do |client|
|
30
65
|
spin do
|
31
66
|
service.incr_connection_count
|
@@ -33,6 +68,9 @@ tcp_listener = spin do
|
|
33
68
|
ensure
|
34
69
|
service.decr_connection_count
|
35
70
|
end
|
71
|
+
rescue Exception => e
|
72
|
+
puts "HTTPS accept_loop error: #{e.inspect}"
|
73
|
+
puts e.backtrace.join("\n")
|
36
74
|
end
|
37
75
|
end
|
38
76
|
|
@@ -46,9 +84,13 @@ unix_listener = spin do
|
|
46
84
|
end
|
47
85
|
|
48
86
|
begin
|
49
|
-
Fiber.await(
|
87
|
+
Fiber.await(http_listener, https_listener, unix_listener)
|
50
88
|
rescue Interrupt
|
51
89
|
puts "Got SIGINT, shutting down gracefully"
|
52
90
|
service.graceful_shutdown
|
53
91
|
puts "post graceful shutdown"
|
92
|
+
rescue Exception => e
|
93
|
+
puts '*' * 40
|
94
|
+
p e
|
95
|
+
puts e.backtrace.join("\n")
|
54
96
|
end
|
data/e
ADDED
File without changes
|
@@ -27,7 +27,8 @@ puts 'Listening on port 4411...'
|
|
27
27
|
|
28
28
|
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
29
29
|
if req.upgrade_protocol == 'websocket'
|
30
|
-
|
30
|
+
conn = req.upgrade_to_websocket
|
31
|
+
ws_handler(conn)
|
31
32
|
else
|
32
33
|
req.respond(HTML, 'Content-Type' => 'text/html')
|
33
34
|
end
|
data/examples/http_server.rb
CHANGED
@@ -13,9 +13,17 @@ puts 'Listening on port 4411...'
|
|
13
13
|
|
14
14
|
spin do
|
15
15
|
Tipi.serve('0.0.0.0', 4411, opts) do |req|
|
16
|
-
req.
|
17
|
-
|
18
|
-
|
16
|
+
if req.path == '/stream'
|
17
|
+
req.send_headers('Foo' => 'Bar')
|
18
|
+
sleep 1
|
19
|
+
req.send_chunk("foo\n")
|
20
|
+
sleep 1
|
21
|
+
req.send_chunk("bar\n")
|
22
|
+
req.finish
|
23
|
+
else
|
24
|
+
req.respond("Hello world!\n")
|
25
|
+
end
|
26
|
+
p req.transfer_counts
|
19
27
|
end
|
20
28
|
p 'done...'
|
21
29
|
end.await
|
@@ -3,9 +3,9 @@
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'tipi'
|
5
5
|
|
6
|
-
$throttler =
|
6
|
+
$throttler = Polyphony::Throttler.new(1000)
|
7
7
|
opts = { reuse_addr: true, dont_linger: true }
|
8
|
-
spin do
|
8
|
+
server = spin do
|
9
9
|
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
10
10
|
$throttler.call { req.respond("Hello world!\n") }
|
11
11
|
end
|
@@ -13,3 +13,4 @@ end
|
|
13
13
|
|
14
14
|
puts "pid: #{Process.pid}"
|
15
15
|
puts 'Listening on port 1234...'
|
16
|
+
server.await
|
data/examples/https_server.rb
CHANGED
@@ -16,7 +16,16 @@ opts = {
|
|
16
16
|
puts "pid: #{Process.pid}"
|
17
17
|
puts 'Listening on port 1234...'
|
18
18
|
Tipi.serve('0.0.0.0', 1234, opts) do |req|
|
19
|
-
req.
|
19
|
+
p path: req.path
|
20
|
+
if req.path == '/stream'
|
21
|
+
req.send_headers('Foo' => 'Bar')
|
22
|
+
sleep 0.5
|
23
|
+
req.send_chunk("foo\n")
|
24
|
+
sleep 0.5
|
25
|
+
req.send_chunk("bar\n", done: true)
|
26
|
+
else
|
27
|
+
req.respond("Hello world!\n")
|
28
|
+
end
|
20
29
|
# req.send_headers
|
21
30
|
# req.send_chunk("Method: #{req.method}\n")
|
22
31
|
# req.send_chunk("Path: #{req.path}\n")
|