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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d8c8ee2edd1c72a68b21c3ea27dc208c0dbfcbb2a6d78d76abb3ec11f115977
4
- data.tar.gz: 5fb43258443827cf5fa44fc1ddd4960740d8694d5469fac70875c46c54558c15
3
+ metadata.gz: 02dcea75056239e37b8cd92705737bf8516719723b63f6610667f657b9d42667
4
+ data.tar.gz: 22660d26e8e6c92c84b033b12f8a095e1eb35d5273067020bb9393dc2560581f
5
5
  SHA512:
6
- metadata.gz: e6c54083c10368f84bd23a0bfd235f01aa74690f7d02667a54c539921e9dc601e7f513858d3a85eee17229d4f4f12a6482d219feb1c1820cd2622c7e70e0dfa9
7
- data.tar.gz: 134a0b072495b5d23f02d1d19e86db66f4f78ffea81ce1e20b48660d77c61966bf83e4876f4870b33f8d63102274d3f3e3555e3166d02cfcaa0086aacdff2520
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
@@ -45,7 +45,7 @@ build-iPhoneSimulator/
45
45
 
46
46
  # for a library or gem, you might want to ignore these files since the code is
47
47
  # intended to run in multiple environments; otherwise, check them in:
48
- # Gemfile.lock
48
+ Gemfile.lock
49
49
  # .ruby-version
50
50
  # .ruby-gemset
51
51
 
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
- # tp2
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
@@ -12,7 +12,7 @@ end
12
12
  task :release do
13
13
  require_relative './lib/tp2/version'
14
14
  version = TP2::VERSION
15
-
15
+
16
16
  puts 'Building tp2...'
17
17
  `gem build tp2.gemspec`
18
18
 
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
- main = Fiber.current
22
- trap('SIGINT') { machine.schedule(main, nil) }
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
- machine.yield # wait for termination
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
@@ -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 '*' * 40
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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- p "request_extensions"
4
-
5
3
  class Qeweney::Request
6
4
  def serve_io(io, opts)
7
5
  # TODO: implement using UM
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 run
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) { it.run }
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
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.4'
2
+ VERSION = '0.6'
3
3
  end
@@ -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
@@ -19,6 +19,6 @@ Gem::Specification.new do |s|
19
19
  s.require_paths = ['lib']
20
20
  s.required_ruby_version = '>= 3.4'
21
21
 
22
- s.add_dependency 'uringmachine', '0.7'
22
+ s.add_dependency 'uringmachine', '0.10'
23
23
  s.add_dependency 'qeweney', '0.21'
24
24
  end
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'
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.7'
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.7'
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