tp2 0.7.1 → 0.8.1

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: ae8cadc9d8c02c88322e7576695b2b3d671c236dfb57125062c61eef9fff67fe
4
- data.tar.gz: edc9d28d07f496aff7e366e606227854756af7604a465254161cb0cb06e4bfd9
3
+ metadata.gz: 75f66b5020339be69ee5ee0122eea1673a4f96330cedfec95cde805ef43b9221
4
+ data.tar.gz: efc072f110cacc703c6ae75f4bade2a0753c73ecffae0f3055f6346eb165c79d
5
5
  SHA512:
6
- metadata.gz: 1966581cc85753018ccc6996751e235c90e7f12d06bcc73d02f11b73f4e95fbe97c3428dbb87dd3bdeeee9336a0719eaf8307c04c8be7825980e7c110cfe50c9
7
- data.tar.gz: f564f9845b1733b8978d6a8e58a8ac8ab77bfcdf64bb12a4c7cdf69f0eff935a724ed9425eb6df299e467620ce5aa4d0fbf8cca6581019858c9650f8c632f242
6
+ metadata.gz: 5d522b6958b99135fe730b514d30ec8be9e18e01cabce1892e188ca2c255c5f963c4fc6ecf6c6b6f82b0c7ef9fc10c56dd2020a57bfbf9ea7abf27b3a3c88d2b
7
+ data.tar.gz: 64a166087f1bae5a36f648ae2068ffa731fc4ecba6e77c400a311fc8be82eea795cf25b640f94f91731d7d3d7484645837b462a0058159a3eb68f2787e54e8d5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # Version 0.8 2025-06-07
2
+
3
+ - Add silent mode
4
+ - Add basic request logging
5
+ - Update UringMachine
6
+
1
7
  # Version 0.7.1 2025-06-07
2
8
 
3
9
  - Update UringMachine
data/bin/tp2 CHANGED
@@ -4,8 +4,10 @@
4
4
  require "tp2"
5
5
  require "optparse"
6
6
 
7
-
8
- opts = {}
7
+ opts = {
8
+ banner: true,
9
+ log: true
10
+ }
9
11
 
10
12
  parser = OptionParser.new do |o|
11
13
  o.banner = "Usage: tp2 [options]"
@@ -28,6 +30,11 @@ parser = OptionParser.new do |o|
28
30
  opts[:bind] << it
29
31
  }
30
32
 
33
+ o.on("-s", "--silent", "Silent mode") {
34
+ opts[:banner] = false
35
+ opts[:log] = false
36
+ }
37
+
31
38
  o.on("-h", "--help", "Show this help message") do
32
39
  puts o
33
40
  exit
@@ -83,5 +90,4 @@ end
83
90
 
84
91
  detect_app(opts) if !opts[:app_type]
85
92
 
86
- puts TP2::BANNER
87
93
  TP2.run(opts)
data/examples/app.rb CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'tp2'
5
+ require 'json'
5
6
 
6
7
  TP2.config do |req|
7
- req.respond('Hello, world!')
8
+ body = req.headers.to_json
9
+ req.respond(body, 'Content-Type' => 'application/json')
8
10
  end
9
11
  TP2.run
@@ -7,21 +7,27 @@ module TP2
7
7
  class HTTP1Adapter
8
8
  attr_reader :fd
9
9
 
10
- def initialize(machine, fd, &app)
10
+ def initialize(machine, fd, opts, &app)
11
11
  @machine = machine
12
12
  @fd = fd
13
+ @opts = opts
13
14
  @stream = UM::Stream.new(machine, fd)
14
15
  @app = app
16
+
17
+ @done = nil
18
+ @response_headers = nil
15
19
  end
16
20
 
17
21
  def run
18
22
  while true
23
+ @done = nil
24
+ @response_headers = nil
19
25
  persist = serve_request
20
26
  break if !persist
21
27
  end
22
28
  rescue UM::Terminate
23
- # do nothing
24
- rescue => e
29
+ # terminated
30
+ rescue Exception => e
25
31
  puts '!' * 40
26
32
  p e
27
33
  puts e.backtrace.join("\n")
@@ -86,9 +92,9 @@ module TP2
86
92
  # @param request [Qeweney::Request] HTTP request
87
93
  # @param body [String] response body
88
94
  # @param headers
89
- def respond(_request, body, headers)
95
+ def respond(request, body, headers)
90
96
  formatted_headers = format_headers(headers, body, false)
91
- # request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
97
+ request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
92
98
  if body
93
99
  buf = formatted_headers + body
94
100
  @machine.send(@fd, buf, buf.bytesize, SEND_FLAGS)
@@ -96,6 +102,9 @@ module TP2
96
102
  else
