tp2 0.11.3 → 0.12

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: ecbdd5f3235d547f97643c111afbdb42c0b069f43d832ad6b3ee4f316104b2f7
4
- data.tar.gz: 00eccb47e2b984880081e3b38bf1839cdfd0849bd8840e54efeaa04f264d5587
3
+ metadata.gz: e3f8224d39ef6a0c94d38882aa4fbde1692de6e777803060eb5adc96e56f3504
4
+ data.tar.gz: 572011895d001d3f132416ad669191fc75291d7c0b0c01fbf5dce619da6bf22c
5
5
  SHA512:
6
- metadata.gz: f69b124309ee1582c3197c2fead077e01d7b5782985ca86caae097bbe70fb0d7ca7d6cb2d85f174a7676e1a048476092aac39292183a52fbeef10eea70b29a18
7
- data.tar.gz: 7f3f7c6c883e04f884ecefee2b4e3d58bf90f56ae21419dff78f897f16b7f2cb019af513ef96680d8969276ee54c0e67f636d3f828ddb966456c361527cb1222
6
+ metadata.gz: 8faf10f9c9da6ef061c3203cd4795d0d9fa01a2ecf7ce788abd89fbdc251a126f760c08424e41b4a5e0a7f10c2ecd7a64b56b152b441049637a9a6ac3f34db4b
7
+ data.tar.gz: 111856fa3064194dc93a98c197adeb9c512c232bc8ca4aea73b9f24542a866360f086cb2c1ba94cee068df3bb8967fdb565104b58604a7c8c3555f6184f07700
data/.rubocop.yml ADDED
@@ -0,0 +1,196 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.2
3
+ RubyInterpreters:
4
+ - ruby
5
+ Exclude:
6
+ - "**/*.gemspec"
7
+ - "test/**/*.rb"
8
+ - "examples/**/*.rb"
9
+ - "Gemfile*"
10
+ # Style/LambdaCall:
11
+ # Enabled: false
12
+ # Style/ModuleFunction:
13
+ # Enabled: false
14
+ # Style/RegexpLiteral:
15
+ # Enabled: false
16
+
17
+ # Naming/MemoizedInstanceVariableName:
18
+ # Enabled: false
19
+
20
+ # Style/Alias:
21
+ # EnforcedStyle: prefer_alias_method
22
+
23
+ # Style/SpecialGlobalVars:
24
+ # Enabled: false
25
+
26
+ # Style/ClassAndModuleChildren:
27
+ # Enabled: false
28
+
29
+ # Metrics/AbcSize:
30
+ # Enabled: false
31
+
32
+ # Style/MixinUsage:
33
+ # Enabled: false
34
+
35
+ # Style/MultilineBlockChain:
36
+ # Enabled: false
37
+
38
+ # Lint/RescueException:
39
+ # Enabled: false
40
+
41
+ # Lint/InheritException:
42
+ # Enabled: false
43
+
44
+ Style/NumericPredicate:
45
+ Enabled: false
46
+
47
+ # Style/TrivialAccessors:
48
+ # Enabled: false
49
+
50
+ # Lint/MissingSuper:
51
+ # Enabled: false
52
+
53
+ # Style/GlobalVars:
54
+ # Exclude:
55
+ # - lib/polyphony/auto_run.rb
56
+ # - lib/polyphony/extensions/core.rb
57
+ # - examples/**/*.rb
58
+
59
+ Layout/HashAlignment:
60
+ Enabled: false
61
+ EnforcedColonStyle: table
62
+ EnforcedHashRocketStyle: table
63
+
64
+ # Naming/AccessorMethodName:
65
+ # Exclude:
66
+ # - lib/polyphony/extensions/fiber.rb
67
+ # - examples/**/*.rb
68
+
69
+ # Naming/MethodName:
70
+ # Exclude:
71
+ # - test/test_signal.rb
72
+
73
+ # Lint/SuppressedException:
74
+ # Exclude:
75
+ # - examples/**/*.rb
76
+
77
+ Metrics/MethodLength:
78
+ Max: 14
79
+ # Exclude:
80
+ # - lib/polyphony/extensions/io.rb
81
+ # - lib/polyphony/extensions/fiber.rb
82
+ # - lib/polyphony/extensions/thread.rb
83
+ # - lib/polyphony/adapters/open3.rb
84
+ # - test/**/*.rb
85
+ # - examples/**/*.rb
86
+
87
+ # Metrics/ModuleLength:
88
+ # Exclude:
89
+ # - lib/polyphony/core/global_api.rb
90
+ # - examples/**/*.rb
91
+
92
+ # Metrics/ClassLength:
93
+ # Exclude:
94
+ # - lib/polyphony/extensions/io.rb
95
+ # - lib/polyphony/extensions/fiber.rb
96
+ # - lib/polyphony/extensions/object.rb
97
+ # - lib/polyphony/extensions/thread.rb
98
+ # - test/**/*.rb
99
+ # - examples/**/*.rb
100
+
101
+ # Metrics/CyclomaticComplexity:
102
+ # Exclude:
103
+ # - lib/polyphony/extensions/fiber.rb
104
+
105
+ # Metrics/PerceivedComplexity:
106
+ # Exclude:
107
+ # - lib/polyphony/extensions/fiber.rb
108
+
109
+ # Style/RegexpLiteral:
110
+ # Enabled: false
111
+
112
+ Style/RescueModifier:
113
+ Enabled: false
114
+ # Style/Documentation:
115
+ # Exclude:
116
+ # - test/**/*.rb
117
+ # - examples/**/*.rb
118
+ # - lib/polyphony/adapters/**/*.rb
119
+
120
+ # Style/FormatString:
121
+ # Exclude:
122
+ # - test/**/*.rb
123
+ # - examples/**/*.rb
124
+
125
+ # Style/FormatStringToken:
126
+ # Exclude:
127
+ # - test/**/*.rb
128
+ # - examples/**/*.rb
129
+
130
+ Naming/MethodParameterName:
131
+ Enabled: false
132
+
133
+ # Security/MarshalLoad:
134
+ # Exclude:
135
+ # - examples/**/*.rb
136
+
137
+ # Lint/ShadowedArgument:
138
+ # Exclude:
139
+ # - lib/polyphony/extensions/fiber.rb
140
+
141
+ # Style/HashEachMethods:
142
+ # Enabled: true
143
+
144
+ # Style/HashTransformKeys:
145
+ # Enabled: true
146
+
147
+ # Style/HashTransformValues:
148
+ # Enabled: true
149
+
150
+ # Layout/EmptyLinesAroundAttributeAccessor:
151
+ # Enabled: true
152
+
153
+ # Layout/SpaceAroundMethodCallOperator:
154
+ # Enabled: true
155
+
156
+ # Lint/DeprecatedOpenSSLConstant:
157
+ # Enabled: true
158
+
159
+ # Lint/MixedRegexpCaptureTypes:
160
+ # Enabled: true
161
+
162
+ # Lint/RaiseException:
163
+ # Enabled: true
164
+
165
+ # Lint/StructNewOverride:
166
+ # Enabled: true
167
+
168
+ Style/NegatedIf:
169
+ Enabled: false
170
+ # Style/NegatedWhile:
171
+ # Enabled: false
172
+
173
+ # Style/CombinableLoops:
174
+ # Enabled: false
175
+
176
+ # Style/InfiniteLoop:
177
+ # Enabled: false
178
+
179
+ # Style/RedundantReturn:
180
+ # Enabled: false
181
+
182
+ # Style/ExponentialNotation:
183
+ # Enabled: true
184
+
185
+ # Style/RedundantRegexpCharacterClass:
186
+ # Enabled: true
187
+
188
+ # Style/RedundantRegexpEscape:
189
+ # Enabled: true
190
+
191
+ # Style/SlicingWithRange:
192
+ # Enabled: true
193
+
194
+ # Style/RaiseArgs:
195
+ # Exclude:
196
+ # - lib/polyphony/extensions/fiber.rb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # Version 0.12 2025-06-24
2
+
3
+ - Make logging pluggable
4
+
1
5
  # Version 0.11.3 2025-06-16
