tp2 0.4 → 0.6
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 +4 -4
- data/.github/workflows/test.yml +24 -0
- data/.gitignore +1 -1
- data/CHANGELOG.md +16 -0
- data/README.md +9 -2
- data/Rakefile +1 -1
- data/examples/simple.rb +11 -4
- data/lib/tp2/http1_adapter.rb +12 -10
- data/lib/tp2/request_extensions.rb +0 -2
- data/lib/tp2/server.rb +46 -3
- data/lib/tp2/version.rb +1 -1
- data/test/test_http1_adapter.rb +0 -17
- data/test/test_server.rb +20 -0
- data/tp2.gemspec +1 -1
- metadata +5 -4
- data/Gemfile.lock +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02dcea75056239e37b8cd92705737bf8516719723b63f6610667f657b9d42667
|
4
|
+
data.tar.gz: 22660d26e8e6c92c84b033b12f8a095e1eb35d5273067020bb9393dc2560581f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0783815bf52da7c2f1cb813022f2717adaa2cd6b169923d72c5817b255a1a4d2139b46e47b0f46ff085080af4b8fcf3c5065ac2d3b29a974f7417ce13f0cd1e6'
|
7
|
+
data.tar.gz: f9720342b6c7a9b78b70c45fadbd5f9dc95ed7720f0c40a60716a963e935acdeb01bc8d338b5cce759b81c4b76ee6e0361e37f519f89a9fca3e03a5b5395654a
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
os: [ubuntu-latest]
|
11
|
+
ruby: [3.4, 'head']
|
12
|
+
|
13
|
+
name: >-
|
14
|
+
${{matrix.os}}, ${{matrix.ruby}}
|
15
|
+
|
16
|
+
runs-on: ${{matrix.os}}
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v3
|
19
|
+
- uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{matrix.ruby}}
|
22
|
+
bundler-cache: true
|
23
|
+
- name: Run tests
|
24
|
+
run: bundle exec rake test
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Version 0.6 2025-05-05
|
2
|
+
|
3
|
+
- Update UringMachine to 0.10
|
4
|
+
|
5
|
+
# Version 0.5.2 2025-05-05
|
6
|
+
|
7
|
+
- Update UringMachine to 0.8.2
|
8
|
+
|
9
|
+
# Version 0.5 2025-05-05
|
10
|
+
|
11
|
+
- Implement graceful shutdown
|
12
|
+
|
13
|
+
# Version 0.4 2025-05-02
|
14
|
+
|
15
|
+
- Add adapter and server tests
|
16
|
+
- Remove http/parser dependency, use regexs to parse HTTP requests
|
data/README.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# TP2
|
2
2
|
|
3
3
|
TP2 is an experimental HTTP server based on
|
4
|
-
[UringMachine](https://github.com/digital-fabric/uringmachine) and [Qeweney](https://github.com/digital-fabric/qeweney).
|
4
|
+
[UringMachine](https://github.com/digital-fabric/uringmachine) and [Qeweney](https://github.com/digital-fabric/qeweney).
|
5
5
|
|
6
|
+
## Features
|
7
|
+
|
8
|
+
- HTTP/1.1
|
9
|
+
- Support for streaming requests and responses
|
10
|
+
- Automatic chunked transfer encoding
|
11
|
+
- Fast, native HTTP protocol parsing (no dependency)
|
12
|
+
- Graceful shutdown
|
data/Rakefile
CHANGED
data/examples/simple.rb
CHANGED
@@ -16,12 +16,19 @@ machine = UM.new
|
|
16
16
|
server = TP2::Server.new(machine, '0.0.0.0', 1234, &app)
|
17
17
|
puts "Listening on port 1234..."
|
18
18
|
|
19
|
-
machine.spin { server.run }
|
19
|
+
server_fiber = machine.spin { server.run }
|
20
20
|
|
21
|
-
|
22
|
-
trap('SIGINT') { machine.
|
21
|
+
sig_queue = UM::Queue.new
|
22
|
+
trap('SIGINT') { machine.push(sig_queue, :SIGINT) }
|
23
23
|
|
24
24
|
puts "Running... (pid: #{Process.pid})"
|
25
25
|
STDOUT.flush
|
26
|
-
|
26
|
+
|
27
|
+
# wait for signal
|
28
|
+
sig = machine.shift(sig_queue)
|
29
|
+
|
30
|
+
puts "Got signal (#{sig}), shutting down gracefully..."
|
31
|
+
machine.schedule(server_fiber, UM::Terminate.new)
|
32
|
+
machine.join(server_fiber)
|
33
|
+
|
27
34
|
puts
|
data/lib/tp2/http1_adapter.rb
CHANGED
@@ -20,8 +20,10 @@ module TP2
|
|
20
20
|
persist = serve_request
|
21
21
|
break if !persist
|
22
22
|
end
|
23
|
+
rescue UM::Terminate
|
24
|
+
# do nothing
|
23
25
|
rescue => e
|
24
|
-
puts '
|
26
|
+
puts '!' * 40
|
25
27
|
p e
|
26
28
|
puts e.backtrace.join("\n")
|
27
29
|
exit!
|
@@ -66,7 +68,7 @@ module TP2
|
|
66
68
|
|
67
69
|
return nil if headers[':body-done-reading']
|
68
70
|
|
69
|
-
# if content-length is not specified, we read to EOF, up to max 1MB size
|
71
|
+
# if content-length is not specified, we read to EOF, up to max 1MB size
|
70
72
|
chunk = read(1 << 20, nil, false)
|
71
73
|
headers[':body-done-reading'] = true
|
72
74
|
chunk
|
@@ -160,17 +162,17 @@ module TP2
|
|
160
162
|
def parse_headers
|
161
163
|
headers = get_request_line
|
162
164
|
return nil if !headers
|
163
|
-
|
165
|
+
|
164
166
|
while true
|
165
167
|
line = get_line
|
166
168
|
break if line.nil? || line.empty?
|
167
169
|
|
168
170
|
m = line.match(RE_HEADER_LINE)
|
169
171
|
raise ProtocolError, 'Invalid header' if !m
|
170
|
-
|
172
|
+
|
171
173
|
headers[m[1].downcase] = m[2]
|
172
174
|
end
|
173
|
-
|
175
|
+
|
174
176
|
headers
|
175
177
|
end
|
176
178
|
|
@@ -178,7 +180,7 @@ module TP2
|
|
178
180
|
while true
|
179
181
|
line = @sio.gets(chomp: true)
|
180
182
|
return line if line
|
181
|
-
|
183
|
+
|
182
184
|
res = @machine.read(@fd, @buffer, 65536, -1)
|
183
185
|
return nil if res == 0
|
184
186
|
end
|
@@ -187,10 +189,10 @@ module TP2
|
|
187
189
|
def get_request_line
|
188
190
|
line = get_line
|
189
191
|
return nil if !line
|
190
|
-
|
192
|
+
|
191
193
|
m = line.match(RE_REQUEST_LINE)
|
192
194
|
raise ProtocolError, 'Invalid request line' if !m
|
193
|
-
|
195
|
+
|
194
196
|
{
|
195
197
|
':method' => m[1].downcase,
|
196
198
|
':path' => m[2],
|
@@ -227,7 +229,7 @@ module TP2
|
|
227
229
|
|
228
230
|
return buf
|
229
231
|
end
|
230
|
-
|
232
|
+
|
231
233
|
|
232
234
|
left -= res
|
233
235
|
end
|
@@ -237,7 +239,7 @@ module TP2
|
|
237
239
|
def read_chunk(headers, buf)
|
238
240
|
chunk_size = get_line
|
239
241
|
return nil if !chunk_size
|
240
|
-
|
242
|
+
|
241
243
|
chunk_size = chunk_size.to_i(16)
|
242
244
|
if chunk_size == 0
|
243
245
|
headers[':body-done-reading'] = true
|
data/lib/tp2/server.rb
CHANGED
@@ -5,6 +5,9 @@ require 'tp2/request_extensions'
|
|
5
5
|
|
6
6
|
module TP2
|
7
7
|
class Server
|
8
|
+
PENDING_REQUESTS_GRACE_PERIOD = 0.1
|
9
|
+
PENDING_REQUESTS_TIMEOUT_PERIOD = 5
|
10
|
+
|
8
11
|
def initialize(machine, hostname, port, &app)
|
9
12
|
@machine = machine
|
10
13
|
@hostname = hostname
|
@@ -12,21 +15,61 @@ module TP2
|
|
12
15
|
@app = app
|
13
16
|
end
|
14
17
|
|
18
|
+
def run
|
19
|
+
setup
|
20
|
+
accept_incoming
|
21
|
+
rescue UM::Terminate
|
22
|
+
graceful_shutdown
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
15
27
|
def setup
|
16
28
|
@server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
|
17
29
|
@machine.setsockopt(@server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
|
18
30
|
@machine.bind(@server_fd, @hostname, @port)
|
19
31
|
@machine.listen(@server_fd, UM::SOMAXCONN)
|
20
32
|
|
33
|
+
# map fibers
|
34
|
+
@fiber_map = {}
|
35
|
+
|
21
36
|
# puts "Listening on #{@hostname}:#{@port}"
|
22
37
|
end
|
23
38
|
|
24
|
-
def
|
25
|
-
setup
|
39
|
+
def accept_incoming
|
26
40
|
@machine.accept_each(@server_fd) do |fd|
|
27
41
|
conn = HTTP1Adapter.new(@machine, fd, &@app)
|
28
|
-
@machine.spin(conn)
|
42
|
+
f = @machine.spin(conn) do
|
43
|
+
it.run
|
44
|
+
ensure
|
45
|
+
@fiber_map.delete(f)
|
46
|
+
end
|
47
|
+
@fiber_map[f] = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def graceful_shutdown
|
52
|
+
# stop listening
|
53
|
+
@machine.close(@server_fd)
|
54
|
+
|
55
|
+
return if @fiber_map.empty?
|
56
|
+
|
57
|
+
# sleep for a bit, let requests finish
|
58
|
+
@machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
|
59
|
+
return if @fiber_map.empty?
|
60
|
+
|
61
|
+
# terminate pending fibers
|
62
|
+
pending = @fiber_map.keys
|
63
|
+
signal = UM::Terminate.new
|
64
|
+
pending.each { @machine.schedule(it, signal) }
|
65
|
+
|
66
|
+
@machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
|
67
|
+
@machine.join(*@fiber_map.keys)
|
68
|
+
rescue UM::Terminate
|
69
|
+
# timeout on waiting for adapters to finish running, do nothing
|
29
70
|
end
|
71
|
+
ensure
|
72
|
+
@machine.close(@server_fd)
|
30
73
|
end
|
31
74
|
end
|
32
75
|
end
|
data/lib/tp2/version.rb
CHANGED
data/test/test_http1_adapter.rb
CHANGED
@@ -452,23 +452,6 @@ class HTTP1AdapterTest < Minitest::Test
|
|
452
452
|
p e.backtrace.join("\n")
|
453
453
|
end
|
454
454
|
|
455
|
-
opts = {
|
456
|
-
upgrade: {
|
457
|
-
echo: lambda do |adapter, _headers|
|
458
|
-
conn = adapter.conn
|
459
|
-
conn << <<~HTTP.crlf_lines
|
460
|
-
HTTP/1.1 101 Switching Protocols
|
461
|
-
Upgrade: echo
|
462
|
-
Connection: Upgrade
|
463
|
-
|
464
|
-
HTTP
|
465
|
-
|
466
|
-
conn.read_loop { |data| conn << data }
|
467
|
-
done = true
|
468
|
-
end
|
469
|
-
}
|
470
|
-
}
|
471
|
-
|
472
455
|
msg = "GET / HTTP/1.1\r\nUpgrade: echo\r\nConnection: upgrade\r\n\r\n"
|
473
456
|
write_http_request(msg, false)
|
474
457
|
@machine.spin { @adapter.serve_request rescue nil }
|
data/test/test_server.rb
CHANGED
@@ -85,6 +85,26 @@ class ServerTest < Minitest::Test
|
|
85
85
|
assert_equal(expected, response)
|
86
86
|
end
|
87
87
|
|
88
|
+
def test_graceful_shutdown
|
89
|
+
@app = ->(req) do
|
90
|
+
@machine.sleep(1)
|
91
|
+
req.respond('Hello, world!', {})
|
92
|
+
rescue UM::Terminate
|
93
|
+
req.respond('Terminated!', {})
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
|
97
|
+
write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHMET /bar HTTP/1.1\r\n\r\n"
|
98
|
+
|
99
|
+
@machine.sleep(0.01)
|
100
|
+
@machine.schedule(@f_server, UM::Terminate.new)
|
101
|
+
@machine.snooze
|
102
|
+
|
103
|
+
response = read_client_side
|
104
|
+
expected = "HTTP/1.1 200\r\nContent-Length: 11\r\n\r\nTerminated!"
|
105
|
+
assert_equal(expected, response)
|
106
|
+
end
|
107
|
+
|
88
108
|
def test_pipelined_requests_with_body
|
89
109
|
skip
|
90
110
|
|
data/tp2.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tp2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -15,14 +15,14 @@ dependencies:
|
|
15
15
|
requirements:
|
16
16
|
- - '='
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '0.
|
18
|
+
version: '0.10'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
23
|
- - '='
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '0.
|
25
|
+
version: '0.10'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: qeweney
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,9 +43,10 @@ extensions: []
|
|
43
43
|
extra_rdoc_files:
|
44
44
|
- README.md
|
45
45
|
files:
|
46
|
+
- ".github/workflows/test.yml"
|
46
47
|
- ".gitignore"
|
48
|
+
- CHANGELOG.md
|
47
49
|
- Gemfile
|
48
|
-
- Gemfile.lock
|
49
50
|
- README.md
|
50
51
|
- Rakefile
|
51
52
|
- TODO.md
|
data/Gemfile.lock
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
tp2 (0.4)
|
5
|
-
qeweney (= 0.21)
|
6
|
-
uringmachine (= 0.7)
|
7
|
-
|
8
|
-
GEM
|
9
|
-
remote: https://rubygems.org/
|
10
|
-
specs:
|
11
|
-
docile (1.4.1)
|
12
|
-
escape_utils (1.3.0)
|
13
|
-
ffi (1.17.2-aarch64-linux-gnu)
|
14
|
-
ffi (1.17.2-aarch64-linux-musl)
|
15
|
-
ffi (1.17.2-arm-linux-gnu)
|
16
|
-
ffi (1.17.2-arm-linux-musl)
|
17
|
-
ffi (1.17.2-arm64-darwin)
|
18
|
-
ffi (1.17.2-x86_64-darwin)
|
19
|
-
ffi (1.17.2-x86_64-linux-gnu)
|
20
|
-
ffi (1.17.2-x86_64-linux-musl)
|
21
|
-
listen (3.9.0)
|
22
|
-
rb-fsevent (~> 0.10, >= 0.10.3)
|
23
|
-
rb-inotify (~> 0.9, >= 0.9.10)
|
24
|
-
minitest (5.25.4)
|
25
|
-
qeweney (0.21)
|
26
|
-
escape_utils (= 1.3.0)
|
27
|
-
rake (13.2.1)
|
28
|
-
rake-compiler (1.2.9)
|
29
|
-
rake
|
30
|
-
rb-fsevent (0.11.2)
|
31
|
-
rb-inotify (0.11.1)
|
32
|
-
ffi (~> 1.0)
|
33
|
-
simplecov (0.22.0)
|
34
|
-
docile (~> 1.1)
|
35
|
-
simplecov-html (~> 0.11)
|
36
|
-
simplecov_json_formatter (~> 0.1)
|
37
|
-
simplecov-html (0.13.1)
|
38
|
-
simplecov_json_formatter (0.1.4)
|
39
|
-
uringmachine (0.7)
|
40
|
-
yard (0.9.37)
|
41
|
-
|
42
|
-
PLATFORMS
|
43
|
-
aarch64-linux-gnu
|
44
|
-
aarch64-linux-musl
|
45
|
-
arm-linux-gnu
|
46
|
-
arm-linux-musl
|
47
|
-
arm64-darwin
|
48
|
-
x86_64-darwin
|
49
|
-
x86_64-linux-gnu
|
50
|
-
x86_64-linux-musl
|
51
|
-
|
52
|
-
DEPENDENCIES
|
53
|
-
listen (= 3.9.0)
|
54
|
-
minitest (= 5.25.4)
|
55
|
-
rake (= 13.2.1)
|
56
|
-
rake-compiler (= 1.2.9)
|
57
|
-
simplecov (= 0.22.0)
|
58
|
-
tp2!
|
59
|
-
yard (= 0.9.37)
|
60
|
-
|
61
|
-
BUNDLED WITH
|
62
|
-
2.6.2
|