itsi 0.2.2 → 0.2.3

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/Cargo.lock +29 -30
  4. data/README.md +1 -0
  5. data/crates/itsi_scheduler/Cargo.toml +1 -1
  6. data/crates/itsi_server/Cargo.toml +1 -1
  7. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -3
  8. data/docs/content/_index.md +1 -1
  9. data/docs/content/contact/_index.md +1 -1
  10. data/docs/content/directory_listing.jpg +0 -0
  11. data/docs/content/error_page.jpg +0 -0
  12. data/docs/content/getting_started/local_development.md +9 -2
  13. data/docs/content/getting_started/logging.md +1 -2
  14. data/docs/content/getting_started/signals.md +0 -1
  15. data/docs/hugo.yaml +3 -0
  16. data/gems/scheduler/Cargo.lock +74 -17
  17. data/gems/scheduler/itsi-scheduler.gemspec +2 -2
  18. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  19. data/gems/server/Cargo.lock +28 -29
  20. data/gems/server/itsi-server.gemspec +2 -2
  21. data/gems/server/lib/itsi/http_request.rb +31 -34
  22. data/gems/server/lib/itsi/http_response.rb +10 -8
  23. data/gems/server/lib/itsi/passfile.rb +6 -6
  24. data/gems/server/lib/itsi/server/config/config_helpers.rb +33 -33
  25. data/gems/server/lib/itsi/server/config/dsl.rb +14 -19
  26. data/gems/server/lib/itsi/server/config/known_paths.rb +11 -7
  27. data/gems/server/lib/itsi/server/config/middleware/error_response.md +13 -0
  28. data/gems/server/lib/itsi/server/config/middleware/static_assets.md +40 -0
  29. data/gems/server/lib/itsi/server/config/option.rb +0 -1
  30. data/gems/server/lib/itsi/server/config/options/nodelay.md +2 -2
  31. data/gems/server/lib/itsi/server/config/options/reuse_address.md +1 -1
  32. data/gems/server/lib/itsi/server/config/typed_struct.rb +32 -35
  33. data/gems/server/lib/itsi/server/config.rb +107 -92
  34. data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
  35. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
  36. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +6 -7
  37. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +0 -1
  38. data/gems/server/lib/itsi/server/rack_interface.rb +0 -1
  39. data/gems/server/lib/itsi/server/route_tester.rb +25 -23
  40. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +9 -7
  41. data/gems/server/lib/itsi/server/version.rb +1 -1
  42. data/gems/server/lib/itsi/server.rb +21 -21
  43. data/gems/server/lib/itsi/standard_headers.rb +80 -80
  44. data/gems/server/test/helpers/test_helper.rb +17 -16
  45. data/gems/server/test/middleware/test_log_requests.rb +54 -2
  46. data/gems/server/test/options/test_workers.rb +12 -5
  47. data/lib/itsi/version.rb +1 -1
  48. metadata +9 -13
  49. data/examples/static_assets_example.rb +0 -83
  50. data/grpc_test/Itsi.rb +0 -11
  51. data/grpc_test/echo.proto +0 -14
  52. data/grpc_test/echo_pb.rb +0 -16
  53. data/grpc_test/echo_service_impl.rb +0 -8
  54. data/grpc_test/echo_services_pb.rb +0 -22
@@ -34,6 +34,19 @@ E.g.
34
34
  auth_api_key .. other options.., error_response: 'forbidden'
35
35
  ```
36
36
 
37
+ ## Example of built-in response
38
+ ### HTML
39
+ {{< card title="Built-in error page" image="/error_page.jpg" subtitle="Default Itsi Error Page." method="Resize" options="10x q80 webp" >}}
40
+ ### JSON
41
+ ```json
42
+ {
43
+ "error": "Too Many Requests",
44
+ "message": "Too many requests within a limited time frame.",
45
+ "code": 429,
46
+ "status": "error"
47
+ }
48
+ ```
49
+
37
50
  ## Override the error response
38
51
  You may instead wish to completely override the error response. You can provide a status code, and a message in up to three
39
52
  formats: plain-text, JSON, or HTML (at least one must be provided). Itsi will serve the appropriate type based on the `Accept` header of the incoming request, or fall back to the default if the requested type is not available.
@@ -20,6 +20,46 @@ It can auto-index directories for simple directory listings.
20
20
  static_assets root_dir: "./"
21
21
  ```
