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.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +124 -109
  3. data/Cargo.toml +6 -0
  4. data/crates/itsi_error/Cargo.toml +1 -0
  5. data/crates/itsi_error/src/lib.rs +100 -10
  6. data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
  7. data/crates/itsi_server/Cargo.toml +8 -10
  8. data/crates/itsi_server/src/default_responses/html/401.html +68 -0
  9. data/crates/itsi_server/src/default_responses/html/403.html +68 -0
  10. data/crates/itsi_server/src/default_responses/html/404.html +68 -0
  11. data/crates/itsi_server/src/default_responses/html/413.html +71 -0
  12. data/crates/itsi_server/src/default_responses/html/429.html +68 -0
  13. data/crates/itsi_server/src/default_responses/html/500.html +71 -0
  14. data/crates/itsi_server/src/default_responses/html/502.html +71 -0
  15. data/crates/itsi_server/src/default_responses/html/503.html +68 -0
  16. data/crates/itsi_server/src/default_responses/html/504.html +69 -0
  17. data/crates/itsi_server/src/default_responses/html/index.html +238 -0
  18. data/crates/itsi_server/src/default_responses/json/401.json +6 -0
  19. data/crates/itsi_server/src/default_responses/json/403.json +6 -0
  20. data/crates/itsi_server/src/default_responses/json/404.json +6 -0
  21. data/crates/itsi_server/src/default_responses/json/413.json +6 -0
  22. data/crates/itsi_server/src/default_responses/json/429.json +6 -0
  23. data/crates/itsi_server/src/default_responses/json/500.json +6 -0
  24. data/crates/itsi_server/src/default_responses/json/502.json +6 -0
  25. data/crates/itsi_server/src/default_responses/json/503.json +6 -0
  26. data/crates/itsi_server/src/default_responses/json/504.json +6 -0
  27. data/crates/itsi_server/src/default_responses/mod.rs +11 -0
  28. data/crates/itsi_server/src/lib.rs +58 -26
  29. data/crates/itsi_server/src/prelude.rs +2 -0
  30. data/crates/itsi_server/src/ruby_types/README.md +21 -0
  31. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
  32. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  33. data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
  34. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
  35. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
  36. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
  37. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
  38. data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
  39. data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
  40. data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
  41. data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
  42. data/crates/itsi_server/src/server/binds/mod.rs +4 -0
  43. data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
  44. data/crates/itsi_server/src/server/http_message_types.rs +97 -0
  45. data/crates/itsi_server/src/server/io_stream.rs +2 -1
  46. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
  70. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  71. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
  72. data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
  73. data/crates/itsi_server/src/server/mod.rs +3 -9
  74. data/crates/itsi_server/src/server/process_worker.rs +21 -3
  75. data/crates/itsi_server/src/server/request_job.rs +2 -2
  76. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
  77. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
  78. data/crates/itsi_server/src/server/signal.rs +24 -41
  79. data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
  80. data/crates/itsi_server/src/server/thread_worker.rs +59 -28
  81. data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
  82. data/crates/itsi_server/src/services/mime_types.rs +1416 -0
  83. data/crates/itsi_server/src/services/mod.rs +6 -0
  84. data/crates/itsi_server/src/services/password_hasher.rs +83 -0
  85. data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
  86. data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
  87. data/crates/itsi_tracing/src/lib.rs +145 -55
  88. data/{Itsi.rb → foo/Itsi.rb} +6 -9
  89. data/gems/scheduler/Cargo.lock +7 -0
  90. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  91. data/gems/scheduler/test/helpers/test_helper.rb +0 -1
  92. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  93. data/gems/scheduler/test/test_network_io.rb +1 -1
  94. data/gems/scheduler/test/test_process_wait.rb +0 -1
  95. data/gems/server/Cargo.lock +124 -109
  96. data/gems/server/exe/itsi +65 -19
  97. data/gems/server/itsi-server.gemspec +4 -3
  98. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  99. data/gems/server/lib/itsi/http_request.rb +116 -17
  100. data/gems/server/lib/itsi/http_response.rb +2 -0
  101. data/gems/server/lib/itsi/passfile.rb +109 -0
  102. data/gems/server/lib/itsi/server/config/dsl.rb +160 -101
  103. data/gems/server/lib/itsi/server/config.rb +58 -23
  104. data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
  105. data/gems/server/lib/itsi/server/default_app/index.html +113 -89
  106. data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
  107. data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
  108. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
  109. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  110. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  111. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  112. data/gems/server/lib/itsi/server/route_tester.rb +107 -0
  113. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  114. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  115. data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
  116. data/gems/server/lib/itsi/server/version.rb +1 -1
  117. data/gems/server/lib/itsi/server.rb +82 -12
  118. data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
  119. data/gems/server/lib/shell_completions/completions.rb +26 -0
  120. data/gems/server/test/helpers/test_helper.rb +2 -1
  121. data/lib/itsi/version.rb +1 -1
  122. data/sandbox/README.md +5 -0
  123. data/sandbox/itsi_file/Gemfile +4 -2
  124. data/sandbox/itsi_file/Gemfile.lock +48 -6
  125. data/sandbox/itsi_file/Itsi.rb +326 -129
  126. data/sandbox/itsi_file/call.json +1 -0
  127. data/sandbox/itsi_file/echo_client/Gemfile +10 -0
  128. data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
  129. data/sandbox/itsi_file/echo_client/README.md +95 -0
  130. data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
  131. data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
  132. data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
  133. data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
  134. data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
  135. data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
  136. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
  137. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
  138. data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
  139. data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
  140. data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
  141. data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
  142. data/sandbox/itsi_sandbox_async/config.ru +0 -1
  143. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  144. data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
  145. data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
  146. data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
  147. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
  148. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
  149. data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
  150. data/sandbox/itsi_sinatra/app.rb +0 -1
  151. data/sandbox/static_files/.env +1 -0
  152. data/sandbox/static_files/404.html +25 -0
  153. data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
  154. data/sandbox/static_files/about.html +68 -0
  155. data/sandbox/static_files/tiny.html +1 -0
  156. data/sandbox/static_files/writebook.zip +0 -0
  157. data/tasks.txt +28 -33
  158. metadata +87 -26
  159. data/crates/itsi_error/src/from.rs +0 -68
  160. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
  161. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
  162. data/crates/itsi_server/src/server/itsi_service.rs +0 -172
  163. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
  164. data/crates/itsi_server/src/server/types.rs +0 -43
  165. data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
  166. data/sandbox/itsi_file/public/assets/index.html +0 -1
  167. /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
  168. /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
  169. /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -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("").freeze
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"
@@ -28,7 +32,7 @@ module Itsi
28
32
  {
29
33
  "SERVER_SOFTWARE" => "Itsi",
30
34
  "SCRIPT_NAME" => script_name,
31
- "REQUEST_METHOD" => method,
35
+ "REQUEST_METHOD" => request_method,
32
36
  "PATH_INFO" => path,
33
37
  "REQUEST_PATH" => path,
34
38
  "QUERY_STRING" => query_string,
@@ -49,39 +53,134 @@ module Itsi
49
53
  "rack.run_once" => false,
50
54
  "rack.hijack?" => true,
51
55
  "rack.multipart.buffer_size" => 16_384,
52
- "rack.hijack" => build_hijack_proc
56
+ "rack.hijack" => method(:hijack)
53
57
  }.tap do |r|