97
103
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
98
104
  end
105
+ log(request, headers) if @opts[:log]
106
+ @done = true
107
+ @response_headers = headers
99
108
  end
100
109
 
101
110
  # Sends response headers. If empty_response is truthy, the response status
@@ -107,9 +116,10 @@ module TP2
107
116
  # @return [void]
108
117
  def send_headers(request, headers, empty_response: false, chunked: true)
109
118
  formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
110
- # request.tx_incr(formatted_headers.bytesize)
119
+ request.tx_incr(formatted_headers.bytesize)
111
120
  #
112
121
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
122
+ @response_headers = headers
113
123
  end
114
124
 
115
125
  # Sends a response body chunk. If no headers were sent, default headers are
@@ -119,26 +129,50 @@ module TP2
119
129
  # @param chunk [String] response body chunk
120
130
  # @param done [boolean] whether the response is completed
121
131
  # @return [void]
122
- def send_chunk(_request, chunk, done: false)
132
+ def send_chunk(request, chunk, done: false)
123
133
  data = +''
124
134
  data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
125
135
  data << "0\r\n\r\n" if done
126
136
  return if data.empty?
127
137
 
128
- # request.tx_incr(data.bytesize)
138
+ request.tx_incr(data.bytesize)
129
139
  @machine.send(@fd, data, data.bytesize, SEND_FLAGS)
140
+ if done && !@done
141
+ log(request, @response_headers) if @opts[:log]
142
+ @done = true
143
+ end
130
144
  end
131
145
 
146
+ EMPTY_CHUNK = "0\r\n\r\n"
147
+ EMPTY_CHUNK_LEN = EMPTY_CHUNK.bytesize
148
+
132
149
  # Finishes the response to the current request. If no headers were sent,
133
150
  # default headers are sent using #send_headers.
134
151
  # @return [void]
135
- def finish(_request)
136
- # request.tx_incr(5)
137
- @machine.send(@fd, "0\r\n\r\n", "0\r\n\r\n".bytesize, SEND_FLAGS)
152
+ def finish(request)
153
+ request.tx_incr(EMPTY_CHUNK_LEN)
154
+ @machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
155
+ if !@done
156
+ log(request, @response_headers) if @opts[:log]
157
+ @done = true
158
+ end
138
159
  end
139
160
 
140
161
  private
141
162
 
163
+ def log(request, response_headers)
164
+ request_headers = request.headers
165
+ str = format(
166
+ "%<stamp>s %<method>s %<path>s %<status>s %<tx>d\n",
167
+ stamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
168
+ method: request_headers[':method'].upcase,
169
+ path: request_headers[':path'],
170
+ status: @status,
171
+ tx: request_headers[':tx']
172
+ )
173
+ @machine.write_async(1, str)
174
+ end
175
+
142
176
  RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
143
177
  RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
144
178
  MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
@@ -237,9 +271,8 @@ module TP2
237
271
  # @param chunked [boolean] whether to use chunked transfer encoding
238
272
  # @return [String] formatted response headers
239
273
  def format_headers(headers, body, chunked)
