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
data/gems/server/exe/itsi
CHANGED
@@ -4,28 +4,48 @@
|
|
4
4
|
require "itsi/server"
|
5
5
|
require "optparse"
|
6
6
|
|
7
|
-
|
7
|
+
|
8
|
+
COMMANDS = {
|
9
|
+
"init" => "Initialize a new Itsi.rb server configuration file",
|
10
|
+
"status" => "Show the status of the server",
|
11
|
+
"start" => "Start the Isti server",
|
12
|
+
"serve" => "Start the Isti server",
|
13
|
+
"stop" => "Stop the server",
|
14
|
+
"reload" => "Reload the server",
|
15
|
+
"restart" => "Restart the server",
|
16
|
+
"add_worker" => "Add a new worker to the server cluster",
|
17
|
+
"remove_worker" => "Remove a worker from the server cluster",
|
18
|
+
"routes" => "Print the routes of the server",
|
19
|
+
"passfile" => "Manage hashed users and passwords in a passfile (like .htpasswd). [add, remove, list]",
|
20
|
+
"test_route" => "Test which route a request will be routed to",
|
21
|
+
"static" => "Serve static assets in the given directory"
|
22
|
+
}
|
23
|
+
|
24
|
+
Itsi::Server::Config.prep_reexec!
|
8
25
|
|
9
26
|
options = {}
|
10
27
|
|
11
|
-
OptionParser.new do |opts|
|
12
|
-
opts.banner = "Usage: itsi [options]"
|
28
|
+
parser = OptionParser.new do |opts|
|
29
|
+
opts.banner = "Usage: itsi [COMMAND] [options]"
|
13
30
|
|
14
31
|
opts.on("-C", "--config CONFIG_FILE", String, "Itsi Configuration file to use (default: Itsi.rb)") do |config_file|
|
15
32
|
options[:config_file] = config_file
|
16
33
|
end
|
17
34
|
|
18
|
-
opts.on("-w", "--workers WORKERS", Integer, "Number of workers
|
35
|
+
opts.on("-w", "--workers WORKERS", Integer, "Number of workers") do |w|
|
19
36
|
options[:workers] = w
|
20
37
|
end
|
21
38
|
|
39
|
+
opts.on("-d", "--daemonize", "Run the process as a daemon") do
|
40
|
+
Process.daemon(true)
|
41
|
+
end
|
42
|
+
|
22
43
|
opts.on("-t", "--threads THREADS", Integer, "Number of threads (default: 1)") do |t|
|
23
44
|
options[:threads] = t
|
24
45
|
end
|
25
46
|
|
26
|
-
opts.on("-
|
27
|
-
|
28
|
-
options[:multithreaded_reactor] = mtr.to_s == "true"
|
47
|
+
opts.on("--[no-]multithreaded-reactor", "Use a multithreaded reactor") do |mtr|
|
48
|
+
options[:multithreaded_reactor] = mtr
|
29
49
|
end
|
30
50
|
|
31
51
|
opts.on("-r", "--rackup_file FILE", String, "Rackup file to use (default: config.ru)") do |rf|
|
@@ -97,9 +117,6 @@ OptionParser.new do |opts|
|
|
97
117
|
options[:shutdown_timeout] = shutdown_timeout
|
98
118
|
end
|
99
119
|
|
100
|
-
opts.on("--script_name SCRIPT_NAME", String, "Script name to inject into Rack ENV") do |script_name|
|
101
|
-
options[:script_name] = script_name
|
102
|
-
end
|
103
120
|
|
104
121
|
opts.on("--stream-body", TrueClass, "Stream body frames (default: false for best compatibility)") do |stream_body|
|
105
122
|
options[:stream_body] = stream_body
|
@@ -107,6 +124,10 @@ OptionParser.new do |opts|
|
|
107
124
|
|
108
125
|
opts.on("-h", "--help", "Show this help message") do
|
109
126
|
puts opts
|
127
|
+
puts "COMMAND: "
|
128
|
+
COMMANDS.each do |command, description|
|
129
|
+
puts " #{command} - #{description}"
|
130
|
+
end
|
110
131
|
exit
|
111
132
|
end
|
112
133
|
|
@@ -117,17 +138,42 @@ OptionParser.new do |opts|
|
|
117
138
|
opts.on("--listeners LISTENERS", String, "Listeners for reexec") do |listeners|
|
118
139
|
options[:listeners] = listeners
|
119
140
|
end
|
120
|
-
end.parse!
|
121
141
|
|
142
|
+
opts.on("--passfile PASSFILE", String, "Passfile") do |passfile|
|
143
|
+
options[:passfile] = passfile
|
144
|
+
end
|
145
|
+
|
146
|
+
opts.on("--algorithm ALGORITHM", String, "Algorithm for password hashing") do |algorithm|
|
147
|
+
options[:algorithm] = algorithm
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
if ENV['COMP_LINE'] || ARGV.include?('--completion')
|
152
|
+
puts COMMANDS.keys
|
153
|
+
exit
|
154
|
+
end
|
122
155
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
when
|
156
|
+
parser.parse!
|
157
|
+
|
158
|
+
case (command = ARGV.shift)
|
159
|
+
when *COMMANDS.keys
|
160
|
+
required_arity = Itsi::Server.method(command).parameters&.select{|c| c.first == :req }&.length&.succ || 2
|
161
|
+
case required_arity
|
162
|
+
when 1 then Itsi::Server.send(command)
|
163
|
+
when 2 then Itsi::Server.send(command, options)
|
164
|
+
else
|
165
|
+
if ARGV.length != required_arity - 2
|
166
|
+
puts "Command #{command} requires #{required_arity - 2} subcommands. "
|
167
|
+
exit(0)
|
168
|
+
end
|
169
|
+
Itsi::Server.send(command, options, *ARGV)
|
170
|
+
end
|
171
|
+
when nil
|
130
172
|
Itsi::Server.start(options)
|
131
173
|
else
|
132
|
-
puts "Invalid command #{command}"
|
174
|
+
puts "Invalid command #{command}.\n"
|
175
|
+
puts "COMMAND: "
|
176
|
+
COMMANDS.each do |command, description|
|
177
|
+
puts " #{command} - #{description}"
|
178
|
+
end
|
133
179
|
end
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
(f == gemspec) ||
|
28
28
|
f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
|
29
29
|
end
|
30
|
-
end + Dir["../../crates/**/*.{toml,rs,lock}"].map do |ext_file|
|
30
|
+
end + Dir["../../crates/**/*.{toml,rs,lock,html,json}"].map do |ext_file|
|
31
31
|
"ext/#{ext_file[%r{.*crates/(.*?)$}, 1]}"
|
32
32
|
end.compact
|
33
33
|
|
@@ -36,10 +36,11 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.require_paths = ["lib"]
|
37
37
|
spec.extensions = ["ext/itsi_server/extconf.rb"]
|
38
38
|
|
39
|
-
# Uncomment to register a new dependency of your gem
|
40
|
-
# spec.add_dependency "example-gem", "~> 1.0"
|
41
39
|
spec.add_dependency "rack", ">= 1.6"
|
40
|
+
spec.add_dependency "json", '~> 2'
|
42
41
|
spec.add_dependency "rb_sys", "~> 0.9.91"
|
42
|
+
|
43
|
+
spec.add_development_dependency "ruby-lsp"
|
43
44
|
# For more information and examples about making a new gem, check out our
|
44
45
|
# guide at: https://bundler.io/guides/creating_gem.html
|
45
46
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Itsi
|
2
|
+
class HttpRequest
|
3
|
+
module ResponseStatusShortcodes
|
4
|
+
|
5
|
+
HTTP_STATUS_CODES = {
|
6
|
+
100 => :continue,
|
7
|
+
101 => :switching_protocols,
|
8
|
+
102 => :processing,
|
9
|
+
200 => :ok,
|
10
|
+
201 => :created,
|
11
|
+
202 => :accepted,
|
12
|
+
203 => :non_authoritative_information,
|
13
|
+
204 => :no_content,
|
14
|
+
205 => :reset_content,
|
15
|
+
206 => :partial_content,
|
16
|
+
207 => :multi_status,
|
17
|
+
208 => :already_reported,
|
18
|
+
226 => :im_used,
|
19
|
+
300 => :multiple_choices,
|
20
|
+
301 => :moved_permanently,
|
21
|
+
302 => :found,
|
22
|
+
303 => :see_other,
|
23
|
+
304 => :not_modified,
|
24
|
+
305 => :use_proxy,
|
25
|
+
307 => :temporary_redirect,
|
26
|
+
308 => :permanent_redirect,
|
27
|
+
400 => :bad_request,
|
28
|
+
401 => :unauthorized,
|
29
|
+
402 => :payment_required,
|
30
|
+
403 => :forbidden,
|
31
|
+
404 => :not_found,
|
32
|
+
405 => :method_not_allowed,
|
33
|
+
406 => :not_acceptable,
|
34
|
+
407 => :proxy_authentication_required,
|
35
|
+
408 => :request_timeout,
|
36
|
+
409 => :conflict,
|
37
|
+
410 => :gone,
|
38
|
+
411 => :length_required,
|
39
|
+
412 => :precondition_failed,
|
40
|
+
413 => :payload_too_large,
|
41
|
+
414 => :uri_too_long,
|
42
|
+
415 => :unsupported_media_type,
|
43
|
+
416 => :range_not_satisfiable,
|
44
|
+
417 => :expectation_failed,
|
45
|
+
418 => :im_a_teapot,
|
46
|
+
421 => :misdirected_request,
|
47
|
+
422 => :unprocessable_entity,
|
48
|
+
423 => :locked,
|
49
|
+
424 => :failed_dependency,
|
50
|
+
425 => :too_early,
|
51
|
+
426 => :upgrade_required,
|
52
|
+
428 => :precondition_required,
|
53
|
+
429 => :too_many_requests,
|
54
|
+
431 => :request_header_fields_too_large,
|
55
|
+
451 => :unavailable_for_legal_reasons,
|
56
|
+
500 => :internal_server_error,
|
57
|
+
501 => :not_implemented,
|
58
|
+
502 => :bad_gateway,
|
59
|
+
503 => :service_unavailable,
|
60
|
+
504 => :gateway_timeout,
|
61
|
+
505 => :http_version_not_supported,
|
62
|
+
506 => :variant_also_negotiates,
|
63
|
+
507 => :insufficient_storage,
|
64
|
+
508 => :loop_detected,
|
65
|
+
510 => :not_extended,
|
66
|
+
511 => :network_authentication_required
|
67
|
+
}.freeze
|
68
|
+
|
69
|
+
HTTP_STATUS_CODES.each do |code, name|
|
70
|
+
define_method(name) {|*args, **kwargs| respond(*args, **kwargs, status: code) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -2,12 +2,16 @@
|
|
2
2
|
|
3
3
|
require "stringio"
|
4
4
|
require "socket"
|
5
|
+
require "uri"
|
6
|
+
require_relative 'http_request/response_status_shortcodes'
|
5
7
|
|
6
8
|
module Itsi
|
7
9
|
class HttpRequest
|
10
|
+
include Server::TypedHandlers::ParamParser
|
11
|
+
include ResponseStatusShortcodes
|
8
12
|
attr_accessor :hijacked
|
9
13
|
|
10
|
-
EMPTY_IO = StringIO.new("")
|
14
|
+
EMPTY_IO = StringIO.new("")
|
11
15
|
RACK_HEADER_MAP = StandardHeaders::ALL.map do |header|
|
12
16
|
rack_form = if header == "content-type"
|
13
17
|
"CONTENT_TYPE"
|
@@ -25,10 +29,11 @@ module Itsi
|
|
25
29
|
path = self.path
|
26
30
|
host = self.host
|
27
31
|
version = self.version
|
32
|
+
|
28
33
|
{
|
29
34
|
"SERVER_SOFTWARE" => "Itsi",
|
30
35
|
"SCRIPT_NAME" => script_name,
|
31
|
-
"REQUEST_METHOD" =>
|
36
|
+
"REQUEST_METHOD" => request_method,
|
32
37
|
"PATH_INFO" => path,
|
33
38
|
"REQUEST_PATH" => path,
|
34
39
|
"QUERY_STRING" => query_string,
|
@@ -49,39 +54,134 @@ module Itsi
|
|
49
54
|
"rack.run_once" => false,
|
50
55
|
"rack.hijack?" => true,
|
51
56
|
"rack.multipart.buffer_size" => 16_384,
|
52
|
-
"rack.hijack" =>
|
57
|
+
"rack.hijack" => method(:hijack)
|
53
58
|
}.tap do |r|
|
54
59
|
headers.each do |(k, v)|
|
55
|
-
r[
|
60
|
+
r[case k
|
61
|
+
when "content-type" then "CONTENT_TYPE"
|
62
|
+
when "content-length" then "CONTENT_LENGTH"
|
63
|
+
when "accept" then "HTTP_ACCEPT"
|
64
|
+
when "accept-encoding" then "HTTP_ACCEPT_ENCODING"
|
65
|
+
when "accept-language" then "HTTP_ACCEPT_LANGUAGE"
|
66
|
+
when "user-agent" then "HTTP_USER_AGENT"
|
67
|
+
when "referer" then "HTTP_REFERER"
|
68
|
+
when "origin" then "HTTP_ORIGIN"
|
69
|
+
when "cookie" then "HTTP_COOKIE"
|
70
|
+
when "authorization" then "HTTP_AUTHORIZATION"
|
71
|
+
when "x-forwarded-for" then "HTTP_X_FORWARDED_FOR"
|
72
|
+
when "x-forwarded-proto" then "HTTP_X_FORWARDED_PROTO"
|
73
|
+
else RACK_HEADER_MAP[k]
|
74
|
+
end
|
75
|
+
] = v
|
56
76
|
end
|
57
77
|
end
|
58
78
|
end
|
59
79
|
|
60
|
-
def respond(
|
61
|
-
|
80
|
+
def respond(
|
81
|
+
_body = nil, _status = 200, _headers = nil,
|
82
|
+
json: nil,
|
83
|
+
html: nil,
|
84
|
+
text: nil,
|
85
|
+
xml: nil,
|
86
|
+
hijack: false,
|
87
|
+
as: nil,
|
88
|
+
status: _status,
|
89
|
+
headers: _headers,
|
90
|
+
body: _body,
|
91
|
+
&blk
|
92
|
+
)
|
93
|
+
|
94
|
+
if json
|
95
|
+
validate!(json, as: as) if as
|
96
|
+
body = json.to_json
|
97
|
+
headers ||= {}
|
98
|
+
headers["Content-Type"] ||= "application/json"
|
99
|
+
elsif html
|
100
|
+
body = html
|
101
|
+
headers ||= {}
|
102
|
+
headers["Content-Type"] ||= "text/html"
|
103
|
+
elsif xml
|
104
|
+
body = xml
|
105
|
+
headers ||= {}
|
106
|
+
headers["Content-Type"] ||= "application/xml"
|
107
|
+
elsif text
|
108
|
+
body = text
|
109
|
+
headers ||= {}
|
110
|
+
headers["Content-Type"] ||= "text/plain"
|
111
|
+
end
|
112
|
+
|
62
113
|
response.respond(status: status, headers: headers, body: body, hijack: hijack, &blk)
|
63
114
|
end
|
64
115
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
app_sock
|
74
|
-
end
|
116
|
+
def hijack
|
117
|
+
self.hijacked = true
|
118
|
+
UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
|
119
|
+
server_sock.autoclose = false
|
120
|
+
self.response.hijack(server_sock.fileno)
|
121
|
+
server_sock.sync = true
|
122
|
+
app_sock.sync = true
|
123
|
+
app_sock
|
75
124
|
end
|
76
125
|
end
|
77
126
|
|
78
127
|
def build_input_io
|
79
128
|
case body
|
80
|
-
when nil then
|
129
|
+
when nil then EMPTY_IO
|
81
130
|
when String then StringIO.new(body)
|
82
131
|
when Array then File.open(body.first, "rb")
|
83
132
|
else body
|
84
133
|
end
|
85
134
|
end
|
135
|
+
|
136
|
+
def validate!(params, as: nil)
|
137
|
+
as ? apply_schema!(params, as) : params
|
138
|
+
end
|
139
|
+
|
140
|
+
def params(schema=nil)
|
141
|
+
params = case
|
142
|
+
when url_encoded? then URI.decode_www_form(build_input_io.read).to_h
|
143
|
+
when json? then JSON.parse(build_input_io.read)
|
144
|
+
when multipart?
|
145
|
+
Rack::Multipart::Parser.parse(
|
146
|
+
build_input_io,
|
147
|
+
content_length,
|
148
|
+
content_type,
|
149
|
+
Rack::Multipart::Parser::TEMPFILE_FACTORY,
|
150
|
+
Rack::Multipart::Parser::BUFSIZE,
|
151
|
+
Rack::Utils.default_query_parser
|
152
|
+
).params
|
153
|
+
else
|
154
|
+
{}
|
155
|
+
end
|
156
|
+
|
157
|
+
params.merge!(query_params).merge!(url_params)
|
158
|
+
|
159
|
+
yield schema ? apply_schema!(params, schema) : params
|
160
|
+
|
161
|
+
rescue StandardError => e
|
162
|
+
if response.json?
|
163
|
+
respond(json: {error: e.message}, status: 400)
|
164
|
+
else
|
165
|
+
respond(e.message, 400)
|
166
|
+
end
|
167
|
+
ensure
|
168
|
+
clean_temp_files(params)
|
169
|
+
end
|
170
|
+
|
171
|
+
def clean_temp_files(params)
|
172
|
+
case params
|
173
|
+
when Hash
|
174
|
+
if params.key?(:tempfile)
|
175
|
+
params[:tempfile].unlink
|
176
|
+
else
|
177
|
+
params.each_value { |v| clean_temp_files(v) }
|
178
|
+
end
|
179
|
+
when Array then params.each { |v| clean_temp_files(v) }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def query_params
|
184
|
+
URI.decode_www_form(query_string).to_h
|
185
|
+
end
|
86
186
|
end
|
87
187
|
end
|
@@ -10,6 +10,8 @@ module Itsi
|
|
10
10
|
def respond _body=nil, _status=200, _header=nil, status: _status, headers: _header, body: _body, hijack: false, &blk
|
11
11
|
self.status = status
|
12
12
|
|
13
|
+
body = body.to_s unless body.is_a?(String)
|
14
|
+
|
13
15
|
if headers
|
14
16
|
headers.each do |key, value|
|
15
17
|
if value.is_a?(Array)
|
@@ -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
|