tp2 0.11.2 → 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: 69d5ffc704823ef9650889a3b0ce785aa9aae379905940672ad89ecb6dca0d2b
4
- data.tar.gz: 8375c0c32f27279c104351821c2dc07ec5dd7ae5b735b64ce3d5dfa774a95c64
3
+ metadata.gz: e3f8224d39ef6a0c94d38882aa4fbde1692de6e777803060eb5adc96e56f3504
4
+ data.tar.gz: 572011895d001d3f132416ad669191fc75291d7c0b0c01fbf5dce619da6bf22c
5
5
  SHA512:
6
- metadata.gz: c6c9ac7ee73752d17d56dcdbb9d87bb74a0712f93328f7b8d57352de069595b45e9cf94d67f178828e39ee2a792e19f760550e9c2d08f7e11f3eaeb929e56e16
7
- data.tar.gz: e0665223dba5271296c2fa8ab4aa624a280a97a86af609e9fd3ff5b9a3f215b65012f7d077f1fca3a5dc8dd698fbe96a8294c87d76bd2383a552aadb064c4bc5
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,12 @@
1
+ # Version 0.12 2025-06-24
2
+
3
+ - Make logging pluggable
4
+
5
+ # Version 0.11.3 2025-06-16
6
+
7
+ - Rename HTTP1Adapter to HTTP1Connection
8
+ - Refactor HTTP error handling
9
+
1
10
  # Version 0.11.2 2025-06-15
2
11
 
3
12
  - Improve URI calculation for logging
data/TODO.md CHANGED
@@ -1,5 +1,6 @@
1
1
  - Add failing tests for request bombs (requests with lots of bytes)
2
- - Add limits for:
2
+ - Add test for `ProtocolError` handling
3
+ - Add limits with tests for:
3
4
  - total request size (pre body)
4
5
  - line size
5
6
  - request method size
@@ -12,3 +13,7 @@
12
13
 