22
22
 
23
+ ### Directory Index
24
+ #### HTML
25
+
26
+ {{< card link="/" title="Static File Server" image="/directory_listing.jpg" subtitle="Static File Listing, Powered by Itsi." method="Resize" options="500x q80 webp" >}}
27
+
28
+ #### JSON
29
+ Directory indexes also support responding in JSON format. E.g.
30
+
31
+ `curl -H "Accept: application/json" http://0.0.0.0`
32
+
33
+ ```json
34
+ {
35
+ "directory": ".",
36
+ "items": [
37
+ {
38
+ "is_dir": false,
39
+ "modified": "2025-04-22 04:21:43",
40
+ "name": "Gemfile",
41
+ "path": "Gemfile",
42
+ "size": "42 B"
43
+ },
44
+ {
45
+ "is_dir": false,
46
+ "modified": "2025-04-22 04:21:48",
47
+ "name": "Gemfile.lock",
48
+ "path": "Gemfile%2Elock",
49
+ "size": "463 B"
50
+ },
51
+ {
52
+ "is_dir": false,
53
+ "modified": "2025-04-22 02:46:45",
54
+ "name": "Itsi.rb",
55
+ "path": "Itsi%2Erb",
56
+ "size": "80 B"
57
+ }
58
+ ],
59
+ "title": "Directory listing for ."
60
+ }
61
+ ```
62
+
23
63
  ## Configuration Options
24
64
 
25
65
 
@@ -4,7 +4,6 @@ module Itsi
4
4
  class Option
5
5
  include ConfigHelpers
6
6
 
7
-
8
7
  def build!
9
8
  location.options[self.class.option_name] = @params
10
9
  end
@@ -8,9 +8,9 @@ This option determines whether the Nagle's algorithm is disabled, allowing small
8
8
 
9
9
  ## Configuration
10
10
  ```ruby {filename=Itsi.rb}
11
- reuse_address true
11
+ nodelay true
12
12
  ```
13
13
 
14
14
  ```ruby {filename=Itsi.rb}
15
- reuse_address false
15
+ nodelay false
16
16
  ```
@@ -4,7 +4,7 @@ url: /options/reuse_address
4
4
  ---
5
5
 
6
6
  Configures whether the server should bind to the underlying socket using the `SO_REUSEADDR` option.
7
- This optiondetermines whether the server allows the reuse of local addresses during binding. This can be useful in scenarios where a socket needs to be quickly rebound without waiting for the operating system to release the address.
7
+ This option determines whether the server allows the reuse of local addresses during binding. This can be useful in scenarios where a socket needs to be quickly rebound without waiting for the operating system to release the address.
8
8
 
9
9
  ## Configuration
