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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/Cargo.lock +29 -30
- data/README.md +1 -0
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_server/Cargo.toml +1 -1
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -3
- data/docs/content/_index.md +1 -1
- data/docs/content/contact/_index.md +1 -1
- data/docs/content/directory_listing.jpg +0 -0
- data/docs/content/error_page.jpg +0 -0
- data/docs/content/getting_started/local_development.md +9 -2
- data/docs/content/getting_started/logging.md +1 -2
- data/docs/content/getting_started/signals.md +0 -1
- data/docs/hugo.yaml +3 -0
- data/gems/scheduler/Cargo.lock +74 -17
- data/gems/scheduler/itsi-scheduler.gemspec +2 -2
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/Cargo.lock +28 -29
- data/gems/server/itsi-server.gemspec +2 -2
- data/gems/server/lib/itsi/http_request.rb +31 -34
- data/gems/server/lib/itsi/http_response.rb +10 -8
- data/gems/server/lib/itsi/passfile.rb +6 -6
- data/gems/server/lib/itsi/server/config/config_helpers.rb +33 -33
- data/gems/server/lib/itsi/server/config/dsl.rb +14 -19
- data/gems/server/lib/itsi/server/config/known_paths.rb +11 -7
- data/gems/server/lib/itsi/server/config/middleware/error_response.md +13 -0
- data/gems/server/lib/itsi/server/config/middleware/static_assets.md +40 -0
- data/gems/server/lib/itsi/server/config/option.rb +0 -1
- data/gems/server/lib/itsi/server/config/options/nodelay.md +2 -2
- data/gems/server/lib/itsi/server/config/options/reuse_address.md +1 -1
- data/gems/server/lib/itsi/server/config/typed_struct.rb +32 -35
- data/gems/server/lib/itsi/server/config.rb +107 -92
- data/gems/server/lib/itsi/server/default_app/default_app.rb +1 -1
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +4 -5
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +6 -7
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +0 -1
- data/gems/server/lib/itsi/server/rack_interface.rb +0 -1
- data/gems/server/lib/itsi/server/route_tester.rb +25 -23
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +9 -7
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +21 -21
- data/gems/server/lib/itsi/standard_headers.rb +80 -80
- data/gems/server/test/helpers/test_helper.rb +17 -16
- data/gems/server/test/middleware/test_log_requests.rb +54 -2
- data/gems/server/test/options/test_workers.rb +12 -5
- data/lib/itsi/version.rb +1 -1
- metadata +9 -13
- data/examples/static_assets_example.rb +0 -83
- data/grpc_test/Itsi.rb +0 -11
- data/grpc_test/echo.proto +0 -14
- data/grpc_test/echo_pb.rb +0 -16
- data/grpc_test/echo_service_impl.rb +0 -8
- 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,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
|
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
|
-
|
2
|
-
|
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
|
-
|
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
|
-
|
21
|
-
|
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)
|
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
|
-
|
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:
|
182
|
-
Validation.new(:Or,
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
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:
|
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:
|
204
|
-
Validation.new(:Hash,
|
205
|
-
return true if
|
206
|
-
raise StandardError.new("Expected hash got #{
|
207
|
-
|
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.
|
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:
|
45
|
-
etag type:
|
46
|
-
compress min_size: 1024 * 1024, level:
|
47
|
-
|
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:
|
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
|
-
|
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
|
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]
|
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
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
@@ -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
|
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
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
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
|