54
58
  headers.each do |(k, v)|
55
- r[RACK_HEADER_MAP[k]] = v
59
+ r[case k
60
+ when "content-type" then "CONTENT_TYPE"
61
+ when "content-length" then "CONTENT_LENGTH"
62
+ when "accept" then "HTTP_ACCEPT"
63
+ when "accept-encoding" then "HTTP_ACCEPT_ENCODING"
64
+ when "accept-language" then "HTTP_ACCEPT_LANGUAGE"
65
+ when "user-agent" then "HTTP_USER_AGENT"
66
+ when "referer" then "HTTP_REFERER"
67
+ when "origin" then "HTTP_ORIGIN"
68
+ when "cookie" then "HTTP_COOKIE"
69
+ when "authorization" then "HTTP_AUTHORIZATION"
70
+ when "x-forwarded-for" then "HTTP_X_FORWARDED_FOR"
71
+ when "x-forwarded-proto" then "HTTP_X_FORWARDED_PROTO"
72
+ else RACK_HEADER_MAP[k]
73
+ end
74
+ ] = v
56
75
  end
57
76
  end
58
77
  end
59
78
 
60
- def respond(_body = nil, _status = 200, _header = nil, status: _status, headers: _header, body: _body,
61
- hijack: false, &blk)
79
+ def respond(
80
+ _body = nil, _status = 200, _headers = nil,
81
+ json: nil,
82
+ html: nil,
83
+ text: nil,
84
+ xml: nil,
85
+ hijack: false,
86
+ as: nil,
87
+ status: _status,
88
+ headers: _headers,
89
+ body: _body,
90
+ &blk
91
+ )
92
+
93
+ if json
94
+ validate!(json, as: as) if as
95
+ body = json.to_json
96
+ headers ||= {}
97
+ headers["Content-Type"] ||= "application/json"
98
+ elsif html
99
+ body = html
100
+ headers ||= {}
101
+ headers["Content-Type"] ||= "text/html"
102
+ elsif xml
103
+ body = xml
104
+ headers ||= {}
105
+ headers["Content-Type"] ||= "application/xml"
106
+ elsif text
107
+ body = text
108
+ headers ||= {}
109
+ headers["Content-Type"] ||= "text/plain"
110
+ end
111
+
62
112
  response.respond(status: status, headers: headers, body: body, hijack: hijack, &blk)