10
10
  ```ruby {filename=Itsi.rb}
@@ -1,5 +1,7 @@
1
- require 'date'
2
- require 'time'
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "time"
3
5
 
4
6
  module Itsi
5
7
  class Server
@@ -10,25 +12,19 @@ module Itsi
10
12
 
11
13
  def self.new(defaults = nil, &defaults_blk)
12
14
  defaults = TypedStruct.module_eval(&defaults_blk) if defaults_blk
13
- unless defaults.is_a?(Hash)
14
- return defaults
15
- end
15
+ return defaults unless defaults.is_a?(Hash)
16
+
16
17
  defaults.transform_values! { _1.is_a?(Validation) ? _1.default(nil) : _1 }
17
18
  Struct.new(*defaults.keys, keyword_init: true) do
18
19
  define_method(:initialize) do |*input, validate: true, **raw_input|
20
+ raise "─ Invalid input to #{self}: #{input.last}" if input.last && !input.last.is_a?(Hash)
19
21
 
20
- if input.last && !input.last.is_a?(Hash)
21
- raise "─ Invalid input to #{self}: #{input.last}"
22
- end
23
- raw_input.transform_keys!{|k| k.to_s.downcase.to_sym }
24
- raw_input.merge!(input.pop.transform_keys!{|k| k.to_s.downcase.to_sym}) if input.last.is_a?(Hash)
22
+ raw_input.transform_keys! { |k| k.to_s.downcase.to_sym }
23
+ raw_input.merge!(input.pop.transform_keys! { |k| k.to_s.downcase.to_sym }) if input.last.is_a?(Hash)
25
24
 
26
25
  excess_keys = raw_input.keys - defaults.keys
27
26
 
28
- if excess_keys.any?
29
- raise "─ Unsupported keys #{excess_keys}"
30
-
31
- end
27
+ raise "─ Unsupported keys #{excess_keys}" if excess_keys.any?
32
28
 
33
29
  initial_values = defaults.each_with_object({}) do |(k, default_config), inputs|
34
30
  value = raw_input.key?(k) ? raw_input[k] : default_config[VALUE].dup
@@ -106,9 +102,7 @@ module Itsi
106
102
 
107
103
  def &(other)
108
104
  tail = self
109
- while tail.next
110
- tail = tail.next
111
- end
105
+ tail = tail.next while tail.next
112
106
  tail.next = other
113
107
  self
114
108
  end
@@ -148,10 +142,14 @@ module Itsi
148
142
  elsif validation.eql?(::Date) then Date.parse(value.to_s)
149
143
  elsif validation.eql?(Float) then Float(value)
150
144
  elsif validation.eql?(Integer) then Integer(value)
151
- elsif validation.eql?(Proc) then
145
+ elsif validation.eql?(Proc)
152
146
  raise ArgumentError, "Invalid #{validation} value: #{value.inspect}" unless value.is_a?(Proc)
153
147
  elsif validation.eql?(String) || validation.eql?(Symbol)
154
- raise ArgumentError, "Invalid #{validation} value: #{value.inspect}" unless value.is_a?(String) || value.is_a?(Symbol)
148
+ unless value.is_a?(String) || value.is_a?(Symbol)
149
+ raise ArgumentError,
150
+ "Invalid #{validation} value: #{value.inspect}"
151
+ end
152
+
155
153
  if validation.eql?(String)
156
154
  value.to_s
157
155
  elsif validation.eql?(Symbol)
@@ -178,33 +176,32 @@ module Itsi
178
176
  {
179
177
  Bool: Validation.new(:Bool, [[true, false]]),
180
178
  Required: Validation.new(:Required, ->(value) { !value.nil? }),
181
- Or: ->(*validations){
182
- Validation.new(:Or, ->(v){
183
-
179
+ Or: lambda { |*validations|
180
+ Validation.new(:Or, lambda { |v|
184
181
  return true if v.nil?
182
+
185
183
  errs = []
186
184
  validations.each do |validation|
187
- begin
188
- v = validation.validate!(v)
189
- return v
190
- rescue StandardError => e
191
- errs << e.message
192
- end
185
+ v = validation.validate!(v)
186
+ return v
187
+ rescue StandardError => e
188
+ errs << e.message
193
189
  end
194
190
  raise StandardError.new("─ Validation failed (None match:) \n └#{errs.join("\n └")}")
195
191
  })
196
192
  },
197
- Range: ->(input_range) {
193
+ Range: lambda { |input_range|
198
194
  Validation.new(:Range, [input_range])
199
195
  },
200
196
  Length: lambda { |input_length|
201
197
  Validation.new(:Length, ->(value) { input_length === value.length })
202
198
  },
203
- Hash: ->(key_type, value_type) {
204
- Validation.new(:Hash, ->(v){
205
- return true if v.nil?
206
- raise StandardError.new("Expected hash got #{v.class}") unless v.is_a?(Hash)
207
- v.map do |k, v|
199
+ Hash: lambda { |key_type, value_type|
200
+ Validation.new(:Hash, lambda { |hash|
201
+ return true if hash.nil?
202
+ raise StandardError.new("Expected hash got #{hash.class}") unless hash.is_a?(Hash)
203
+
204
+ hash.map do |k, v|
208
205
  [
209
206
  key_type.validate!(k),
210
207
  value_type.validate!(v)
@@ -213,7 +210,7 @@ module Itsi
213
210
  })
214
211
  },
215
212
  Type: ->(input_type) { Validation.new(:Type, input_type) },
216
- Enum: ->(allowed_values) { Validation.new(:Enum, [allowed_values.map{|v| v.kind_of?(Symbol) ? v.to_s : v}]) },
213
+ Enum: ->(allowed_values) { Validation.new(:Enum, [allowed_values.map { |v| v.is_a?(Symbol) ? v.to_s : v }]) },
217
214
  Array: lambda { |*value_validations|
218
215
  Validation.new(:Array, [::Array, lambda { |value|
219
216
  return true unless value
@@ -41,15 +41,18 @@ module Itsi
41
41
  elsif args[:static]
42
42
  DSL.evaluate do
43
43
  location "/" do
44
- rate_limit key: 'address', store_config: 'in_memory', requests: 2, seconds: 5
45
- etag type: 'strong', algorithm: 'md5', min_body_size: 1024 * 1024
46
- compress min_size: 1024 * 1024, level: 'fastest', algorithms: %w[zstd gzip br deflate], mime_types: %w[all], compress_streams: true
47
- log_requests before: { level: "INFO", format: "[{request_id}] {method} {path_and_query} - {addr} " }, after: { level: "INFO", format: "[{request_id}] └─ {status} in {response_time}" }
44
+ rate_limit key: "address", store_config: "in_memory", requests: 2, seconds: 5
45
+ etag type: "strong", algorithm: "md5", min_body_size: 1024 * 1024
46
+ compress min_size: 1024 * 1024, level: "fastest", algorithms: %w[zstd gzip br deflate],
47
+ mime_types: %w[all], compress_streams: true
48
+ log_requests before: { level: "INFO", format: "[{request_id}] {method} {path_and_query} - {addr} " },
49
+ after: { level: "INFO",
50
+ format: "[{request_id}] └─ {status} in {response_time}" }
48
51
  static_assets \
49
52
  relative_path: true,
50
53
  allowed_extensions: [],
51
- root_dir: '.',
52
- not_found_behavior: {error: 'not_found'},
54
+ root_dir: ".",
55
+ not_found_behavior: { error: "not_found" },
53
56
  auto_index: true,
54
57
  try_html_extension: true,
55
58
  max_file_size_in_memory: 1024 * 1024, # 1MB
@@ -57,7 +60,7 @@ module Itsi
57
60
  file_check_interval: 1,
58
61
  serve_hidden_files: false,
59
62
  headers: {
60
- 'X-Content-Type-Options' => 'nosniff'
63
+ "X-Content-Type-Options" => "nosniff"
61
64
  }
62
65
  end
63
66
  end
@@ -69,7 +72,7 @@ module Itsi
69
72
  rackup_file args.fetch(:rackup_file, "./config.ru")
70
73
  end
71
74
  else
72
- DSL.evaluate{}
75
+ DSL.evaluate {}
73
76
  end
74
77
 
75
78
  itsifile_config.transform_keys!(&:to_sym)
@@ -85,7 +88,7 @@ module Itsi
85
88
  Itsi.log_debug("Preloading middleware and default rack app")
86
89
  preloaded_middleware = middleware_loader.call
87
90
  middleware_loader = -> { preloaded_middleware }
88
- rescue StandardError => e
91
+ rescue Exception => e # rubocop:disable Lint/RescueException
89
92
  errors << [e, e.backtrace[0]]
90
93
  end
91
94
  # If we're just preloading a specific gem group, we'll do that here too
@@ -107,7 +110,13 @@ module Itsi
107
110
  worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) },
108
111
  silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) },
109
112
  shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) },
110
- hooks: args[:hooks] && itsifile_config[:hooks] ? args[:hooks].merge(itsifile_config[:hooks]) : itsifile_config.fetch(:hooks, args[:hooks]),
113
+ hooks: if args[:hooks] && itsifile_config[:hooks]
114
+ args[:hooks].merge(itsifile_config[:hooks])
115
+ else
116
+ itsifile_config.fetch(
117
+ :hooks, args[:hooks]
118
+ )
119
+ end,
111
120
  preload: !!preload,
112
121
  request_timeout: itsifile_config.fetch(:request_timeout, nil),
113
122
  header_read_timeout: args.fetch(:header_read_timeout) { itsifile_config.fetch(:header_read_timeout, nil) },
@@ -132,53 +141,23 @@ module Itsi
132
141
  listeners: args.fetch(:listeners) { nil },
133
142
  reuse_address: itsifile_config.fetch(:reuse_address, true),
134
143
  reuse_port: itsifile_config.fetch(:reuse_port, true),
135
- listen_backlog: itsifile_config.fetch(:listen_backlog, 1024 ),
144
+ listen_backlog: itsifile_config.fetch(:listen_backlog, 1024),
136
145
  nodelay: itsifile_config.fetch(:nodelay, true),
137
146
  recv_buffer_size: itsifile_config.fetch(:recv_buffer_size, 262_144)
138
147
  }.transform_keys(&:to_s)
139
148
 
140
- error_lines = errors.flat_map do |(error, message)|
141
- location = message[/(.*?)\:in/,1]
142
- file, lineno = location.split(":")
143
- lineno = lineno.to_i
144
- err_message = error.kind_of?(NoMethodError) ? error.detailed_message : error.message
145
- file_lines = IO.readlines(file)
146
- info_lines = if error.kind_of?(SyntaxError)
147
- []
148
- else
149
- ([lineno-2, 0].max...[file_lines.length, lineno.succ.succ].min).map do |currline|
150
- if currline == lineno-1
151
- line = file_lines[currline][0...-1]
152
- padding = line[/^\s+/]&.length || 0
153
-
154
- [
155
- " \e[31m#{currline.succ.to_s.rjust(3)} | #{line}\e[0m",
156
- " | #{' ' * padding}\e[33m^^^\e[0m "
157
- ]
158
- else
159
- " #{currline.succ.to_s.rjust(3)} | #{file_lines[currline][0...-1]}"
160
- end
161
- end.flatten
162
- end
163
- [
164
- err_message,
165
- " --> #{File.expand_path(file)}:#{lineno}",
166
- *info_lines
167
- ]
168
- end
169
-
170
- return srv_config, error_lines
171
- rescue
149
+ [srv_config, errors_to_error_lines(errors)]
150
+ rescue StandardError
172
151
  Itsi.log_error e.message
173
152
  puts e.backtrace
174
153
  end
175
154
 
176
155
  def self.test!(cli_params)
177
- _, errors = build_config(cli_params, Itsi::Server::Config.config_file_path(cli_params[:config_file]))
156
+ config, errors = build_config(cli_params, Itsi::Server::Config.config_file_path(cli_params[:config_file]))
178
157
  unless errors.any?
179
158
  begin
180
- _["middleware_loader"][]
181
- rescue Exception => e
159
+ config["middleware_loader"][]
160
+ rescue Exception => e # rubocop:disable Lint/RescueException
182
161
  errors = [e]
183
162
  end
184
163
  end
@@ -191,6 +170,42 @@ module Itsi
191
170
  end
192
171
  end
193
172
 
173
+ def self.errors_to_error_lines(errors)
174
+ return unless errors
175
+
176
+ errors.flat_map do |(error, message)|
177
+ location = message[/(.*?):in/, 1]
178
+ file, lineno = location.split(":")
179
+ lineno = lineno.to_i
180
+ err_message = error.is_a?(NoMethodError) ? error.detailed_message : error.message
181
+ file_lines = IO.readlines(file)
182
+ info_lines = \
183
+ if error.is_a?(SyntaxError)
184
+ []
185
+ else
186
+
187
+ ([lineno - 2, 0].max...[file_lines.length, lineno.succ.succ].min).map do |currline|
188
+ if currline == lineno - 1
189
+ line = file_lines[currline][0...-1]
190
+ padding = line[/^\s+/]&.length || 0
191
+
192
+ [
193
+ " \e[31m#{currline.succ.to_s.rjust(3)} | #{line}\e[0m",
194
+ " | #{" " * padding}\e[33m^^^\e[0m "
195
+ ]
196
+ else
197
+ " #{currline.succ.to_s.rjust(3)} | #{file_lines[currline][0...-1]}"
198
+ end
199
+ end.flatten
200
+ end
201
+ [
202
+ err_message,
203
+ " --> #{File.expand_path(file)}:#{lineno}",
204
+ *info_lines
205
+ ]
206
+ end
207
+ end
208
+
194
209
  # Reloads the entire process
195
210
  # using exec, passing in any active file descriptors
196
211
  # and previous invocation arguments
@@ -233,56 +248,56 @@ module Itsi
233
248
 
234
249
  default_config = IO.read("#{__dir__}/default_config/Itsi.rb")
235
250
 
236
- if File.exist?("./config.ru")
237
- default_config << <<~RUBY
238
- # You can mount several Ruby apps as either
239
- # 1. rackup files
240
- # 2. inline rack apps
241
- # 3. inline Ruby endpoints
242
- #
243
- # 1. rackup_file
244
- rackup_file "./config.ru"
245
- #
246
- # 2. inline rack app
247
- # require 'rack'
248
- # run(Rack::Builder.app do
249
- # use Rack::CommonLogger
250
- # run ->(env) { [200, { 'content-type' => 'text/plain' }, ['OK']] }
251
- # end)
252
- #
253
- # 3. Endpoints
254
- # endpoint "/" do |req|
255
- # req.ok "Hello from Itsi"
256
- # end
257
- RUBY
258
- else
259
- default_config << <<~RUBY
260
- # You can mount several Ruby apps as either
261
- # 1. rackup files
262
- # 2. inline rack apps
263
- # 3. inline Ruby endpoints
264
- #
265
- # 1. rackup_file
266
- # Use `rackup_file` to specify the Rack app file name.
267
- #
268
- # 2. inline rack app
269
- # require 'rack'
270
- # run(Rack::Builder.app do
271
- # use Rack::CommonLogger
272
- # run ->(env) { [200, { 'content-type' => 'text/plain' }, ['OK']] }
273
- # end)
274
- #
275
- # 3. Endpoint
276
- endpoint "/" do |req|
277
- req.ok "Hello from Itsi"
278
- end
279
- RUBY
280
- end
251
+ default_config << \
252
+ if File.exist?("./config.ru")
253
+ <<~RUBY
254
+ # You can mount several Ruby apps as either
255
+ # 1. rackup files
256
+ # 2. inline rack apps
257
+ # 3. inline Ruby endpoints
258
+ #
259
+ # 1. rackup_file
260
+ rackup_file "./config.ru"
261
+ #
262
+ # 2. inline rack app
263
+ # require 'rack'
264
+ # run(Rack::Builder.app do
265
+ # use Rack::CommonLogger
266
+ # run ->(env) { [200, { 'content-type' => 'text/plain' }, ['OK']] }
267
+ # end)
268
+ #
269
+ # 3. Endpoints
270
+ # endpoint "/" do |req|
271
+ # req.ok "Hello from Itsi"
272
+ # end
273
+ RUBY
274
+ else
275
+ <<~RUBY
276
+ # You can mount several Ruby apps as either
277
+ # 1. rackup files
278
+ # 2. inline rack apps
279
+ # 3. inline Ruby endpoints
280
+ #
281
+ # 1. rackup_file
282
+ # Use `rackup_file` to specify the Rack app file name.
283
+ #
284
+ # 2. inline rack app
285
+ # require 'rack'
286
+ # run(Rack::Builder.app do
287
+ # use Rack::CommonLogger
288
+ # run ->(env) { [200, { 'content-type' => 'text/plain' }, ['OK']] }
289
+ # end)
290
+ #
291
+ # 3. Endpoint
292
+ endpoint "/" do |req|
293
+ req.ok "Hello from Itsi"
294
+ end
295
+ RUBY
296
+ end
281
297
 
282
298
  File.open(ITSI_DEFAULT_CONFIG_FILE, "w") do |file|
283
299
  file.write(default_config)
284
300
  end
285
-
286
301
  end
287
302
  end
288
303
  end
@@ -11,7 +11,7 @@ DEFAULT_APP = lambda {
11
11
  [
12
12
  { "Content-Type" => "application/json" },
13
13
  [{ "message" => "You're running on Itsi!", "rack_env" => env,
14
- "version" => Itsi::Server::VERSION }.to_json]
14
+ "version" => Itsi::Server::VERSION }.to_json]
15
15
  ]
16
16
  else
17
17
  [
@@ -7,11 +7,11 @@ module Itsi
7
7
  attr_accessor :rpc_desc
8
8
 
9
9
  def input_stream?
10
- @input_stream ||= @rpc_desc&.input&.is_a?(GRPC::RpcDesc::Stream) || false
10
+ @input_stream ||= @rpc_desc&.input.is_a?(GRPC::RpcDesc::Stream) || false
11
11
  end
12
12
 
13
13
  def output_stream?
14
- @output_stream ||= @rpc_desc&.output&.is_a?(GRPC::RpcDesc::Stream) || false
14
+ @output_stream ||= @rpc_desc&.output.is_a?(GRPC::RpcDesc::Stream) || false
15
15
  end
16
16
 
17
17
  def input_type
@@ -64,7 +64,7 @@ module Itsi
64
64
  return nil if first_char.nil?
65
65
 
66
66
  # Step 2: Process objects until we hit the end of the JSON stream or array.
67
- loop do
67
+ loop do # rubocop:disable Lint/UnreachableLoop,Metrics/BlockLength
68
68
  # Skip any whitespace or commas preceding an object.
69
69
  char = nil
70
70
  loop do
@@ -89,7 +89,6 @@ module Itsi
89
89
 
90
90
  buffer << ch
91
91
 
92
-
93
92
  if in_string
94
93
  if escape
95
94
  escape = false
@@ -120,7 +119,7 @@ module Itsi
120
119
  def remote_read
121
120
  if content_type == "application/json"
122
121
  if input_stream?
123
- if next_item = parse_from_json_stream(reader)
122
+ if (next_item = parse_from_json_stream(reader))
124
123
  input_type.decode_json(next_item)
125
124
  end
126
125
  else
@@ -29,7 +29,6 @@ module Itsi
29
29
  end
30
30
 
31
31
  def handle_request(active_call)
32
- puts "Handling active call"
33
32
  unless (active_call.rpc_desc = @rpc_descs[active_call.method_name])
34
33
  active_call.stream.write("\n")
35
34
  active_call.send_status(13, "Method not found")
@@ -89,18 +88,18 @@ module Itsi
89
88
  result = service.send(active_call.method_name, message, active_call) do |response|
90
89
  active_call.remote_send(response)
91
90
  end
92
- if result.kind_of?(Enumerator)
93
- result.each { |response| active_call.remote_send(response) }
94
- end
91
+ return unless result.is_a?(Enumerator)
92
+
93
+ result.each { |response| active_call.remote_send(response) }
95
94
  end
96
95
 
97
96
  def handle_bidi_streaming(active_call)
98
97
  result = service.send(active_call.method_name, active_call.each_remote_read, active_call) do |response|
99
98
  active_call.remote_send(response)
100
99
  end
101
- if result.kind_of?(Enumerator)
102
- result.each { |response| active_call.remote_send(response) }
103
- end
100
+ return unless result.is_a?(Enumerator)
101
+
102
+ result.each { |response| active_call.remote_send(response) }
104
103
  end
105
104
  end
106
105
  end
@@ -1,6 +1,5 @@
1
1
  return unless defined?(::Rackup::Handler) || defined?(Rack::Handler)
2
2
 
3
-
4
3
  module Rack
5
4
  module Handler
6
5
  module Itsi
@@ -1,7 +1,6 @@
1
1
  module Itsi
2
2
  class Server
3
3
  module RackInterface
4
-
5
4
  # Builds a handler proc that is compatible with Rack applications.
6
5
  def self.for(app)
7
6
  require "rack"