itsi 0.1.14 → 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/Cargo.lock +124 -109
- data/Cargo.toml +6 -0
- data/crates/itsi_error/Cargo.toml +1 -0
- data/crates/itsi_error/src/lib.rs +100 -10
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
- data/crates/itsi_server/Cargo.toml +8 -10
- data/crates/itsi_server/src/default_responses/html/401.html +68 -0
- data/crates/itsi_server/src/default_responses/html/403.html +68 -0
- data/crates/itsi_server/src/default_responses/html/404.html +68 -0
- data/crates/itsi_server/src/default_responses/html/413.html +71 -0
- data/crates/itsi_server/src/default_responses/html/429.html +68 -0
- data/crates/itsi_server/src/default_responses/html/500.html +71 -0
- data/crates/itsi_server/src/default_responses/html/502.html +71 -0
- data/crates/itsi_server/src/default_responses/html/503.html +68 -0
- data/crates/itsi_server/src/default_responses/html/504.html +69 -0
- data/crates/itsi_server/src/default_responses/html/index.html +238 -0
- data/crates/itsi_server/src/default_responses/json/401.json +6 -0
- data/crates/itsi_server/src/default_responses/json/403.json +6 -0
- data/crates/itsi_server/src/default_responses/json/404.json +6 -0
- data/crates/itsi_server/src/default_responses/json/413.json +6 -0
- data/crates/itsi_server/src/default_responses/json/429.json +6 -0
- data/crates/itsi_server/src/default_responses/json/500.json +6 -0
- data/crates/itsi_server/src/default_responses/json/502.json +6 -0
- data/crates/itsi_server/src/default_responses/json/503.json +6 -0
- data/crates/itsi_server/src/default_responses/json/504.json +6 -0
- data/crates/itsi_server/src/default_responses/mod.rs +11 -0
- data/crates/itsi_server/src/lib.rs +58 -26
- data/crates/itsi_server/src/prelude.rs +2 -0
- data/crates/itsi_server/src/ruby_types/README.md +21 -0
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
- data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
- data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
- data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
- data/crates/itsi_server/src/server/binds/mod.rs +4 -0
- data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
- data/crates/itsi_server/src/server/http_message_types.rs +97 -0
- data/crates/itsi_server/src/server/io_stream.rs +2 -1
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
- data/crates/itsi_server/src/server/mod.rs +3 -9
- data/crates/itsi_server/src/server/process_worker.rs +21 -3
- data/crates/itsi_server/src/server/request_job.rs +2 -2
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
- data/crates/itsi_server/src/server/signal.rs +24 -41
- data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/crates/itsi_server/src/server/thread_worker.rs +59 -28
- data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/crates/itsi_server/src/services/mime_types.rs +1416 -0
- data/crates/itsi_server/src/services/mod.rs +6 -0
- data/crates/itsi_server/src/services/password_hasher.rs +83 -0
- data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
- data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
- data/crates/itsi_tracing/src/lib.rs +145 -55
- data/{Itsi.rb → foo/Itsi.rb} +6 -9
- data/gems/scheduler/Cargo.lock +7 -0
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/test/helpers/test_helper.rb +0 -1
- data/gems/scheduler/test/test_address_resolve.rb +0 -1
- data/gems/scheduler/test/test_network_io.rb +1 -1
- data/gems/scheduler/test/test_process_wait.rb +0 -1
- data/gems/server/Cargo.lock +124 -109
- data/gems/server/exe/itsi +65 -19
- data/gems/server/itsi-server.gemspec +4 -3
- data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/gems/server/lib/itsi/http_request.rb +116 -17
- data/gems/server/lib/itsi/http_response.rb +2 -0
- data/gems/server/lib/itsi/passfile.rb +109 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +160 -101
- data/gems/server/lib/itsi/server/config.rb +58 -23
- data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
- data/gems/server/lib/itsi/server/default_app/index.html +113 -89
- data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/gems/server/lib/itsi/server/route_tester.rb +107 -0
- data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -12
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/gems/server/lib/shell_completions/completions.rb +26 -0
- data/gems/server/test/helpers/test_helper.rb +2 -1
- data/lib/itsi/version.rb +1 -1
- data/sandbox/README.md +5 -0
- data/sandbox/itsi_file/Gemfile +4 -2
- data/sandbox/itsi_file/Gemfile.lock +48 -6
- data/sandbox/itsi_file/Itsi.rb +326 -129
- data/sandbox/itsi_file/call.json +1 -0
- data/sandbox/itsi_file/echo_client/Gemfile +10 -0
- data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
- data/sandbox/itsi_file/echo_client/README.md +95 -0
- data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
- data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
- data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
- data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
- data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
- data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
- data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
- data/sandbox/itsi_sandbox_async/config.ru +0 -1
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
- data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
- data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
- data/sandbox/itsi_sinatra/app.rb +0 -1
- data/sandbox/static_files/.env +1 -0
- data/sandbox/static_files/404.html +25 -0
- data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
- data/sandbox/static_files/about.html +68 -0
- data/sandbox/static_files/tiny.html +1 -0
- data/sandbox/static_files/writebook.zip +0 -0
- data/tasks.txt +28 -33
- metadata +87 -26
- data/crates/itsi_error/src/from.rs +0 -68
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
- data/crates/itsi_server/src/server/itsi_service.rs +0 -172
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
- data/crates/itsi_server/src/server/types.rs +0 -43
- data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
- data/sandbox/itsi_file/public/assets/index.html +0 -1
- /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
- /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
- /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -5,10 +5,10 @@ module Itsi
|
|
5
5
|
attr_reader :parent, :children, :middleware, :controller_class, :routes, :methods, :protocols,
|
6
6
|
:hosts, :ports, :extensions, :content_types, :accepts, :options
|
7
7
|
|
8
|
-
def self.evaluate(config = Itsi::Server::Config.config_file_path)
|
9
|
-
new do
|
10
|
-
if
|
11
|
-
instance_exec(&
|
8
|
+
def self.evaluate(config = Itsi::Server::Config.config_file_path, &blk)
|
9
|
+
new(routes: ["/"]) do
|
10
|
+
if blk
|
11
|
+
instance_exec(&blk)
|
12
12
|
else
|
13
13
|
code = IO.read(config)
|
14
14
|
instance_eval(code, config.to_s, 1)
|
@@ -35,7 +35,6 @@ module Itsi
|
|
35
35
|
@controller_class = nil
|
36
36
|
|
37
37
|
@controller = controller
|
38
|
-
# We'll store our array of route specs (strings or a single Regexp).
|
39
38
|
@routes = Array(routes).flatten
|
40
39
|
@methods = methods.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
41
40
|
@protocols = protocols.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
@@ -49,6 +48,8 @@ module Itsi
|
|
49
48
|
middleware_loaders: [],
|
50
49
|
middleware_loader: lambda do
|
51
50
|
@options[:middleware_loaders].each(&:call)
|
51
|
+
@middleware[:app] ||= {}
|
52
|
+
@middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[]
|
52
53
|
flatten_routes
|
53
54
|
end
|
54
55
|
}
|
@@ -56,7 +57,6 @@ module Itsi
|
|
56
57
|
instance_exec(&block)
|
57
58
|
end
|
58
59
|
|
59
|
-
|
60
60
|
def workers(workers)
|
61
61
|
raise "Workers must be set at the root" unless @parent.nil?
|
62
62
|
|
@@ -84,84 +84,106 @@ module Itsi
|
|
84
84
|
def log_format(format)
|
85
85
|
raise "Log format must be set at the root" unless @parent.nil?
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
93
94
|
end
|
94
95
|
|
95
|
-
def get(route, app_proc = nil, &blk)
|
96
|
-
endpoint(route, :get, app_proc, &blk)
|
96
|
+
def get(route, app_proc = nil, nonblocking: false, &blk)
|
97
|
+
endpoint(route, [:get], app_proc, nonblocking: nonblocking, &blk)
|
97
98
|
end
|
98
99
|
|
99
|
-
def post(route, app_proc = nil, &blk)
|
100
|
-
endpoint(route, :post, app_proc, &blk)
|
100
|
+
def post(route, app_proc = nil, nonblocking: false, &blk)
|
101
|
+
endpoint(route, [:post], app_proc, nonblocking: nonblocking, &blk)
|
101
102
|
end
|
102
103
|
|
103
|
-
def put(route, app_proc = nil, &blk)
|
104
|
-
endpoint(route, :put, app_proc, &blk)
|
104
|
+
def put(route, app_proc = nil, nonblocking: false, &blk)
|
105
|
+
endpoint(route, [:put], app_proc, nonblocking: nonblocking, &blk)
|
105
106
|
end
|
106
107
|
|
107
|
-
def delete(route, app_proc = nil, &blk)
|
108
|
-
endpoint(route, :delete, app_proc, &blk)
|
108
|
+
def delete(route, app_proc = nil, nonblocking: false, &blk)
|
109
|
+
endpoint(route, [:delete], app_proc, nonblocking: nonblocking, &blk)
|
109
110
|
end
|
110
111
|
|
111
|
-
def patch(route, app_proc = nil, &blk)
|
112
|
-
endpoint(route, :patch, app_proc, &blk)
|
112
|
+
def patch(route, app_proc = nil, nonblocking: false, &blk)
|
113
|
+
endpoint(route, [:patch], app_proc, nonblocking: nonblocking, &blk)
|
113
114
|
end
|
114
115
|
|
115
|
-
def endpoint(route,
|
116
|
-
raise "You can't use both a block and an explicit handler in the same endpoint" if blk && app_proc
|
116
|
+
def endpoint(route, methods=[], app_proc = nil, nonblocking: false, &blk)
|
117
117
|
raise "You must provide either a block or an explicit handler for the endpoint" if app_proc.nil? && blk.nil?
|
118
118
|
|
119
119
|
app_proc = @controller.method(app_proc).to_proc if app_proc.is_a?(Symbol)
|
120
120
|
|
121
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
|
122
136
|
|
123
|
-
|
124
|
-
|
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 }
|
125
150
|
end
|
126
151
|
end
|
127
152
|
|
128
|
-
def grpc(
|
153
|
+
def grpc(*handlers, reflection: true, nonblocking: false, **, &blk)
|
129
154
|
if @middleware[:app] && @middleware[:app][:request_type].to_s != "grpc"
|
130
155
|
raise "App has already been set. You can use only one of `run` and `rackup_file` or `grpc` per location"
|
131
156
|
end
|
132
157
|
|
133
|
-
|
134
|
-
request_type: "grpc",
|
135
|
-
impls: []
|
136
|
-
}
|
137
|
-
@middleware[:app][:impls] << handler
|
138
|
-
@middleware[:app][:app_proc] = Itsi::Server::GrpcInterface.for(@middleware[:app][:impls])
|
139
|
-
end
|
158
|
+
grpc_reflection(handlers) if reflection
|
140
159
|
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
144
165
|
end
|
166
|
+
end
|
145
167
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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" }
|
150
177
|
end
|
151
178
|
end
|
152
179
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
end
|
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
|
157
183
|
|
184
|
+
def rackup_file(rackup_file, nonblocking: false, sendfile: true, path_info: "/")
|
158
185
|
raise "Rackup file #{rackup_file} doesn't exist" unless File.exist?(rackup_file)
|
159
|
-
|
160
|
-
if @parent.nil?
|
161
|
-
@options[:app_loader] = -> { { "app_proc" => Itsi::Server::RackInterface.for(rackup_file) } }
|
162
|
-
else
|
163
|
-
@middleware[:app] = { app_proc: Itsi::Server::RackInterface.for(rackup_file) }
|
164
|
-
end
|
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 }
|
165
187
|
end
|
166
188
|
|
167
189
|
def include(path)
|
@@ -209,6 +231,20 @@ module Itsi
|
|
209
231
|
@options[:multithreaded_reactor] = !!multithreaded
|
210
232
|
end
|
211
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
|
+
|
212
248
|
def watch(path, commands)
|
213
249
|
raise "Watch be set at the root" unless @parent.nil?
|
214
250
|
|
@@ -223,6 +259,18 @@ module Itsi
|
|
223
259
|
@options[:scheduler_class] = klass_name if klass_name
|
224
260
|
end
|
225
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
|
+
|
226
274
|
def preload(preload)
|
227
275
|
raise "Preload must be set at the root" unless @parent.nil?
|
228
276
|
|
@@ -235,11 +283,6 @@ module Itsi
|
|
235
283
|
@options[:shutdown_timeout] = shutdown_timeout.to_f
|
236
284
|
end
|
237
285
|
|
238
|
-
def script_name(script_name)
|
239
|
-
raise "Script name must be set at the root" unless @parent.nil?
|
240
|
-
|
241
|
-
@options[:script_name] = script_name.to_s
|
242
|
-
end
|
243
286
|
|
244
287
|
def stream_body(stream_body)
|
245
288
|
raise "Stream body must be set at the root" unless @parent.nil?
|
@@ -298,80 +341,85 @@ module Itsi
|
|
298
341
|
end
|
299
342
|
|
300
343
|
def controller(controller)
|
301
|
-
raise "`controller` must be set inside a location block" if @parent.nil?
|
302
|
-
|
303
344
|
@controller = controller
|
304
345
|
end
|
305
346
|
|
306
347
|
def auth_basic(**args)
|
307
|
-
|
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
|
308
356
|
|
309
357
|
@middleware[:auth_basic] = args
|
310
358
|
end
|
311
359
|
|
312
360
|
def redirect(**args)
|
313
|
-
raise "`redirect` must be set inside a location block" if @parent.nil?
|
314
|
-
|
315
361
|
@middleware[:redirect] = args
|
316
362
|
end
|
317
363
|
|
318
364
|
def proxy(**args)
|
319
|
-
raise "`proxy` must be set inside a location block" if @parent.nil?
|
320
|
-
|
321
365
|
@middleware[:proxy] = args
|
322
366
|
end
|
323
367
|
|
324
|
-
def
|
325
|
-
|
368
|
+
def static_response(**args)
|
369
|
+
args[:body] = args[:body].bytes
|
370
|
+
@middleware[:static_response] = args
|
371
|
+
end
|
326
372
|
|
373
|
+
def auth_jwt(**args)
|
327
374
|
@middleware[:auth_jwt] = args
|
328
375
|
end
|
329
376
|
|
330
377
|
def auth_api_key(**args)
|
331
|
-
|
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
|
332
390
|
|
333
391
|
@middleware[:auth_api_key] = args
|
334
392
|
end
|
335
393
|
|
336
394
|
def compress(**args)
|
337
|
-
raise "`compress` must be set inside a location block" if @parent.nil?
|
338
|
-
|
339
395
|
@middleware[:compression] = args
|
340
396
|
end
|
341
397
|
|
342
398
|
def request_headers(**args)
|
343
|
-
raise "`request_headers` must be set inside a location block" if @parent.nil?
|
344
|
-
|
345
399
|
@middleware[:request_headers] = args
|
346
400
|
end
|
347
401
|
|
348
|
-
def
|
349
|
-
|
402
|
+
def max_body(**args)
|
403
|
+
@middleware[:max_body] = args
|
404
|
+
end
|
350
405
|
|
406
|
+
def response_headers(**args)
|
351
407
|
@middleware[:response_headers] = args
|
352
408
|
end
|
353
409
|
|
354
410
|
def rate_limit(**args)
|
355
|
-
raise "`rate_limit` must be set inside a location block" if @parent.nil?
|
356
|
-
|
357
411
|
@middleware[:rate_limit] = args
|
358
412
|
end
|
359
413
|
|
360
414
|
def cache_control(**args)
|
361
|
-
raise "`cache_control` must be set inside a location block" if @parent.nil?
|
362
|
-
|
363
415
|
@middleware[:cache_control] = args
|
364
416
|
end
|
365
417
|
|
366
418
|
def etag(**args)
|
367
|
-
raise "`etag` must be set inside a location block" if @parent.nil?
|
368
|
-
|
369
419
|
@middleware[:etag] = args
|
370
420
|
end
|
371
421
|
|
372
422
|
def intrusion_protection(**args)
|
373
|
-
raise "`intrusion_protection` must be set inside a location block" if @parent.nil?
|
374
|
-
|
375
423
|
args[:banned_url_patterns] = Array(args[:banned_url_patterns]).map do |pattern|
|
376
424
|
if pattern.is_a?(Regexp)
|
377
425
|
pattern.source
|
@@ -383,14 +431,10 @@ module Itsi
|
|
383
431
|
end
|
384
432
|
|
385
433
|
def cors(**args)
|
386
|
-
raise "`cors` must be set inside a location block" if @parent.nil?
|
387
|
-
|
388
434
|
@middleware[:cors] = args
|
389
435
|
end
|
390
436
|
|
391
437
|
def static_assets(**args)
|
392
|
-
raise "`static_assets` must be set inside a location block" if @parent.nil?
|
393
|
-
|
394
438
|
root_dir = args[:root_dir] || "."
|
395
439
|
|
396
440
|
if !File.exist?(root_dir)
|
@@ -401,14 +445,20 @@ module Itsi
|
|
401
445
|
|
402
446
|
args[:relative_path] = true unless args.key?(:relative_path)
|
403
447
|
|
404
|
-
|
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
|
405
457
|
@middleware[:static_assets] = args
|
406
458
|
end
|
407
459
|
end
|
408
460
|
|
409
461
|
def file_server(**args)
|
410
|
-
raise "`file_server` must be set inside a location block" if @parent.nil?
|
411
|
-
|
412
462
|
# Forward to static_assets for implementation
|
413
463
|
puts "Note: file_server is an alias for static_assets"
|
414
464
|
static_assets(**args)
|
@@ -421,7 +471,7 @@ module Itsi
|
|
421
471
|
if route_options
|
422
472
|
result << deep_stringify_keys(
|
423
473
|
{
|
424
|
-
route: Regexp.new("^#{route_options}
|
474
|
+
route: Regexp.new("^#{route_options}/?$"),
|
425
475
|
methods: @methods.any? ? @methods : nil,
|
426
476
|
protocols: @protocols.any? ? @protocols : nil,
|
427
477
|
hosts: @hosts.any? ? @hosts : nil,
|
@@ -443,22 +493,27 @@ module Itsi
|
|
443
493
|
case seg
|
444
494
|
when Regexp
|
445
495
|
seg.source
|
446
|
-
when /^:([A-Za-z_]\w*)(?:\(([^)]*)\))?$/
|
447
|
-
param_name = Regexp.last_match(1)
|
448
|
-
custom = Regexp.last_match(2)
|
449
|
-
if custom && !custom.empty?
|
450
|
-
"(?<#{param_name}>#{custom})"
|
451
|
-
else
|
452
|
-
"(?<#{param_name}>[^/]+)"
|
453
|
-
end
|
454
|
-
when /\*/
|
455
|
-
seg.gsub(/\*/, ".*")
|
456
496
|
else
|
457
|
-
|
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("/")
|
458
514
|
end
|
459
515
|
end.join("|")
|
460
|
-
|
461
|
-
if parent.paths_from_parent && parent.paths_from_parent != "(?:/.*)"
|
516
|
+
if parent && parent.paths_from_parent && parent.paths_from_parent != "(?:/)"
|
462
517
|
"#{parent.paths_from_parent}#{route_or_str != "" ? "(?:#{route_or_str})" : ""}"
|
463
518
|
else
|
464
519
|
route_or_str = "/#{route_or_str}" unless route_or_str.start_with?("/")
|
@@ -475,12 +530,16 @@ module Itsi
|
|
475
530
|
chain = []
|
476
531
|
node = self
|
477
532
|
while node
|
533
|
+
if node.middleware[:app]&.[](:preloader)
|
534
|
+
node.middleware[:app][:app_proc] = node.middleware[:app].delete(:preloader).call
|
535
|
+
end
|
478
536
|
chain << node
|
479
537
|
node = node.parent
|
480
538
|
end
|
481
539
|
chain.reverse!
|
482
540
|
|
483
541
|
merged = {}
|
542
|
+
|
484
543
|
chain.each do |n|
|
485
544
|
n.middleware.each do |k, v|
|
486
545
|
merged[k] = v
|
@@ -5,78 +5,113 @@ module Itsi
|
|
5
5
|
module Config
|
6
6
|
require_relative "config/dsl"
|
7
7
|
require_relative "default_app/default_app"
|
8
|
-
require "debug"
|
9
8
|
|
10
9
|
ITSI_DEFAULT_CONFIG_FILE = "Itsi.rb"
|
11
10
|
|
12
|
-
def self.
|
11
|
+
def self.prep_reexec!
|
13
12
|
@argv ||= ARGV[0...ARGV.index("--listeners")]
|
13
|
+
|
14
|
+
auto_suppress_fork_darwin_fork_safety_warnings = [
|
15
|
+
ENV["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"].nil? || ENV["PGGSSENCMODE"].nil?,
|
16
|
+
RUBY_PLATFORM =~ /darwin/,
|
17
|
+
!ENV.key?("ITSI_DISABLE_AUTO_DISABLE_DARWIN_FORK_SAFETY_WARNINGS"),
|
18
|
+
$PROGRAM_NAME =~ /itsi$/
|
19
|
+
].all?
|
20
|
+
return unless auto_suppress_fork_darwin_fork_safety_warnings
|
21
|
+
|
22
|
+
env = ENV.to_h.merge("OBJC_DISABLE_INITIALIZE_FORK_SAFETY" => "YES", "PGGSSENCMODE" => "disable")
|
23
|
+
if ENV["BUNDLE_BIN_PATH"]
|
24
|
+
exec env, "bundle", "exec", $PROGRAM_NAME, *@argv
|
25
|
+
else
|
26
|
+
exec env, $PROGRAM_NAME, *@argv
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
16
30
|
# The configuration used when launching the Itsi server are evaluated in the following precedence:
|
17
31
|
# 1. CLI Args.
|
18
32
|
# 2. Itsi.rb file.
|
19
33
|
# 3. Default values.
|
20
|
-
def self.build_config(args, config_file_path, builder_proc)
|
34
|
+
def self.build_config(args, config_file_path, builder_proc = nil)
|
35
|
+
args.transform_keys!(&:to_sym)
|
21
36
|
itsifile_config = \
|
22
37
|
if builder_proc
|
23
|
-
DSL.evaluate(builder_proc)
|
38
|
+
DSL.evaluate(&builder_proc)
|
39
|
+
elsif args[:static]
|
40
|
+
DSL.evaluate do
|
41
|
+
location "/" do
|
42
|
+
allow_list allowed_patterns: ['127.0.0.1']
|
43
|
+
rate_limit key: 'address', store_config: 'in_memory', requests: 2, seconds: 5
|
44
|
+
etag type: 'strong', algorithm: 'md5', min_body_size: 1024 * 1024
|
45
|
+
compress min_size: 1024 * 1024, level: 'fastest', algorithms: %w[zstd gzip brotli deflate], mime_types: %w[all], compress_streams: true
|
46
|
+
log_requests before: { level: "INFO", format: "[{request_id}] {method} {path_and_query} - {addr} " }, after: { level: "INFO", format: "[{request_id}] └─ {status} in {response_time}" }
|
47
|
+
static_assets \
|
48
|
+
relative_path: true,
|
49
|
+
allowed_extensions: [],
|
50
|
+
root_dir: '.',
|
51
|
+
not_found_behavior: {error: 'not_found'},
|
52
|
+
auto_index: true,
|
53
|
+
try_html_extension: true,
|
54
|
+
max_file_size_in_memory: 1024 * 1024, # 1MB
|
55
|
+
max_files_in_memory: 1000,
|
56
|
+
file_check_interval: 1,
|
57
|
+
serve_hidden_files: false,
|
58
|
+
headers: {
|
59
|
+
'X-Content-Type-Options' => 'nosniff'
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
24
63
|
elsif File.exist?(config_file_path.to_s)
|
25
64
|
DSL.evaluate(config_file_path)
|
65
|
+
elsif File.exist?("./config.ru")
|
66
|
+
DSL.evaluate do
|
67
|
+
preload true
|
68
|
+
rackup_file args.fetch(:rackup_file, "./config.ru")
|
69
|
+
end
|
26
70
|
else
|
27
|
-
{}
|
71
|
+
DSL.evaluate{}
|
28
72
|
end
|
29
|
-
|
73
|
+
|
30
74
|
itsifile_config.transform_keys!(&:to_sym)
|
31
75
|
|
32
76
|
# We'll preload while we load config, if enabled.
|
33
77
|
middleware_loader = itsifile_config.fetch(:middleware_loader, -> {})
|
34
|
-
default_app_loader = itsifile_config.fetch(:app_loader) do
|
35
|
-
rackup_file_path = args.fetch(:rackup_file, "./config.ru")
|
36
|
-
if File.exist?(rackup_file_path)
|
37
|
-
lambda {
|
38
|
-
{ "app_proc" => Itsi::Server::RackInterface.for(rackup_file_path) }
|
39
|
-
}
|
40
|
-
else
|
41
|
-
DEFAULT_APP
|
42
|
-
end
|
43
|
-
end
|
44
78
|
preload = args.fetch(:preload) { itsifile_config.fetch(:preload, false) }
|
45
79
|
|
46
80
|
case preload
|
47
81
|
# If we preload everything, then we'll load middleware and default rack app ahead of time
|
48
82
|
when true
|
49
83
|
preloaded_middleware = middleware_loader.call
|
50
|
-
preloaded_app = default_app_loader.call
|
51
84
|
middleware_loader = -> { preloaded_middleware }
|
52
|
-
default_app_loader = -> { preloaded_app }
|
53
85
|
# If we're just preloading a specific gem group, we'll do that here too
|
54
86
|
when Symbol
|
55
87
|
Bundler.require(preload)
|
56
88
|
end
|
57
89
|
|
58
90
|
{
|
59
|
-
workers: args.fetch(:workers) { itsifile_config.fetch(:workers,
|
91
|
+
workers: args.fetch(:workers) { itsifile_config.fetch(:workers, 1) },
|
60
92
|
worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) },
|
61
93
|
silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) },
|
62
94
|
shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) },
|
63
95
|
hooks: itsifile_config.fetch(:hooks, nil),
|
64
96
|
preload: !!preload,
|
97
|
+
request_timeout: itsifile_config.fetch(:request_timeout, nil),
|
65
98
|
notify_watchers: itsifile_config.fetch(:notify_watchers, nil),
|
66
99
|
threads: args.fetch(:threads) { itsifile_config.fetch(:threads, 1) },
|
67
|
-
|
100
|
+
scheduler_threads: args.fetch(:scheduler_threads) { itsifile_config.fetch(:scheduler_threads, nil) },
|
68
101
|
streamable_body: args.fetch(:streamable_body) { itsifile_config.fetch(:streamable_body, false) },
|
69
102
|
multithreaded_reactor: args.fetch(:multithreaded_reactor) do
|
70
|
-
itsifile_config.fetch(:multithreaded_reactor,
|
103
|
+
itsifile_config.fetch(:multithreaded_reactor, nil)
|
71
104
|
end,
|
105
|
+
pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, true) },
|
72
106
|
scheduler_class: args.fetch(:scheduler_class) { itsifile_config.fetch(:scheduler_class, nil) },
|
73
107
|
oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do
|
74
108
|
itsifile_config.fetch(:oob_gc_responses_threshold, nil)
|
75
109
|
end,
|
76
110
|
log_level: args.fetch(:log_level) { itsifile_config.fetch(:log_level, nil) },
|
111
|
+
log_format: args.fetch(:log_format) { itsifile_config.fetch(:log_format, nil) },
|
112
|
+
log_target: args.fetch(:log_target) { itsifile_config.fetch(:log_target, nil) },
|
77
113
|
binds: args.fetch(:binds) { itsifile_config.fetch(:binds, ["http://0.0.0.0:3000"]) },
|
78
114
|
middleware_loader: middleware_loader,
|
79
|
-
default_app_loader: default_app_loader,
|
80
115
|
listeners: args.fetch(:listeners) { nil }
|
81
116
|
}.transform_keys(&:to_s)
|
82
117
|
end
|
@@ -123,7 +158,7 @@ module Itsi
|
|
123
158
|
|
124
159
|
puts "Writing default configuration..."
|
125
160
|
File.open(ITSI_DEFAULT_CONFIG_FILE, "w") do |file|
|
126
|
-
file.write(IO.read("#{__dir__}/Itsi.rb"))
|
161
|
+
file.write(IO.read("#{__dir__}/default_config/Itsi.rb"))
|
127
162
|
end
|
128
163
|
end
|
129
164
|
end
|
@@ -4,35 +4,31 @@ DEFAULT_INDEX = IO.read("#{__dir__}/index.html").freeze
|
|
4
4
|
DEFAULT_BINDS = ["http://0.0.0.0:3000"].freeze
|
5
5
|
DEFAULT_APP = lambda {
|
6
6
|
require "json"
|
7
|
-
|
8
|
-
Itsi.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
7
|
+
|
8
|
+
Itsi::Server::RackInterface.for(lambda do |env|
|
9
|
+
headers, body = \
|
10
|
+
if env["itsi.response"].json?
|
11
|
+
[
|
12
|
+
{ "Content-Type" => "application/json" },
|
13
|
+
[{ "message" => "You're running on Itsi!", "rack_env" => env,
|
14
|
+
"version" => Itsi::Server::VERSION }.to_json]
|
15
|
+
]
|
16
|
+
else
|
17
|
+
[
|
18
|
+
{ "Content-Type" => "text/html" },
|
19
19
|
[
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
HTTP_USER_AGENT: env["HTTP_USER_AGENT"]
|
30
|
-
)
|
31
|
-
]
|
20
|
+
format(
|
21
|
+
DEFAULT_INDEX,
|
22
|
+
REQUEST_METHOD: env["REQUEST_METHOD"],
|
23
|
+
PATH_INFO: env["PATH_INFO"],
|
24
|
+
SERVER_NAME: env["SERVER_NAME"],
|
25
|
+
SERVER_PORT: env["SERVER_PORT"],
|
26
|
+
REMOTE_ADDR: env["REMOTE_ADDR"],
|
27
|
+
HTTP_USER_AGENT: env["HTTP_USER_AGENT"]
|
28
|
+
)
|
32
29
|
]
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
]
|
31
|
+
end
|
32
|
+
[200, headers, body]
|
33
|
+
end)
|
38
34
|
}
|