63
113
  end
64
114
 
65
- def build_hijack_proc
66
- lambda do
67
- self.hijacked = true
68
- UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
69
- server_sock.autoclose = false
70
- response.hijack(server_sock.fileno)
71
- server_sock.sync = true
72
- app_sock.sync = true
73
- app_sock
74
- end
115
+ def hijack
116
+ self.hijacked = true
117
+ UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
118
+ server_sock.autoclose = false
119
+ self.response.hijack(server_sock.fileno)
120
+ server_sock.sync = true
121
+ app_sock.sync = true
122
+ app_sock
75
123
  end
76
124
  end
77
125
 
78
126
  def build_input_io
79
127
  case body
80
- when nil then StringIO.new("")
128
+ when nil then EMPTY_IO
81
129
  when String then StringIO.new(body)
82
130
  when Array then File.open(body.first, "rb")
83
131
  else body
84
132
  end
85
133
  end
134
+
135
+ def validate!(params, as: nil)
136
+ as ? apply_schema!(params, as) : params
137
+ end
138
+
139
+ def params(schema=nil)
140
+ params = case
141
+ when url_encoded? then URI.decode_www_form(build_input_io.read).to_h
142
+ when json? then JSON.parse(build_input_io.read)
143
+ when multipart?
144
+ Rack::Multipart::Parser.parse(
145
+ build_input_io,
146
+ content_length,
147
+ content_type,
148
+ Rack::Multipart::Parser::TEMPFILE_FACTORY,
149
+ Rack::Multipart::Parser::BUFSIZE,
150
+ Rack::Utils.default_query_parser
151
+ ).params
152
+ else
153
+ {}
154
+ end
155
+
156
+ params.merge!(query_params).merge!(url_params)
157
+
158
+ yield schema ? apply_schema!(params, schema) : params
159
+
160
+ rescue StandardError => e
161
+ if response.json?
162
+ respond(json: {error: e.message}, status: 400)
163
+ else
164
+ respond(e.message, 400)
165
+ end
166
+ ensure
167
+ clean_temp_files(params)
168
+ end
169
+
170
+ def clean_temp_files(params)
171
+ case params
172
+ when Hash
173
+ if params.key?(:tempfile)
174
+ params[:tempfile].unlink
175
+ else
176
+ params.each_value { |v| clean_temp_files(v) }
177
+ end
178
+ when Array then params.each { |v| clean_temp_files(v) }
179
+ end
180
+ end
181
+
182
+ def query_params
183
+ URI.decode_www_form(query_string).to_h
184
+ end
86
185
  end
87
186
  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