240
- status = headers[':status']
241
- status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
242
- lines = format_status_line(body, status, chunked)
274
+ @status = headers[':status'] || (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
275
+ lines = format_status_line(body, @status, chunked)
243
276
  headers.each do |k, v|
244
277
  next if k =~ INTERNAL_HEADER_REGEXP
245
278
 
data/lib/tp2/server.rb CHANGED
@@ -17,7 +17,7 @@ module TP2
17
17
 
18
18
  def self.tp2_app(opts)
19
19
  if opts[:app_location]
20
- puts "Loading app at #{opts[:app_location]}"
20
+ puts "Loading app at #{opts[:app_location]}" if opts[:log]
21
21
  require opts[:app_location]
22
22
 
23
23
  opts.merge!(TP2.config)
@@ -67,7 +67,7 @@ module TP2
67
67
  @accept_fibers << @machine.spin { accept_incoming(fd) }
68
68
  end
69
69
  bind_string = bind_info.map { it.join(':') }.join(", ")
70
- puts "Listening on #{bind_string}"
70
+ @machine.puts("Listening on #{bind_string}") if @opts[:log]
71
71
 
72
72
  # map fibers
73
73
  @connection_fiber_map = {}
@@ -102,7 +102,7 @@ module TP2
102
102
 
103
103
  def accept_incoming(listen_fd)
104
104
  @machine.accept_each(listen_fd) do |fd|
105
- conn = HTTP1Adapter.new(@machine, fd, &@app)
105
+ conn = HTTP1Adapter.new(@machine, fd, @opts, &@app)
106
106
  f = @machine.spin(conn) do
107
107
  it.run
108
108
  ensure
@@ -110,6 +110,12 @@ module TP2
110
110
  end
111
111
  @connection_fiber_map[f] = true
112
112
  end
113
+ rescue UM::Terminate
114
+ # terminated
115
+ rescue Exception => e
116
+ p e
117
+ p e.backtrace
118
+ exit!
113
119
  end
114
120
 
115
121
  def close_all_server_fds
data/lib/tp2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.1'
3
3
  end
data/lib/tp2.rb CHANGED
@@ -4,6 +4,12 @@ require 'uringmachine'
4
4
  require 'tp2/version'
5
5
  require 'tp2/server'
6
6
 
7
+ class UringMachine
8
+ def puts(str)
9
+ write_async(1, "#{str}\n")
10
+ end
11
+ end
12
+
7
13
  module TP2
8
14
  BANNER = (
9
15
  "\n" +
@@ -15,7 +21,6 @@ module TP2
15
21
  " / \\ https://github.com/noteflakes/tp2\n" +
16
22
  "⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
17
23
  )
18
-
19
24
 
20
25
  class << self
21
26
  def run(opts = nil, &app)
@@ -27,8 +32,8 @@ module TP2
27
32
  machine = UM.new
28
33
  server = Server.new(machine, opts, &app)
29
34
 
30
- setup_signal_handling(machine)
31
-
35
+ setup_signal_handling(machine, Fiber.current)
36
+ machine.puts(TP2::BANNER) if opts[:banner]
32
37
  server.run
33
38
  ensure
34
39
  @in_run = false
@@ -44,18 +49,18 @@ module TP2
44
49
 
45
50
  private
46
51
 
47
- def setup_signal_handling(machine)
52
+ def setup_signal_handling(machine, fiber)
48
53
  queue = UM::Queue.new
49
54
  trap('SIGINT') { machine.push(queue, :SIGINT) }
50
- machine.spin { terminate_on_signal(machine, queue, Fiber.current) }
55
+ machine.spin { watch_for_int_signal(machine, queue, fiber) }
51
56
  end
52
57
 
53
58
  # waits for signal from queue, then terminates given fiber
54
59
  # to be done
55
- def terminate_on_signal(machine, queue, fiber)
60
+ def watch_for_int_signal(machine, queue, fiber)
56
61
  sig = machine.shift(queue)
57
- puts "\nGot signal (#{sig}), shutting down gracefully..."
62
+ machine.puts("\nGot #{sig} signal, shutting down gracefully...")
58
63
  machine.schedule(fiber, UM::Terminate.new)
59
64
  end
60
65
  end
61
- end
66
+ end
@@ -31,7 +31,7 @@ class HTTP1AdapterTest < Minitest::Test
31
31
  @reqs = []
32
32
  @hook = nil
33
33
  @app = ->(req) { @hook&.call(req); @reqs << req }
34
- @adapter = TP2::HTTP1Adapter.new(@machine, @s_fd, &@app)
34
+ @adapter = TP2::HTTP1Adapter.new(@machine, @s_fd, {}, &@app)
35
35
  end
36
36
 
37
37
  def teardown
data/test/test_server.rb CHANGED
@@ -136,7 +136,8 @@ class ServerTest < Minitest::Test
136
136
  ':path' => '/foo',
137
137
  ':protocol' => 'http/1.1',
138
138
  'server' => 'foo.com',
139
- 'content-length' => '3'
139
+ 'content-length' => '3',
140
+ ':tx' => 48,
140
141
  }, headers)
141
142
  body = @bodies.shift
142
143
  assert_equal 'abc', body
@@ -148,7 +149,8 @@ class ServerTest < Minitest::Test
148
149
  ':path' => '/bar',
149
150
  ':protocol' => 'http/1.1',
150
151
  'server' => 'bar.com',
151
- 'content-length' => '6'
152
+ 'content-length' => '6',
153
+ ':tx' => 51,
152
154
  }, headers)
153
155
  body = @bodies.shift
154
156
  assert_equal 'defghi', body
data/tp2.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |s|
20
20
  s.required_ruby_version = '>= 3.4'
21
21
  s.executables = ['tp2']
22
22
 
23
- s.add_dependency 'uringmachine', '>=0.12.1'
23
+ s.add_dependency 'uringmachine', '>=0.13'
24
24
  s.add_dependency 'qeweney', '0.21'
25
25
  s.add_dependency 'rack', '3.1.15'
26
26
  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.7.1
4
+ version: 0.8.1
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.12.1
18
+ version: '0.13'
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.12.1
25
+ version: '0.13'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: qeweney
28
28
  requirement: !ruby/object:Gem::Requirement