2
6
 
3
7
  - Rename HTTP1Adapter to HTTP1Connection
data/TODO.md CHANGED
@@ -1,8 +1,6 @@
1
1
  - Add failing tests for request bombs (requests with lots of bytes)
2
-
3
- - add test for `ProtocolError` handling
4
-
5
- - Add limits for:
2
+ - Add test for `ProtocolError` handling
3
+ - Add limits with tests for:
6
4
  - total request size (pre body)
7
5
  - line size
8
6
  - request method size
@@ -15,3 +13,7 @@
15
13
 
16
14
  HEADER_RE = /^\s([^\s^\:]{1, #{MAX_HEADER_KEY_BYTES}}).../
17
15
  ```
16
+
17
+ - Benchmark parsing with UM::Stream (current approach), against parsing with strscan
18
+
19
+ - Add pluggable logging. Move default logging format outside and pass a callable.
data/bin/tp2 CHANGED
@@ -1,46 +1,47 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "tp2"
5
- require "optparse"
4
+ require 'tp2'
5
+ require 'optparse'
6
6
 
7
7
  opts = {
8
8
  banner: true,
9
- log: true
9
+ logger: true
10
10
  }
11
11
 
12
12
  parser = OptionParser.new do |o|
13
- o.banner = "Usage: tp2 [options]"
13
+ o.banner = 'Usage: tp2 [options]'
14
14
 
15
- o.on("-c", "--config CONFIG_FILE", String, "TP2 config file to use (default: app.rb)") {
15
+ o.on('-c', '--config CONFIG_FILE', String, 'TP2 config file to use (default: app.rb)') do
16
16
  opts[:app_type] = :tp2
17
17
  opts[:app_location] = it
18
- }
19
- o.on("-r", "--rack RACK_FILE", String, "Rack config file to use (default: config.ru)") {
18
+ end
19
+ o.on('-r', '--rack RACK_FILE', String, 'Rack config file to use (default: config.ru)') do
20
20
  opts[:app_type] = :rack
21
21
  opts[:app_location] = it
22
- }
23
- o.on("-d", "--dir PATH", String, "Static files at path") {
22
+ end
23
+ o.on('-d', '--dir PATH', String, 'Static files at path') do
24
24
  opts[:app_type] = :static
25
25
  opts[:app_location] = it
26
- }
26
+ end
27
27
 
28
- o.on("-b", "--bind BIND", String, "Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.") {
28
+ o.on('-b', '--bind BIND', String,
29
+ 'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
29
30
  opts[:bind] ||= []
30
31
  opts[:bind] << it
31
- }
32
+ end
32
33
 
33
- o.on("-s", "--silent", "Silent mode") {
34
+ o.on('-s', '--silent', 'Silent mode') do
34
35
  opts[:banner] = nil
35
- opts[:log] = nil
36
- }
36
+ opts[:logger] = nil
37
+ end
37
38
 
38
- o.on("-h", "--help", "Show this help message") do
39
+ o.on('-h', '--help', 'Show this help message') do
39
40
  puts o
40
41
  exit
41
42
  end
42
43
 
43
- o.on("-v", "--version", "Show version") do
44
+ o.on('-v', '--version', 'Show version') do
44
45
  puts "TP2 version #{TP2::VERSION}"
45
46
  exit
46
47
  end
@@ -59,7 +60,7 @@ opts[:location] = ARGV.shift || '.'
59
60
  def check_app_file(opts, fn, type)
60
61
  fn = File.join(opts[:location], fn)
61
62
  return false if !File.file?(fn)
62
-
63
+
63
64
  opts[:app_type] = type
64
65
  opts[:app_location] = File.expand_path(fn)
65
66
  end
@@ -20,7 +20,7 @@ module TP2
20
20
  end
21
21
 
22
22
  def run
23
- while true
23
+ loop do
24
24
  @done = nil
25
25
  @response_headers = nil
26
26
  persist = serve_request
@@ -28,9 +28,8 @@ module TP2
28
28
  end
29
29
  rescue UM::Terminate, SystemCallError
30
30
  # we're done, we don't care about broken socket
31
- rescue StandardError
32
- @opts[:log]&.log("Unhandled exception #{e.class}: #{e.message}, closing connection")
33
- @opts[:log]&.log(e.backtrace.join("\n"))
31
+ rescue StandardError => e
32
+ @opts[:logger]&.call(e, nil)
34
33
  ensure
35
34
  @machine.close_async(@fd)
36
35
  end
@@ -45,7 +44,7 @@ module TP2
45
44
  persist_connection?(headers)
46
45
  rescue ProtocolError
47
46
  # TODO: add hook for testing protocol errors
48
- return false
47
+ false
49
48
  end
50
49
 
51
50
  def get_body(req)
@@ -60,7 +59,7 @@ module TP2
60
59
  read(1 << 20, nil, false)
61
60
  end
62
61
 
63
- def get_body_chunk(req, buffered_only = false)
62
+ def get_body_chunk(req, _buffered_only = false)
64
63
  headers = req.headers
65
64
  content_length = headers['content-length']
66
65
  if content_length
@@ -105,7 +104,7 @@ module TP2
105
104
  else
106
105
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
107
106
  end
108
- log(request, headers) if @opts[:log]
107
+ @opts[:logger]&.call(request, headers)
109
108
  @done = true
110
109
  @response_headers = headers
111
110
  end
@@ -120,7 +119,6 @@ module TP2
120
119
  def send_headers(request, headers, empty_response: false, chunked: true)
121
120
  formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
122
121
  request.tx_incr(formatted_headers.bytesize)
123
- #
124
122
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
125
123
  @response_headers = headers
126
124
  end
@@ -143,10 +141,10 @@ module TP2
143
141
 
144
142
  request.tx_incr(data.bytesize)
145
143
  @machine.send(@fd, data, data.bytesize, SEND_FLAGS)
146
- if done && !@done
147
- log(request, @response_headers) if @opts[:log]
148
- @done = true
149
- end
144
+ return if @done || !done
145
+
146
+ @opts[:logger]&.call(request, @response_headers)
147
+ @done = true
150
148
  end
151
149
 
152
150
  # Finishes the response to the current request. If no headers were sent,
@@ -155,10 +153,10 @@ module TP2
155
153
  def finish(request)
156
154
  request.tx_incr(EMPTY_CHUNK_LEN)
157
155
  @machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
158
- if !@done
159
- log(request, @response_headers) if @opts[:log]
160
- @done = true
161
- end
156
+ return if @done
157
+
158
+ @opts[:logger]&.call(request, @response_headers)
159
+ @done = true
162
160
  end
163
161
 
164
162
  def respond_with_static_file(req, path, opts, cache_headers)
@@ -170,17 +168,17 @@ module TP2
170
168
  opts[:headers] = cache_headers
171
169
  end
172
170
 
173
- maxlen = opts[:max_len] || 65536
171
+ maxlen = opts[:max_len] || 65_536
174
172
  buf = String.new(capacity: maxlen)
175
173
  headers_sent = nil
176
- while true
174
+ loop do
177
175
  res = @machine.read(fd, buf, maxlen, 0)
178
176
  if res < maxlen && !headers_sent
179
177
  return respond(req, buf, opts[:headers])
180
178
  elsif res == 0
181
179
  return finish(req)
182
180
  end
183
-
181
+
184
182
  if !headers_sent
185
183
  send_headers(req, opts[:headers])
186
184
  headers_sent = true
@@ -199,34 +197,8 @@ module TP2
199
197
 
200
198
  private
201
199
 
202
- def log(request, response_headers)
203
- return if !@opts[:log]
204
-
205
- request_headers = request.headers
206
- uri = full_uri(request_headers)
207
-
208
- str = format(
209
- "%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d",
210
- client_ip: request.forwarded_for || '?',
211
- method: request_headers[':method'].upcase,
212
- uri: uri,
213
- status: @status,
214
- tx: request_headers[':tx']
215
- )
216
- @opts[:log].log(str)
217
- end
218
-
219
- def full_uri(headers)
220
- format(
221
- "%s://%s%s",
222
- headers['x_forwarded_proto'] || 'http',
223
- headers['host'],
224
- headers[':path']
225
- )
226
- end
227
-
228
- RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
229
- RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
200
+ RE_REQUEST_LINE = %r{^([a-z]+)\s+([^\s]+)\s+(http/[0-9.]{1,3})}i
201
+ RE_HEADER_LINE = /^([a-z0-9-]+):\s+(.+)/i
230
202
  MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
231
203
  MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
232
204
  MAX_CHUNK_SIZE_LEN = 16
@@ -236,11 +208,9 @@ module TP2
236
208
 
237
209
  def persist_connection?(headers)
238
210
  connection = headers['connection']&.downcase
239
- if headers[':protocol'] == 'http/1.1'
240
- return connection != 'close'
241
- else
242
- return connection && connection != 'close'
243
- end
211
+ return connection != 'close' if headers[':protocol'] == 'http/1.1'
212
+
213
+ connection && connection != 'close'
244
214
  end
245
215
 
246
216
  def parse_headers
@@ -248,7 +218,7 @@ module TP2
248
218
  headers = get_request_line(buf)
249
219
  return nil if !headers
250
220
 
251
- while true
221
+ loop do
252
222
  line = @stream.get_line(buf, MAX_HEADER_LINE_LEN)
253
223
  break if line.nil? || line.empty?
254
224
 
@@ -276,7 +246,7 @@ module TP2
276
246
  end
277
247
 
278
248
  def get_body_chunked_encoding(headers)
279
- buf = String.new(capacity: 65536)
249
+ buf = String.new(capacity: 65_536)
280
250
  while read_chunk(headers, buf)
281
251
  end
282
252
 
@@ -285,9 +255,7 @@ module TP2
285
255
 
286
256
  def read(len, buf = nil, raise_on_eof = true)
287
257
  str = @stream.get_string(buf, len)
288
- if !str && raise_on_eof
289
- raise ProtocolError, 'Missing data'
290
- end
258
+ raise ProtocolError, 'Missing data' if !str && raise_on_eof
291
259
 
292
260
  str
293
261
  end
@@ -314,7 +282,7 @@ module TP2
314
282
  request.headers[':protocol'] == 'http/1.1'
315
283
  end
316
284
 
317
- INTERNAL_HEADER_REGEXP = /^:/.freeze
285
+ INTERNAL_HEADER_REGEXP = /^:/
318
286
 
319
287
  # Formats response headers into an array. If empty_response is true(thy),
320
288
  # the response status code will default to 204, otherwise to 200.
data/lib/tp2/logger.rb CHANGED
@@ -2,15 +2,52 @@
2
2
 
3
3
  module TP2
4
4
  class Logger
5
- def initialize(machine, fd = STDOUT.fileno, **opts)
5
+ def initialize(machine, fd = $stdout.fileno, **opts)
6
6
  @machine = machine
7
7
  @fd = fd
8
8
  @opts = opts
9
9
  end
10
10
 
11
11
  def log(str)
12
- str = format("%s %s\n", Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'), str)
12
+ str = format(
13
+ "%<stamp>s %<msg>s\n",
14
+ stamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
15
+ msg: str
16
+ )
13
17
  @machine.write_async(@fd, str)
14
18
  end
19
+
20
+ def call(request, _response_headers)
21
+ if request.is_a?(Exception)
22
+ e = request
23
+ log("Error: #{e.inspect}: #{e.backtrace.inspect}")
24
+ return
25
+ end
26
+
27
+ log(format_request_log_line(request))
28
+ end
29
+
30
+ def format_request_log_line(request)
31
+ request_headers = request.headers
32
+ uri = full_uri(request_headers)
33
+
34
+ format(
35
+ '%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d',
36
+ client_ip: request.forwarded_for || '?',
37
+ method: request_headers[':method'].upcase,
38
+ uri: uri,
39
+ status: @status,
40
+ tx: request_headers[':tx']
41
+ )
42
+ end
43
+
44
+ def full_uri(headers)
45
+ format(
46
+ '%<scheme>s://%<host>s%<path>s',
47
+ scheme: headers['x_forwarded_proto'] || 'http',
48
+ host: headers['host'],
49
+ path: headers[':path']
50
+ )
51
+ end
15
52
  end
16
53
  end
data/lib/tp2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.11.3'
2
+ VERSION = '0.12'
3
3
  end
data/lib/tp2.rb CHANGED
@@ -22,7 +22,7 @@ module TP2
22
22
  " / \\ https://github.com/noteflakes/tp2\n" +
23
23
  "⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
24
24
  )
25
-
25
+
26
26
  class << self
27
27
  def run(opts = nil, &app)
28
28
  if @in_run
@@ -37,7 +37,7 @@ module TP2
37
37
  machine = UM.new
38
38
 
39
39
  machine.puts(TP2::BANNER) if opts[:banner]
40
- opts[:log] = opts[:log] && TP2::Logger.new(machine, **opts)
40
+ opts[:logger] = opts[:logger] && TP2::Logger.new(machine, **opts)
41
41
 
42
42
  server = Server.new(machine, opts, &app)
43
43
 
@@ -50,7 +50,7 @@ module TP2
50
50
 
51
51
  def config(opts = nil, &app)
52
52
  return @config if !opts && !app
53
-
53
+
54
54
  @config = opts || {}
55
55
  @config[:app] = app if app
56
56
  end
@@ -66,7 +66,7 @@ module TP2
66
66
  # waits for signal from queue, then terminates given fiber
67
67
  # to be done
68
68
  def watch_for_int_signal(machine, queue, fiber)
69
- sig = machine.shift(queue)
69
+ machine.shift(queue)
70
70
  machine.schedule(fiber, UM::Terminate.new)
71
71
  end
72
72
  end
data/test/helper.rb CHANGED
@@ -6,6 +6,12 @@ require 'tp2'
6
6
 
7
7
  require_relative './coverage' if ENV['COVERAGE']
8
8
 
9
+ class String
10
+ def crlf_lines
11
+ chomp.gsub("\n", "\r\n").chomp
12
+ end
13
+ end
14
+
9
15
  module Kernel
10
16
  def capture_exception
11
17
  yield
@@ -2,12 +2,6 @@
2
2
 
3
3
  require_relative './helper'
4
4
 
5
- class String
6
- def crlf_lines
7
- chomp.gsub("\n", "\r\n").chomp
8
- end
9
- end
10
-
11
5
  class HTTP1ConnectionTest < Minitest::Test
12
6
  def make_socket_pair
13
7
  port = 10000 + rand(30000)
data/test/test_server.rb CHANGED
@@ -25,19 +25,25 @@ class ServerTest < Minitest::Test
25
25
  def setup
26
26
  @machine = UM.new
27
27
  @port = 10000 + rand(30000)
28
- @server = TP2::Server.new(@machine, { bind: "127.0.0.1:#{@port}" }) { @app&.call(it) }
29
- @f_server = @machine.spin do
30
- @server.run
31
- rescue STOP
32
- ensure
33
- @server_done = true
34
- end
28
+ @opts = { bind: "127.0.0.1:#{@port}" }
29
+ @server = TP2::Server.new(@machine, @opts) { @app&.call(it) }
30
+ @f_server = @machine.spin { run_server }
35
31
 
32
+ # let server spin and listen to incoming connections
36
33
  @machine.sleep(0.01)
34
+
37
35
  @client_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
38
36
  @machine.connect(@client_fd, '127.0.0.1', @port)
39
37
  end
40
38
 
39
+ def run_server
40
+ @server.run
41
+ rescue STOP
42
+ # ignore
43
+ ensure
44
+ @server_done = true
45
+ end
46
+
41
47
  def teardown
42
48
  @machine.close(@client_fd) rescue nil
43
49
  @machine.schedule(@f_server, STOP.new)
@@ -240,4 +246,17 @@ class ServerTest < Minitest::Test
240
246
  body = @bodies.shift
241
247
  assert_equal 'barbaz', body
242
248
  end
249
+
250
+ def test_logging
251
+ reqs = []
252
+ @opts[:logger] = ->(req, _h) { reqs << req }
253
+ @app = ->(req) { req.respond('Hello, world!', {}) }
254
+
255
+ write_http_request "GET / HTTP/1.0\r\n\r\n"
256
+ response = read_client_side
257
+ expected = "HTTP/1.1 200\r\nContent-Length: 13\r\n\r\nHello, world!"
258
+ assert_equal(expected, response)
259
+
260
+ assert_equal 1, reqs.size
261
+ end
243
262
  end
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.14'
23
+ s.add_dependency 'uringmachine', '~> 0.15'
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.11.3
4
+ version: '0.12'
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.14'
18
+ version: '0.15'
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.14'
25
+ version: '0.15'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: qeweney
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -60,6 +60,7 @@ extra_rdoc_files:
60
60
  files:
61
61
  - ".github/workflows/test.yml"
62
62
  - ".gitignore"
63
+ - ".rubocop.yml"
63
64
  - CHANGELOG.md
64
65
  - Gemfile
65
66
  - README.md