itsi 0.1.14 → 0.1.19
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 +126 -272
- 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 +12 -11
- 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 +126 -272
- 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 +117 -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 +171 -99
- 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 +327 -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,83 +84,118 @@ 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=nil, 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
|
+
app = { preloader: -> { app_proc }, nonblocking: nonblocking }
|
150
|
+
@middleware[:app] = app
|
151
|
+
location("*") do
|
152
|
+
@middleware[:app] = app
|
153
|
+
end
|
125
154
|
end
|
126
155
|
end
|
127
156
|
|
128
|
-
def grpc(
|
157
|
+
def grpc(*handlers, reflection: true, nonblocking: false, **, &blk)
|
129
158
|
if @middleware[:app] && @middleware[:app][:request_type].to_s != "grpc"
|
130
159
|
raise "App has already been set. You can use only one of `run` and `rackup_file` or `grpc` per location"
|
131
160
|
end
|
132
161
|
|
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
|
162
|
+
grpc_reflection(handlers) if reflection
|
140
163
|
|
141
|
-
|
142
|
-
|
143
|
-
|
164
|
+
handlers.each do |handler|
|
165
|
+
location(Regexp.new("#{Regexp.escape(handler.class.service_name)}/(?:#{handler.class.rpc_descs.keys.map(&:to_s).join("|")})")) do
|
166
|
+
@middleware[:app] = { preloader: -> { Itsi::Server::GrpcInterface.for(handler) }, request_type: "grpc", nonblocking: nonblocking }
|
167
|
+
instance_exec(&blk)
|
168
|
+
end
|
144
169
|
end
|
170
|
+
end
|
145
171
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
172
|
+
def grpc_reflection(handlers)
|
173
|
+
@grpc_reflected_services ||= []
|
174
|
+
@grpc_reflected_services.concat(handlers)
|
175
|
+
|
176
|
+
location(["grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
|
177
|
+
"grpc.reflection.v1.ServerReflection/ServerReflectionInfo"]) do
|
178
|
+
@middleware[:app] = { preloader: lambda {
|
179
|
+
Itsi::Server::GrpcInterface.reflection_for(handlers)
|
180
|
+
}, request_type: "grpc" }
|
150
181
|
end
|
151
182
|
end
|
152
183
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
184
|
+
def run(app, sendfile: true, nonblocking: false, path_info: "/")
|
185
|
+
app_args = { preloader: -> { Itsi::Server::RackInterface.for(app) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
|
186
|
+
base_path = "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$"
|
187
|
+
@middleware[:app] = app_args
|
188
|
+
location("*") do
|
189
|
+
@middleware[:app] = app_args
|
156
190
|
end
|
191
|
+
end
|
157
192
|
|
193
|
+
def rackup_file(rackup_file, nonblocking: false, sendfile: true, path_info: "/")
|
158
194
|
raise "Rackup file #{rackup_file} doesn't exist" unless File.exist?(rackup_file)
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
@middleware[:app] = { app_proc: Itsi::Server::RackInterface.for(rackup_file) }
|
195
|
+
app_args = { preloader: -> { Itsi::Server::RackInterface.for(rackup_file) }, sendfile: sendfile, base_path: "^(?<base_path>#{paths_from_parent.gsub(/\.\*\)$/, ')')}).*$", path_info: path_info, nonblocking: nonblocking }
|
196
|
+
@middleware[:app] = app_args
|
197
|
+
location("*") do
|
198
|
+
@middleware[:app] = app_args
|
164
199
|
end
|
165
200
|
end
|
166
201
|
|
@@ -209,6 +244,20 @@ module Itsi
|
|
209
244
|
@options[:multithreaded_reactor] = !!multithreaded
|
210
245
|
end
|
211
246
|
|
247
|
+
def pin_worker_cores(pin_worker_cores)
|
248
|
+
raise "Pin worker cores must be set at the root" unless @parent.nil?
|
249
|
+
|
250
|
+
@options[:pin_worker_cores] = !!pin_worker_cores
|
251
|
+
end
|
252
|
+
|
253
|
+
def auto_reload_config!
|
254
|
+
if ENV["BUNDLE_BIN_PATH"]
|
255
|
+
watch "Itsi.rb", [%w[bundle exec itsi restart]]
|
256
|
+
else
|
257
|
+
watch "Itsi.rb", [%w[itsi restart]]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
212
261
|
def watch(path, commands)
|
213
262
|
raise "Watch be set at the root" unless @parent.nil?
|
214
263
|
|
@@ -223,6 +272,18 @@ module Itsi
|
|
223
272
|
@options[:scheduler_class] = klass_name if klass_name
|
224
273
|
end
|
225
274
|
|
275
|
+
def scheduler_threads(threads = 1)
|
276
|
+
raise "Scheduler threads must be set at the root" unless @parent.nil?
|
277
|
+
|
278
|
+
@options[:scheduler_threads] = threads
|
279
|
+
end
|
280
|
+
|
281
|
+
def request_timeout(request_timeout)
|
282
|
+
raise "Request timeout must be set at the root" unless @parent.nil?
|
283
|
+
|
284
|
+
@options[:request_timeout] = request_timeout
|
285
|
+
end
|
286
|
+
|
226
287
|
def preload(preload)
|
227
288
|
raise "Preload must be set at the root" unless @parent.nil?
|
228
289
|
|
@@ -235,11 +296,6 @@ module Itsi
|
|
235
296
|
@options[:shutdown_timeout] = shutdown_timeout.to_f
|
236
297
|
end
|
237
298
|
|
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
299
|
|
244
300
|
def stream_body(stream_body)
|
245
301
|
raise "Stream body must be set at the root" unless @parent.nil?
|
@@ -298,80 +354,85 @@ module Itsi
|
|
298
354
|
end
|
299
355
|
|
300
356
|
def controller(controller)
|
301
|
-
raise "`controller` must be set inside a location block" if @parent.nil?
|
302
|
-
|
303
357
|
@controller = controller
|
304
358
|
end
|
305
359
|
|
306
360
|
def auth_basic(**args)
|
307
|
-
|
361
|
+
|
362
|
+
if File.exist?(".itsi-credentials") && !args[:credential_file]
|
363
|
+
args[:credential_file] = ".itsi-credentials"
|
364
|
+
end
|
365
|
+
|
366
|
+
if args[:credential_file] && File.exist?(args[:credential_file])
|
367
|
+
args[:credential_pairs] = Passfile.load(args[:credential_file])
|
368
|
+
end
|
308
369
|
|
309
370
|
@middleware[:auth_basic] = args
|
310
371
|
end
|
311
372
|
|
312
373
|
def redirect(**args)
|
313
|
-
raise "`redirect` must be set inside a location block" if @parent.nil?
|
314
|
-
|
315
374
|
@middleware[:redirect] = args
|
316
375
|
end
|
317
376
|
|
318
377
|
def proxy(**args)
|
319
|
-
raise "`proxy` must be set inside a location block" if @parent.nil?
|
320
|
-
|
321
378
|
@middleware[:proxy] = args
|
322
379
|
end
|
323
380
|
|
324
|
-
def
|
325
|
-
|
381
|
+
def static_response(**args)
|
382
|
+
args[:body] = args[:body].bytes
|
383
|
+
@middleware[:static_response] = args
|
384
|
+
end
|
326
385
|
|
386
|
+
def auth_jwt(**args)
|
327
387
|
@middleware[:auth_jwt] = args
|
328
388
|
end
|
329
389
|
|
330
390
|
def auth_api_key(**args)
|
331
|
-
|
391
|
+
if args[:valid_keys] && args[:valid_keys].is_a?(Array)
|
392
|
+
args[:valid_keys] = args[:valid_keys].each_with_index.map { |key, index| [index, key] }.to_h
|
393
|
+
args[:key_id_source] = nil
|
394
|
+
end
|
395
|
+
|
396
|
+
if File.exist?(".itsi-credentials") && !args[:credential_file]
|
397
|
+
args[:credential_file] = ".itsi-credentials"
|
398
|
+
end
|
399
|
+
|
400
|
+
if args[:credential_file] && File.exist?(args[:credential_file])
|
401
|
+
args[:valid_keys] = Passfile.load(args[:credential_file])
|
402
|
+
end
|
332
403
|
|
333
404
|
@middleware[:auth_api_key] = args
|
334
405
|
end
|
335
406
|
|
336
407
|
def compress(**args)
|
337
|
-
raise "`compress` must be set inside a location block" if @parent.nil?
|
338
|
-
|
339
408
|
@middleware[:compression] = args
|
340
409
|
end
|
341
410
|
|
342
411
|
def request_headers(**args)
|
343
|
-
raise "`request_headers` must be set inside a location block" if @parent.nil?
|
344
|
-
|
345
412
|
@middleware[:request_headers] = args
|
346
413
|
end
|
347
414
|
|
348
|
-
def
|
349
|
-
|
415
|
+
def max_body(**args)
|
416
|
+
@middleware[:max_body] = args
|
417
|
+
end
|
350
418
|
|
419
|
+
def response_headers(**args)
|
351
420
|
@middleware[:response_headers] = args
|
352
421
|
end
|
353
422
|
|
354
423
|
def rate_limit(**args)
|
355
|
-
raise "`rate_limit` must be set inside a location block" if @parent.nil?
|
356
|
-
|
357
424
|
@middleware[:rate_limit] = args
|
358
425
|
end
|
359
426
|
|
360
427
|
def cache_control(**args)
|
361
|
-
raise "`cache_control` must be set inside a location block" if @parent.nil?
|
362
|
-
|
363
428
|
@middleware[:cache_control] = args
|
364
429
|
end
|
365
430
|
|
366
431
|
def etag(**args)
|
367
|
-
raise "`etag` must be set inside a location block" if @parent.nil?
|
368
|
-
|
369
432
|
@middleware[:etag] = args
|
370
433
|
end
|
371
434
|
|
372
435
|
def intrusion_protection(**args)
|
373
|
-
raise "`intrusion_protection` must be set inside a location block" if @parent.nil?
|
374
|
-
|
375
436
|
args[:banned_url_patterns] = Array(args[:banned_url_patterns]).map do |pattern|
|
376
437
|
if pattern.is_a?(Regexp)
|
377
438
|
pattern.source
|
@@ -383,14 +444,10 @@ module Itsi
|
|
383
444
|
end
|
384
445
|
|
385
446
|
def cors(**args)
|
386
|
-
raise "`cors` must be set inside a location block" if @parent.nil?
|
387
|
-
|
388
447
|
@middleware[:cors] = args
|
389
448
|
end
|
390
449
|
|
391
450
|
def static_assets(**args)
|
392
|
-
raise "`static_assets` must be set inside a location block" if @parent.nil?
|
393
|
-
|
394
451
|
root_dir = args[:root_dir] || "."
|
395
452
|
|
396
453
|
if !File.exist?(root_dir)
|
@@ -401,14 +458,20 @@ module Itsi
|
|
401
458
|
|
402
459
|
args[:relative_path] = true unless args.key?(:relative_path)
|
403
460
|
|
404
|
-
|
461
|
+
args[:allowed_extensions] ||= []
|
462
|
+
|
463
|
+
if (args[:allowed_extensions].include?("html") || args[:allowed_extensions].include?(:html)) && args[:try_html_extension]
|
464
|
+
args[:allowed_extensions] << ""
|
465
|
+
end
|
466
|
+
|
467
|
+
args[:base_path] = "^(?<base_path>#{paths_from_parent}).*$"
|
468
|
+
|
469
|
+
location("*", extensions: args[:allowed_extensions]) do
|
405
470
|
@middleware[:static_assets] = args
|
406
471
|
end
|
407
472
|
end
|
408
473
|
|
409
474
|
def file_server(**args)
|
410
|
-
raise "`file_server` must be set inside a location block" if @parent.nil?
|
411
|
-
|
412
475
|
# Forward to static_assets for implementation
|
413
476
|
puts "Note: file_server is an alias for static_assets"
|
414
477
|
static_assets(**args)
|
@@ -421,7 +484,7 @@ module Itsi
|
|
421
484
|
if route_options
|
422
485
|
result << deep_stringify_keys(
|
423
486
|
{
|
424
|
-
route: Regexp.new("^#{route_options}
|
487
|
+
route: Regexp.new("^#{route_options}/?$"),
|
425
488
|
methods: @methods.any? ? @methods : nil,
|
426
489
|
protocols: @protocols.any? ? @protocols : nil,
|
427
490
|
hosts: @hosts.any? ? @hosts : nil,
|
@@ -443,22 +506,27 @@ module Itsi
|
|
443
506
|
case seg
|
444
507
|
when Regexp
|
445
508
|
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
509
|
else
|
457
|
-
|
510
|
+
parts = seg.split('/')
|
511
|
+
parts.map do |part|
|
512
|
+
case part
|
513
|
+
when /^:([A-Za-z_]\w*)(?:\(([^)]*)\))?$/
|
514
|
+
param_name = Regexp.last_match(1)
|
515
|
+
custom = Regexp.last_match(2)
|
516
|
+
if custom && !custom.empty?
|
517
|
+
"(?<#{param_name}>#{custom})"
|
518
|
+
else
|
519
|
+
"(?<#{param_name}>[^/]+)"
|
520
|
+
end
|
521
|
+
when /\*/
|
522
|
+
part.gsub(/\*/, ".*")
|
523
|
+
else
|
524
|
+
Regexp.escape(part)
|
525
|
+
end
|
526
|
+
end.join("/")
|
458
527
|
end
|
459
528
|
end.join("|")
|
460
|
-
|
461
|
-
if parent.paths_from_parent && parent.paths_from_parent != "(?:/.*)"
|
529
|
+
if parent && parent.paths_from_parent && parent.paths_from_parent != "(?:/)"
|
462
530
|
"#{parent.paths_from_parent}#{route_or_str != "" ? "(?:#{route_or_str})" : ""}"
|
463
531
|
else
|
464
532
|
route_or_str = "/#{route_or_str}" unless route_or_str.start_with?("/")
|
@@ -475,12 +543,16 @@ module Itsi
|
|
475
543
|
chain = []
|
476
544
|
node = self
|
477
545
|
while node
|
546
|
+
if node.middleware[:app]&.[](:preloader)
|
547
|
+
node.middleware[:app][:app_proc] = node.middleware[:app].delete(:preloader).call
|
548
|
+
end
|
478
549
|
chain << node
|
479
550
|
node = node.parent
|
480
551
|
end
|
481
552
|
chain.reverse!
|
482
553
|
|
483
554
|
merged = {}
|
555
|
+
|
484
556
|
chain.each do |n|
|
485
557
|
n.middleware.each do |k, v|
|
486
558
|
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
|
}
|