13
14
  HEADER_RE = /^\s([^\s^\:]{1, #{MAX_HEADER_KEY_BYTES}}).../
14
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
@@ -4,7 +4,7 @@ require 'qeweney'
4
4
  require 'stringio'
5
5
 
6
6
  module TP2
7
- class HTTP1Adapter
7
+ class HTTP1Connection
8
8
  attr_reader :fd
9
9
 
10
10
  def initialize(machine, fd, opts, &app)
@@ -20,30 +20,21 @@ 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
27
27
  break if !persist
28
28
  end
29
- rescue Exception => e
30
- case e
31
- when UM::Terminate
32
- # we're done
33
- when SystemCallError
34
- @opts[:log]&.log("Encountered #{e.class}, closing connection")
35
- when ProtocolError
36
- @opts[:log]&.log("Protocol error (#{e.message}), closing connection")
37
- when StandardError
38
- @opts[:log]&.log("Unhandled exception #{e.class}: #{e.message}, closing connection")
39
- @opts[:log]&.log(e.backtrace.join("\n"))
40
- else
41
- raise
42
- end
29
+ rescue UM::Terminate, SystemCallError
30
+ # we're done, we don't care about broken socket
31
+ rescue StandardError => e
32
+ @opts[:logger]&.call(e, nil)
43
33
  ensure
44
34
  @machine.close_async(@fd)
45
35
  end
46
36
 
37
+ # Returns true if connection should persist
47
38
  def serve_request
48
39
  headers = parse_headers
49
40
  return false if !headers
@@ -51,6 +42,9 @@ module TP2
51
42
  request = Qeweney::Request.new(headers, self)
52
43
  @app.call(request)
53
44
  persist_connection?(headers)
45
+ rescue ProtocolError
46
+ # TODO: add hook for testing protocol errors
47
+ false
54
48
  end
55
49
 
56
50
  def get_body(req)
@@ -65,7 +59,7 @@ module TP2
65
59
  read(1 << 20, nil, false)
66
60
  end
67
61
 
68
- def get_body_chunk(req, buffered_only = false)
62
+ def get_body_chunk(req, _buffered_only = false)
69
63
  headers = req.headers
70
64
  content_length = headers['content-length']
71
65
  if content_length
@@ -110,7 +104,7 @@ module TP2
110
104
  else
111
105
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
112
106
  end
113
- log(request, headers) if @opts[:log]
107
+ @opts[:logger]&.call(request, headers)
114
108
  @done = true
115
109
  @response_headers = headers
116
110
  end
@@ -125,7 +119,6 @@ module TP2
125
119
  def send_headers(request, headers, empty_response: false, chunked: true)
126
120
  formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
127
121
  request.tx_incr(formatted_headers.bytesize)
128
- #
129
122
  @machine.send(@fd, formatted_headers, formatted_headers.bytesize, SEND_FLAGS)
130
123
  @response_headers = headers
131
124
  end
@@ -148,10 +141,10 @@ module TP2
148
141
 
149
142
  request.tx_incr(data.bytesize)
150
143
  @machine.send(@fd, data, data.bytesize, SEND_FLAGS)
151
- if done && !@done
152
- log(request, @response_headers) if @opts[:log]
153
- @done = true
154
- end
144
+ return if @done || !done
145
+
146
+ @opts[:logger]&.call(request, @response_headers)
147
+ @done = true
155
148
  end
156
149
 
157
150
  # Finishes the response to the current request. If no headers were sent,
@@ -160,10 +153,10 @@ module TP2
160
153
  def finish(request)
161
154
  request.tx_incr(EMPTY_CHUNK_LEN)
162
155
  @machine.send(@fd, EMPTY_CHUNK, EMPTY_CHUNK_LEN, SEND_FLAGS)
163
- if !@done
164
- log(request, @response_headers) if @opts[:log]
165
- @done = true
166
- end
156
+ return if @done
157
+
158
+ @opts[:logger]&.call(request, @response_headers)
159
+ @done = true
167
160
  end
168
161
 
169
162
  def respond_with_static_file(req, path, opts, cache_headers)
@@ -175,17 +168,17 @@ module TP2
175
168
  opts[:headers] = cache_headers
176
169
  end
177
170
 
178
- maxlen = opts[:max_len] || 65536
171
+ maxlen = opts[:max_len] || 65_536
179
172
  buf = String.new(capacity: maxlen)
180
173
  headers_sent = nil
181
- while true
174
+ loop do
182
175
  res = @machine.read(fd, buf, maxlen, 0)
183
176
  if res < maxlen && !headers_sent
184
177
  return respond(req, buf, opts[:headers])
185
178
  elsif res == 0
186
179
  return finish(req)
187
180
  end
188
-
181
+
189
182
  if !headers_sent
190
183
  send_headers(req, opts[:headers])
191
184
  headers_sent = true
@@ -204,34 +197,8 @@ module TP2
204
197
 
205
198
  private
206
199
 
207
- def log(request, response_headers)
208
- return if !@opts[:log]
209
-
210
- request_headers = request.headers
211
- uri = full_uri(request_headers)
212
-
213
- str = format(
214
- "%<client_ip>s %<method>s %<uri>s %<status>s %<tx>d",
215
- client_ip: request.forwarded_for || '?',
216
- method: request_headers[':method'].upcase,
217
- uri: uri,
218
- status: @status,
219
- tx: request_headers[':tx']
220
- )
221
- @opts[:log].log(str)
222
- end
223
-
224
- def full_uri(headers)
225
- format(
226
- "%s://%s%s",
227
- headers['x_forwarded_proto'] || 'http',
228
- headers['host'],
229
- headers[':path']
230
- )
231
- end
232
-
233
- RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
234
- 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
235
202
  MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
236
203
  MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
237
204
  MAX_CHUNK_SIZE_LEN = 16
@@ -241,11 +208,9 @@ module TP2
241
208
 
242
209
  def persist_connection?(headers)
243
210
  connection = headers['connection']&.downcase
244
- if headers[':protocol'] == 'http/1.1'
245
- return connection != 'close'
246
- else
247
- return connection && connection != 'close'
248
- end
211
+ return connection != 'close' if headers[':protocol'] == 'http/1.1'
212
+
213
+ connection && connection != 'close'
249
214
  end
250
215
 
251
216
  def parse_headers
@@ -253,7 +218,7 @@ module TP2
253
218
  headers = get_request_line(buf)
254
219
  return nil if !headers
255
220
 
256
- while true
221
+ loop do
257
222
  line = @stream.get_line(buf, MAX_HEADER_LINE_LEN)
258
223
  break if line.nil? || line.empty?
259
224
 
@@ -281,7 +246,7 @@ module TP2
281
246
  end
282
247
 
283
248
  def get_body_chunked_encoding(headers)
284
- buf = String.new(capacity: 65536)
249
+ buf = String.new(capacity: 65_536)
285
250
  while read_chunk(headers, buf)
286
251
  end
287
252
 
@@ -290,9 +255,7 @@ module TP2
290
255
 
291
256
  def read(len, buf = nil, raise_on_eof = true)
292
257
  str = @stream.get_string(buf, len)
293
- if !str && raise_on_eof
294
- raise ProtocolError, 'Missing data'
295
- end
258
+ raise ProtocolError, 'Missing data' if !str && raise_on_eof
296
259
 
297
260
  str
298
261
  end
@@ -319,7 +282,7 @@ module TP2
319
282
  request.headers[':protocol'] == 'http/1.1'
320
283
  end
321
284
 
322
- INTERNAL_HEADER_REGEXP = /^:/.freeze
285
+ INTERNAL_HEADER_REGEXP = /^:/
323
286
 
324
287
  # Formats response headers into an array. If empty_response is true(thy),
325
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/server.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tp2/http1_adapter'
3
+ require 'tp2/http1_connection'
4
4
  require 'tp2/request_extensions'
5
5
  require 'tp2/rack_adapter'
6
6
 
@@ -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, @opts, &@app)
105
+ conn = HTTP1Connection.new(@machine, fd, @opts, &@app)
106
106
  f = @machine.spin(conn) do
107
107
  it.run
108
108
  ensure
data/lib/tp2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.11.2'
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,13 +2,7 @@
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
- class HTTP1AdapterTest < Minitest::Test
5
+ class HTTP1ConnectionTest < Minitest::Test
12
6
  def make_socket_pair
13
7
  port = 10000 + rand(30000)
14
8
  server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
@@ -31,7 +25,7 @@ class HTTP1AdapterTest < Minitest::Test
31
25
  @reqs = []
32
26
  @hook = nil
33
27
  @app = ->(req) { @hook&.call(req); @reqs << req }
34
- @adapter = TP2::HTTP1Adapter.new(@machine, @s_fd, {}, &@app)
28
+ @adapter = TP2::HTTP1Connection.new(@machine, @s_fd, {}, &@app)
35
29
  end
36
30
 
37
31
  def teardown
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)
@@ -75,7 +81,7 @@ class ServerTest < Minitest::Test
75
81
  req.respond("method: #{req.method}")
76
82
  }
77
83
 
78
- write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHMET /bar HTTP/1.1\r\n\r\n"
84
+ write_http_request "GET /foo HTTP/1.1\r\nServer: foo.com\r\n\r\nSCHmet /bar HTTP/1.1\r\n\r\n"
79
85
 
80
86
  response = read_client_side
81
87
  expected = "HTTP/1.1 200\r\nContent-Length: 11\r\n\r\nmethod: getHTTP/1.1 200\r\nContent-Length: 14\r\n\r\nmethod: schmet"
@@ -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.2
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
@@ -70,7 +71,7 @@ files:
70
71
  - examples/rack.ru
71
72
  - examples/simple.rb
72
73
  - lib/tp2.rb
73
- - lib/tp2/http1_adapter.rb
74
+ - lib/tp2/http1_connection.rb
74
75
  - lib/tp2/logger.rb
75
76
  - lib/tp2/rack_adapter.rb
76
77
  - lib/tp2/request_extensions.rb