itsi-server 0.1.1 → 0.1.18
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 +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +3937 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +141 -46
- data/ext/itsi_error/Cargo.toml +3 -0
- data/ext/itsi_error/src/lib.rs +98 -24
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +72 -14
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/default_responses/html/401.html +68 -0
- data/ext/itsi_server/src/default_responses/html/403.html +68 -0
- data/ext/itsi_server/src/default_responses/html/404.html +68 -0
- data/ext/itsi_server/src/default_responses/html/413.html +71 -0
- data/ext/itsi_server/src/default_responses/html/429.html +68 -0
- data/ext/itsi_server/src/default_responses/html/500.html +71 -0
- data/ext/itsi_server/src/default_responses/html/502.html +71 -0
- data/ext/itsi_server/src/default_responses/html/503.html +68 -0
- data/ext/itsi_server/src/default_responses/html/504.html +69 -0
- data/ext/itsi_server/src/default_responses/html/index.html +238 -0
- data/ext/itsi_server/src/default_responses/json/401.json +6 -0
- data/ext/itsi_server/src/default_responses/json/403.json +6 -0
- data/ext/itsi_server/src/default_responses/json/404.json +6 -0
- data/ext/itsi_server/src/default_responses/json/413.json +6 -0
- data/ext/itsi_server/src/default_responses/json/429.json +6 -0
- data/ext/itsi_server/src/default_responses/json/500.json +6 -0
- data/ext/itsi_server/src/default_responses/json/502.json +6 -0
- data/ext/itsi_server/src/default_responses/json/503.json +6 -0
- data/ext/itsi_server/src/default_responses/json/504.json +6 -0
- data/ext/itsi_server/src/default_responses/mod.rs +11 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +132 -40
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +201 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +432 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +270 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/http_message_types.rs +97 -0
- data/ext/itsi_server/src/server/io_stream.rs +105 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
- data/ext/itsi_server/src/server/mod.rs +12 -5
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +76 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/ext/itsi_server/src/server/thread_worker.rs +475 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/ext/itsi_server/src/services/mime_types.rs +1416 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +83 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +315 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/lib/itsi/http_request.rb +186 -0
- data/lib/itsi/http_response.rb +41 -0
- data/lib/itsi/passfile.rb +109 -0
- data/lib/itsi/server/config/dsl.rb +565 -0
- data/lib/itsi/server/config.rb +166 -0
- data/lib/itsi/server/default_app/default_app.rb +34 -0
- data/lib/itsi/server/default_app/index.html +115 -0
- data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
- data/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/route_tester.rb +107 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/lib/itsi/server/typed_handlers.rb +17 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +160 -9
- data/lib/itsi/standard_headers.rb +86 -0
- data/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/lib/shell_completions/completions.rb +26 -0
- metadata +182 -25
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/bind.rs +0 -138
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/listener.rs +0 -218
- data/ext/itsi_server/src/server/tls.rs +0 -138
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,109 @@
|
|
1
|
+
module Itsi
|
2
|
+
class Server
|
3
|
+
|
4
|
+
module Passfile
|
5
|
+
require 'io/console'
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def load(filename)
|
10
|
+
if filename.nil? || filename.strip.empty?
|
11
|
+
puts "Error: a valid filename is required."
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
|
15
|
+
creds = {}
|
16
|
+
if File.exist?(filename)
|
17
|
+
File.foreach(filename) do |line|
|
18
|
+
line.chomp!
|
19
|
+
next if line.empty?
|
20
|
+
|
21
|
+
user, pass = line.split(':', 2)
|
22
|
+
creds[user] = pass
|
23
|
+
end
|
24
|
+
end
|
25
|
+
creds
|
26
|
+
end
|
27
|
+
|
28
|
+
def save(creds, filename)
|
29
|
+
File.open(filename, 'w', 0o600) do |f|
|
30
|
+
creds.each do |u, p|
|
31
|
+
f.puts "#{u}:#{p}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def echo(filename, algorithm)
|
37
|
+
return unless (creds = load(filename))
|
38
|
+
print "Enter username: "
|
39
|
+
username = $stdin.gets.chomp
|
40
|
+
|
41
|
+
print "Enter password: "
|
42
|
+
|
43
|
+
password = $stdin.noecho(&:gets).chomp
|
44
|
+
puts
|
45
|
+
|
46
|
+
print "Confirm password: "
|
47
|
+
password_confirm = $stdin.noecho(&:gets).chomp
|
48
|
+
puts
|
49
|
+
|
50
|
+
if password != password_confirm
|
51
|
+
puts "Error: Passwords do not match!"
|
52
|
+
exit(1)
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "#{username}:#{Itsi.create_password_hash(password, algorithm)}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def add(filename, algorithm)
|
59
|
+
return unless (creds = load(filename))
|
60
|
+
print "Enter username: "
|
61
|
+
username = $stdin.gets.chomp
|
62
|
+
|
63
|
+
print "Enter password: "
|
64
|
+
|
65
|
+
password = $stdin.noecho(&:gets).chomp
|
66
|
+
puts
|
67
|
+
|
68
|
+
print "Confirm password: "
|
69
|
+
password_confirm = $stdin.noecho(&:gets).chomp
|
70
|
+
puts
|
71
|
+
|
72
|
+
if password != password_confirm
|
73
|
+
puts "Error: Passwords do not match!"
|
74
|
+
exit(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
creds[username] = Itsi.create_password_hash(password, algorithm)
|
78
|
+
|
79
|
+
save(creds, filename)
|
80
|
+
|
81
|
+
puts "User '#{username}' added."
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove(filename)
|
85
|
+
return unless (creds = load(filename))
|
86
|
+
|
87
|
+
print "Enter username to remove: "
|
88
|
+
username = $stdin.gets.chomp
|
89
|
+
|
90
|
+
if creds.key?(username)
|
91
|
+
creds.delete(username)
|
92
|
+
save(creds, filename)
|
93
|
+
puts "User '#{username}' removed."
|
94
|
+
else
|
95
|
+
puts "Warning: User '#{username}' not found."
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def list(filename)
|
100
|
+
puts "Current credentials in '#{filename}':"
|
101
|
+
return unless (creds = load(filename))
|
102
|
+
creds.each do |u, p|
|
103
|
+
puts "#{u}:#{p}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,565 @@
|
|
1
|
+
module Itsi
|
2
|
+
class Server
|
3
|
+
module Config
|
4
|
+
class DSL
|
5
|
+
attr_reader :parent, :children, :middleware, :controller_class, :routes, :methods, :protocols,
|
6
|
+
:hosts, :ports, :extensions, :content_types, :accepts, :options
|
7
|
+
|
8
|
+
def self.evaluate(config = Itsi::Server::Config.config_file_path, &blk)
|
9
|
+
new(routes: ["/"]) do
|
10
|
+
if blk
|
11
|
+
instance_exec(&blk)
|
12
|
+
else
|
13
|
+
code = IO.read(config)
|
14
|
+
instance_eval(code, config.to_s, 1)
|
15
|
+
end
|
16
|
+
end.options
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(
|
20
|
+
parent = nil,
|
21
|
+
routes: [],
|
22
|
+
methods: [],
|
23
|
+
protocols: [],
|
24
|
+
hosts: [],
|
25
|
+
ports: [],
|
26
|
+
extensions: [],
|
27
|
+
content_types: [],
|
28
|
+
accepts: [],
|
29
|
+
controller: self,
|
30
|
+
&block
|
31
|
+
)
|
32
|
+
@parent = parent
|
33
|
+
@children = []
|
34
|
+
@middleware = {}
|
35
|
+
@controller_class = nil
|
36
|
+
|
37
|
+
@controller = controller
|
38
|
+
@routes = Array(routes).flatten
|
39
|
+
@methods = methods.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
40
|
+
@protocols = protocols.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
41
|
+
@hosts = hosts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
42
|
+
@ports = ports.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
43
|
+
@extensions = extensions.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
44
|
+
@content_types = content_types.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
45
|
+
@accepts = accepts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
46
|
+
|
47
|
+
@options = {
|
48
|
+
middleware_loaders: [],
|
49
|
+
middleware_loader: lambda do
|
50
|
+
@options[:middleware_loaders].each(&:call)
|
51
|
+
@middleware[:app] ||= {}
|
52
|
+
@middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[]
|
53
|
+
flatten_routes
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
instance_exec(&block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def workers(workers)
|
61
|
+
raise "Workers must be set at the root" unless @parent.nil?
|
62
|
+
|
63
|
+
@options[:workers] = [workers.to_i, 1].max
|
64
|
+
end
|
65
|
+
|
66
|
+
def threads(threads)
|
67
|
+
raise "Threads must be set at the root" unless @parent.nil?
|
68
|
+
|
69
|
+
@options[:threads] = [threads.to_i, 1].max
|
70
|
+
end
|
71
|
+
|
72
|
+
def oob_gc_responses_threshold(threshold)
|
73
|
+
raise "OOB GC responses threshold must be set at the root" unless @parent.nil?
|
74
|
+
|
75
|
+
@options[:oob_gc_responses_threshold] = threshold.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def log_level(level)
|
79
|
+
raise "Log level must be set at the root" unless @parent.nil?
|
80
|
+
|
81
|
+
@options[:log_level] = level.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_format(format)
|
85
|
+
raise "Log format must be set at the root" unless @parent.nil?
|
86
|
+
|
87
|
+
@options[:log_format] = format.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def log_target(target)
|
91
|
+
raise "Log target must be set at the root" unless @parent.nil?
|
92
|
+
|
93
|
+
@options[:log_target] = target.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(route, app_proc = nil, nonblocking: false, &blk)
|
97
|
+
endpoint(route, [:get], app_proc, nonblocking: nonblocking, &blk)
|
98
|
+
end
|
99
|
+
|
100
|
+
def post(route, app_proc = nil, nonblocking: false, &blk)
|
101
|
+
endpoint(route, [:post], app_proc, nonblocking: nonblocking, &blk)
|
102
|
+
end
|
103
|
+
|
104
|
+
def put(route, app_proc = nil, nonblocking: false, &blk)
|
105
|
+
endpoint(route, [:put], app_proc, nonblocking: nonblocking, &blk)
|
106
|
+
end
|
107
|
+
|
108
|
+
def delete(route, app_proc = nil, nonblocking: false, &blk)
|
109
|
+
endpoint(route, [:delete], app_proc, nonblocking: nonblocking, &blk)
|
110
|
+
end
|
111
|
+
|
112
|
+
def patch(route, app_proc = nil, nonblocking: false, &blk)
|
113
|
+
endpoint(route, [:patch], app_proc, nonblocking: nonblocking, &blk)
|
114
|
+
end
|
115
|
+
|
116
|
+
def endpoint(route, methods=[], app_proc = nil, nonblocking: false, &blk)
|
117
|
+
raise "You must provide either a block or an explicit handler for the endpoint" if app_proc.nil? && blk.nil?
|
118
|
+
|
119
|
+
app_proc = @controller.method(app_proc).to_proc if app_proc.is_a?(Symbol)
|
120
|
+
|
121
|
+
app_proc ||= blk
|
122
|
+
num_required, keywords = Itsi::Server::TypedHandlers::SourceParser.extract_expr_from_source_location(app_proc)
|
123
|
+
params_schema = keywords[:params]
|
124
|
+
|
125
|
+
if params_schema && num_required > 1
|
126
|
+
raise "Cannot accept multiple required parameters in a single endpoint. A single typed or untyped params argument is supported"
|
127
|
+
end
|
128
|
+
if num_required > 2
|
129
|
+
raise "Cannot accept more than two required parameters in a single endpoint. An can either accept a single request argument, or a request and a params argument (which may be typed or untyped)"
|
130
|
+
end
|
131
|
+
if num_required == 0
|
132
|
+
raise "Cannot accept zero required parameters in a single endpoint. Endpoint must accept a request parameter"
|
133
|
+
end
|
134
|
+
|
135
|
+
accepts_params = !params_schema.nil? || num_required > 1
|
136
|
+
|
137
|
+
if accepts_params
|
138
|
+
app_proc = Itsi::Server::TypedHandlers.handler_for(app_proc, params_schema)
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
if route && methods.any?
|
143
|
+
# For endpoints, it's usually assumed trailing slash and non-trailing slash behaviour is the same
|
144
|
+
routes = route == "/" ? ["", "/"] : [route]
|
145
|
+
location(*routes, methods: methods) do
|
146
|
+
@middleware[:app] = { preloader: -> { app_proc }, nonblocking: nonblocking }
|
147
|
+
end
|
148
|
+
else
|
149
|
+
@middleware[:app] = { preloader: -> { app_proc }, nonblocking: nonblocking }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def grpc(*handlers, reflection: true, nonblocking: false, **, &blk)
|
154
|
+
if @middleware[:app] && @middleware[:app][:request_type].to_s != "grpc"
|
155
|
+
raise "App has already been set. You can use only one of `run` and `rackup_file` or `grpc` per location"
|
156
|
+
end
|
157
|
+
|
158
|
+
grpc_reflection(handlers) if reflection
|
159
|
+
|
160
|
+
handlers.each do |handler|
|
161
|
+
location(Regexp.new("#{Regexp.escape(handler.class.service_name)}/(?:#{handler.class.rpc_descs.keys.map(&:to_s).join("|")})")) do
|
162
|
+
@middleware[:app] = { preloader: -> { Itsi::Server::GrpcInterface.for(handler) }, request_type: "grpc", nonblocking: nonblocking }
|
163
|
+
instance_exec(&blk)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def grpc_reflection(handlers)
|
169
|
+
@grpc_reflected_services ||= []
|
170
|
+
@grpc_reflected_services.concat(handlers)
|
171
|
+
|
172
|
+
location(["grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
|
173
|
+
"grpc.reflection.v1.ServerReflection/ServerReflectionInfo"]) do
|
174
|
+
@middleware[:app] = { preloader: lambda {
|
175
|
+
Itsi::Server::GrpcInterface.reflection_for(handlers)
|
176
|
+
}, request_type: "grpc" }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def run(app, sendfile: true, nonblocking: false, path_info: "/")
|
181
|
+
@middleware[:app] = { preloader: -> { Itsi::Server::RackInterface.for(app) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
|
182
|
+
end
|
183
|
+
|
184
|
+
def rackup_file(rackup_file, nonblocking: false, sendfile: true, path_info: "/")
|
185
|
+
raise "Rackup file #{rackup_file} doesn't exist" unless File.exist?(rackup_file)
|
186
|
+
@middleware[:app] = { preloader: -> { Itsi::Server::RackInterface.for(rackup_file) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
|
187
|
+
end
|
188
|
+
|
189
|
+
def include(path)
|
190
|
+
code = IO.read("#{path}.rb")
|
191
|
+
instance_eval(code, "#{path}.rb", 1)
|
192
|
+
end
|
193
|
+
|
194
|
+
def bind(bind_str)
|
195
|
+
raise "Bind must be set at the root" unless @parent.nil?
|
196
|
+
|
197
|
+
@options[:binds] ||= []
|
198
|
+
@options[:binds] << bind_str.to_s
|
199
|
+
end
|
200
|
+
|
201
|
+
def after_fork(&block)
|
202
|
+
raise "After fork must be set at the root" unless @parent.nil?
|
203
|
+
|
204
|
+
@options[:hooks] ||= {}
|
205
|
+
@options[:hooks][:after_fork] = block
|
206
|
+
end
|
207
|
+
|
208
|
+
def before_fork(&block)
|
209
|
+
raise "Before fork must be set at the root" unless @parent.nil?
|
210
|
+
|
211
|
+
@options[:hooks] ||= {}
|
212
|
+
@options[:hooks][:before_fork] = block
|
213
|
+
end
|
214
|
+
|
215
|
+
def after_memory_threshold_reached(&block)
|
216
|
+
raise "Before fork must be set at the root" unless @parent.nil?
|
217
|
+
|
218
|
+
@options[:hooks] ||= {}
|
219
|
+
@options[:hooks][:after_memory_threshold_reached] = block
|
220
|
+
end
|
221
|
+
|
222
|
+
def worker_memory_limit(memory_limit)
|
223
|
+
raise "Worker memory limit must be set at the root" unless @parent.nil?
|
224
|
+
|
225
|
+
@options[:worker_memory_limit] = memory_limit
|
226
|
+
end
|
227
|
+
|
228
|
+
def multithreaded_reactor(multithreaded)
|
229
|
+
raise "Multithreaded reactor must be set at the root" unless @parent.nil?
|
230
|
+
|
231
|
+
@options[:multithreaded_reactor] = !!multithreaded
|
232
|
+
end
|
233
|
+
|
234
|
+
def pin_worker_cores(pin_worker_cores)
|
235
|
+
raise "Pin worker cores must be set at the root" unless @parent.nil?
|
236
|
+
|
237
|
+
@options[:pin_worker_cores] = !!pin_worker_cores
|
238
|
+
end
|
239
|
+
|
240
|
+
def auto_reload_config!
|
241
|
+
if ENV["BUNDLE_BIN_PATH"]
|
242
|
+
watch "Itsi.rb", [%w[bundle exec itsi restart]]
|
243
|
+
else
|
244
|
+
watch "Itsi.rb", [%w[itsi restart]]
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def watch(path, commands)
|
249
|
+
raise "Watch be set at the root" unless @parent.nil?
|
250
|
+
|
251
|
+
@options[:notify_watchers] ||= []
|
252
|
+
@options[:notify_watchers] << [path, commands]
|
253
|
+
end
|
254
|
+
|
255
|
+
def fiber_scheduler(klass_name = true)
|
256
|
+
raise "Fiber scheduler must be set at the root" unless @parent.nil?
|
257
|
+
|
258
|
+
klass_name = "Itsi::Scheduler" if klass_name == true
|
259
|
+
@options[:scheduler_class] = klass_name if klass_name
|
260
|
+
end
|
261
|
+
|
262
|
+
def scheduler_threads(threads = 1)
|
263
|
+
raise "Scheduler threads must be set at the root" unless @parent.nil?
|
264
|
+
|
265
|
+
@options[:scheduler_threads] = threads
|
266
|
+
end
|
267
|
+
|
268
|
+
def request_timeout(request_timeout)
|
269
|
+
raise "Request timeout must be set at the root" unless @parent.nil?
|
270
|
+
|
271
|
+
@options[:request_timeout] = request_timeout
|
272
|
+
end
|
273
|
+
|
274
|
+
def preload(preload)
|
275
|
+
raise "Preload must be set at the root" unless @parent.nil?
|
276
|
+
|
277
|
+
@options[:preload] = preload
|
278
|
+
end
|
279
|
+
|
280
|
+
def shutdown_timeout(shutdown_timeout)
|
281
|
+
raise "Shutdown timeout must be set at the root" unless @parent.nil?
|
282
|
+
|
283
|
+
@options[:shutdown_timeout] = shutdown_timeout.to_f
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
def stream_body(stream_body)
|
288
|
+
raise "Stream body must be set at the root" unless @parent.nil?
|
289
|
+
|
290
|
+
@options[:stream_body] = !!stream_body
|
291
|
+
end
|
292
|
+
|
293
|
+
def location(*routes, methods: [], protocols: [], hosts: [], ports: [], extensions: [], content_types: [],
|
294
|
+
accepts: [], &block)
|
295
|
+
build_child = lambda {
|
296
|
+
@children << DSL.new(
|
297
|
+
self,
|
298
|
+
routes: routes,
|
299
|
+
methods: Array(methods) | self.methods,
|
300
|
+
protocols: Array(protocols) | self.protocols,
|
301
|
+
hosts: Array(hosts) | self.hosts,
|
302
|
+
ports: Array(ports) | self.ports,
|
303
|
+
extensions: Array(extensions) | self.extensions,
|
304
|
+
content_types: Array(content_types) | self.content_types,
|
305
|
+
accepts: Array(accepts) | self.accepts,
|
306
|
+
controller: @controller,
|
307
|
+
&block
|
308
|
+
)
|
309
|
+
}
|
310
|
+
if @parent.nil?
|
311
|
+
@options[:middleware_loaders] << build_child
|
312
|
+
else
|
313
|
+
build_child[]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def log_requests(**args)
|
318
|
+
@middleware[:log_requests] = args
|
319
|
+
end
|
320
|
+
|
321
|
+
def allow_list(**args)
|
322
|
+
args[:allowed_patterns] = Array(args[:allowed_patterns]).map do |pattern|
|
323
|
+
if pattern.is_a?(Regexp)
|
324
|
+
pattern.source
|
325
|
+
else
|
326
|
+
pattern
|
327
|
+
end
|
328
|
+
end
|
329
|
+
@middleware[:allow_list] = args
|
330
|
+
end
|
331
|
+
|
332
|
+
def deny_list(**args)
|
333
|
+
args[:denied_patterns] = Array(args[:denied_patterns]).map do |pattern|
|
334
|
+
if pattern.is_a?(Regexp)
|
335
|
+
pattern.source
|
336
|
+
else
|
337
|
+
pattern
|
338
|
+
end
|
339
|
+
end
|
340
|
+
@middleware[:deny_list] = args
|
341
|
+
end
|
342
|
+
|
343
|
+
def controller(controller)
|
344
|
+
@controller = controller
|
345
|
+
end
|
346
|
+
|
347
|
+
def auth_basic(**args)
|
348
|
+
|
349
|
+
if File.exist?(".itsi-credentials") && !args[:credential_file]
|
350
|
+
args[:credential_file] = ".itsi-credentials"
|
351
|
+
end
|
352
|
+
|
353
|
+
if args[:credential_file] && File.exist?(args[:credential_file])
|
354
|
+
args[:credential_pairs] = Passfile.load(args[:credential_file])
|
355
|
+
end
|
356
|
+
|
357
|
+
@middleware[:auth_basic] = args
|
358
|
+
end
|
359
|
+
|
360
|
+
def redirect(**args)
|
361
|
+
@middleware[:redirect] = args
|
362
|
+
end
|
363
|
+
|
364
|
+
def proxy(**args)
|
365
|
+
@middleware[:proxy] = args
|
366
|
+
end
|
367
|
+
|
368
|
+
def static_response(**args)
|
369
|
+
args[:body] = args[:body].bytes
|
370
|
+
@middleware[:static_response] = args
|
371
|
+
end
|
372
|
+
|
373
|
+
def auth_jwt(**args)
|
374
|
+
@middleware[:auth_jwt] = args
|
375
|
+
end
|
376
|
+
|
377
|
+
def auth_api_key(**args)
|
378
|
+
if args[:valid_keys] && args[:valid_keys].is_a?(Array)
|
379
|
+
args[:valid_keys] = args[:valid_keys].each_with_index.map { |key, index| [index, key] }.to_h
|
380
|
+
args[:key_id_source] = nil
|
381
|
+
end
|
382
|
+
|
383
|
+
if File.exist?(".itsi-credentials") && !args[:credential_file]
|
384
|
+
args[:credential_file] = ".itsi-credentials"
|
385
|
+
end
|
386
|
+
|
387
|
+
if args[:credential_file] && File.exist?(args[:credential_file])
|
388
|
+
args[:valid_keys] = Passfile.load(args[:credential_file])
|
389
|
+
end
|
390
|
+
|
391
|
+
@middleware[:auth_api_key] = args
|
392
|
+
end
|
393
|
+
|
394
|
+
def compress(**args)
|
395
|
+
@middleware[:compression] = args
|
396
|
+
end
|
397
|
+
|
398
|
+
def request_headers(**args)
|
399
|
+
@middleware[:request_headers] = args
|
400
|
+
end
|
401
|
+
|
402
|
+
def max_body(**args)
|
403
|
+
@middleware[:max_body] = args
|
404
|
+
end
|
405
|
+
|
406
|
+
def response_headers(**args)
|
407
|
+
@middleware[:response_headers] = args
|
408
|
+
end
|
409
|
+
|
410
|
+
def rate_limit(**args)
|
411
|
+
@middleware[:rate_limit] = args
|
412
|
+
end
|
413
|
+
|
414
|
+
def cache_control(**args)
|
415
|
+
@middleware[:cache_control] = args
|
416
|
+
end
|
417
|
+
|
418
|
+
def etag(**args)
|
419
|
+
@middleware[:etag] = args
|
420
|
+
end
|
421
|
+
|
422
|
+
def intrusion_protection(**args)
|
423
|
+
args[:banned_url_patterns] = Array(args[:banned_url_patterns]).map do |pattern|
|
424
|
+
if pattern.is_a?(Regexp)
|
425
|
+
pattern.source
|
426
|
+
else
|
427
|
+
pattern
|
428
|
+
end
|
429
|
+
end
|
430
|
+
@middleware[:intrusion_protection] = args
|
431
|
+
end
|
432
|
+
|
433
|
+
def cors(**args)
|
434
|
+
@middleware[:cors] = args
|
435
|
+
end
|
436
|
+
|
437
|
+
def static_assets(**args)
|
438
|
+
root_dir = args[:root_dir] || "."
|
439
|
+
|
440
|
+
if !File.exist?(root_dir)
|
441
|
+
warn "Warning: static_assets root_dir '#{root_dir}' does not exist!"
|
442
|
+
elsif !File.directory?(root_dir)
|
443
|
+
warn "Warning: static_assets root_dir '#{root_dir}' is not a directory!"
|
444
|
+
end
|
445
|
+
|
446
|
+
args[:relative_path] = true unless args.key?(:relative_path)
|
447
|
+
|
448
|
+
args[:allowed_extensions] ||= []
|
449
|
+
|
450
|
+
if (args[:allowed_extensions].include?("html") || args[:allowed_extensions].include?(:html)) && args[:try_html_extension]
|
451
|
+
args[:allowed_extensions] << ""
|
452
|
+
end
|
453
|
+
|
454
|
+
args[:base_path] = "^(?<base_path>#{paths_from_parent}).*$"
|
455
|
+
|
456
|
+
location("*", extensions: args[:allowed_extensions]) do
|
457
|
+
@middleware[:static_assets] = args
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
def file_server(**args)
|
462
|
+
# Forward to static_assets for implementation
|
463
|
+
puts "Note: file_server is an alias for static_assets"
|
464
|
+
static_assets(**args)
|
465
|
+
end
|
466
|
+
|
467
|
+
def flatten_routes
|
468
|
+
result = []
|
469
|
+
result.concat(@children.flat_map(&:flatten_routes))
|
470
|
+
route_options = paths_from_parent
|
471
|
+
if route_options
|
472
|
+
result << deep_stringify_keys(
|
473
|
+
{
|
474
|
+
route: Regexp.new("^#{route_options}/?$"),
|
475
|
+
methods: @methods.any? ? @methods : nil,
|
476
|
+
protocols: @protocols.any? ? @protocols : nil,
|
477
|
+
hosts: @hosts.any? ? @hosts : nil,
|
478
|
+
ports: @ports.any? ? @ports : nil,
|
479
|
+
extensions: @extensions.any? ? @extensions : nil,
|
480
|
+
content_types: @content_types.any? ? @content_types : nil,
|
481
|
+
accepts: @accepts.any? ? @accepts : nil,
|
482
|
+
middleware: effective_middleware
|
483
|
+
}
|
484
|
+
)
|
485
|
+
end
|
486
|
+
result
|
487
|
+
end
|
488
|
+
|
489
|
+
def paths_from_parent
|
490
|
+
return nil unless @routes.any?
|
491
|
+
|
492
|
+
route_or_str = @routes.map do |seg|
|
493
|
+
case seg
|
494
|
+
when Regexp
|
495
|
+
seg.source
|
496
|
+
else
|
497
|
+
parts = seg.split('/')
|
498
|
+
parts.map do |part|
|
499
|
+
case part
|
500
|
+
when /^:([A-Za-z_]\w*)(?:\(([^)]*)\))?$/
|
501
|
+
param_name = Regexp.last_match(1)
|
502
|
+
custom = Regexp.last_match(2)
|
503
|
+
if custom && !custom.empty?
|
504
|
+
"(?<#{param_name}>#{custom})"
|
505
|
+
else
|
506
|
+
"(?<#{param_name}>[^/]+)"
|
507
|
+
end
|
508
|
+
when /\*/
|
509
|
+
part.gsub(/\*/, ".*")
|
510
|
+
else
|
511
|
+
Regexp.escape(part)
|
512
|
+
end
|
513
|
+
end.join("/")
|
514
|
+
end
|
515
|
+
end.join("|")
|
516
|
+
if parent && parent.paths_from_parent && parent.paths_from_parent != "(?:/)"
|
517
|
+
"#{parent.paths_from_parent}#{route_or_str != "" ? "(?:#{route_or_str})" : ""}"
|
518
|
+
else
|
519
|
+
route_or_str = "/#{route_or_str}" unless route_or_str.start_with?("/")
|
520
|
+
"(?:#{route_or_str})"
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def effective_middleware
|
525
|
+
merged = merge_ancestor_middleware
|
526
|
+
merged.map { |k, v| { type: k.to_s, parameters: v } }
|
527
|
+
end
|
528
|
+
|
529
|
+
def merge_ancestor_middleware
|
530
|
+
chain = []
|
531
|
+
node = self
|
532
|
+
while node
|
533
|
+
if node.middleware[:app]&.[](:preloader)
|
534
|
+
node.middleware[:app][:app_proc] = node.middleware[:app].delete(:preloader).call
|
535
|
+
end
|
536
|
+
chain << node
|
537
|
+
node = node.parent
|
538
|
+
end
|
539
|
+
chain.reverse!
|
540
|
+
|
541
|
+
merged = {}
|
542
|
+
|
543
|
+
chain.each do |n|
|
544
|
+
n.middleware.each do |k, v|
|
545
|
+
merged[k] = v
|
546
|
+
end
|
547
|
+
end
|
548
|
+
merged
|
549
|
+
end
|
550
|
+
|
551
|
+
def deep_stringify_keys(obj)
|
552
|
+
case obj
|
553
|
+
when Hash
|
554
|
+
obj.transform_keys!(&:to_s)
|
555
|
+
obj.transform_values! { |v| deep_stringify_keys(v) }
|
556
|
+
when Array
|
557
|
+
obj.map { |v| deep_stringify_keys(v) }
|
558
|
+
else
|
559
|
+
obj
|
560
|
+
end
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|