itsi 0.2.15 → 0.2.17

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 (182) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/Cargo.lock +76 -74
  4. data/crates/itsi_acme/Cargo.toml +1 -1
  5. data/crates/itsi_scheduler/Cargo.toml +1 -1
  6. data/crates/itsi_scheduler/extconf.rb +3 -1
  7. data/crates/itsi_server/Cargo.lock +1 -1
  8. data/crates/itsi_server/Cargo.toml +3 -1
  9. data/crates/itsi_server/extconf.rb +3 -1
  10. data/crates/itsi_server/src/lib.rs +7 -1
  11. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  12. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +6 -6
  13. data/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  14. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +71 -42
  15. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  16. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
  17. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +32 -6
  18. data/crates/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  19. data/crates/itsi_server/src/server/binds/listener.rs +49 -8
  20. data/crates/itsi_server/src/server/frame_stream.rs +142 -0
  21. data/crates/itsi_server/src/server/http_message_types.rs +143 -10
  22. data/crates/itsi_server/src/server/io_stream.rs +28 -5
  23. data/crates/itsi_server/src/server/lifecycle_event.rs +1 -1
  24. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  25. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  26. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  27. data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  28. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -58
  29. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +6 -9
  30. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +27 -42
  31. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +65 -14
  32. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +1 -1
  33. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +8 -11
  34. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +21 -8
  35. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  36. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +1 -5
  37. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  38. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +13 -6
  39. data/crates/itsi_server/src/server/mod.rs +1 -0
  40. data/crates/itsi_server/src/server/process_worker.rs +5 -5
  41. data/crates/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
  42. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
  43. data/crates/itsi_server/src/server/serve_strategy/mod.rs +1 -0
  44. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +166 -206
  45. data/crates/itsi_server/src/server/signal.rs +37 -9
  46. data/crates/itsi_server/src/server/thread_worker.rs +92 -70
  47. data/crates/itsi_server/src/services/itsi_http_service.rs +67 -62
  48. data/crates/itsi_server/src/services/mime_types.rs +185 -183
  49. data/crates/itsi_server/src/services/rate_limiter.rs +16 -34
  50. data/crates/itsi_server/src/services/static_file_server.rs +35 -60
  51. data/docs/benchmark-dashboard/.gitignore +27 -0
  52. data/docs/benchmark-dashboard/app/api/benchmarks/route.ts +22 -0
  53. data/docs/benchmark-dashboard/app/globals.css +94 -0
  54. data/docs/benchmark-dashboard/app/layout.tsx +20 -0
  55. data/docs/benchmark-dashboard/app/page.tsx +252 -0
  56. data/docs/benchmark-dashboard/components/benchmark-dashboard.tsx +1663 -0
  57. data/docs/benchmark-dashboard/components/theme-provider.tsx +11 -0
  58. data/docs/benchmark-dashboard/components/ui/accordion.tsx +58 -0
  59. data/docs/benchmark-dashboard/components/ui/alert-dialog.tsx +141 -0
  60. data/docs/benchmark-dashboard/components/ui/alert.tsx +59 -0
  61. data/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx +7 -0
  62. data/docs/benchmark-dashboard/components/ui/avatar.tsx +50 -0
  63. data/docs/benchmark-dashboard/components/ui/badge.tsx +36 -0
  64. data/docs/benchmark-dashboard/components/ui/breadcrumb.tsx +115 -0
  65. data/docs/benchmark-dashboard/components/ui/button.tsx +56 -0
  66. data/docs/benchmark-dashboard/components/ui/calendar.tsx +66 -0
  67. data/docs/benchmark-dashboard/components/ui/card.tsx +79 -0
  68. data/docs/benchmark-dashboard/components/ui/carousel.tsx +262 -0
  69. data/docs/benchmark-dashboard/components/ui/chart.tsx +365 -0
  70. data/docs/benchmark-dashboard/components/ui/checkbox.tsx +30 -0
  71. data/docs/benchmark-dashboard/components/ui/collapsible.tsx +11 -0
  72. data/docs/benchmark-dashboard/components/ui/command.tsx +153 -0
  73. data/docs/benchmark-dashboard/components/ui/context-menu.tsx +200 -0
  74. data/docs/benchmark-dashboard/components/ui/dialog.tsx +122 -0
  75. data/docs/benchmark-dashboard/components/ui/drawer.tsx +118 -0
  76. data/docs/benchmark-dashboard/components/ui/dropdown-menu.tsx +200 -0
  77. data/docs/benchmark-dashboard/components/ui/form.tsx +178 -0
  78. data/docs/benchmark-dashboard/components/ui/hover-card.tsx +29 -0
  79. data/docs/benchmark-dashboard/components/ui/input-otp.tsx +71 -0
  80. data/docs/benchmark-dashboard/components/ui/input.tsx +22 -0
  81. data/docs/benchmark-dashboard/components/ui/label.tsx +26 -0
  82. data/docs/benchmark-dashboard/components/ui/loading-spinner.tsx +12 -0
  83. data/docs/benchmark-dashboard/components/ui/menubar.tsx +236 -0
  84. data/docs/benchmark-dashboard/components/ui/navigation-menu.tsx +128 -0
  85. data/docs/benchmark-dashboard/components/ui/pagination.tsx +117 -0
  86. data/docs/benchmark-dashboard/components/ui/popover.tsx +31 -0
  87. data/docs/benchmark-dashboard/components/ui/progress.tsx +28 -0
  88. data/docs/benchmark-dashboard/components/ui/radio-group.tsx +44 -0
  89. data/docs/benchmark-dashboard/components/ui/resizable.tsx +45 -0
  90. data/docs/benchmark-dashboard/components/ui/scroll-area.tsx +48 -0
  91. data/docs/benchmark-dashboard/components/ui/select.tsx +160 -0
  92. data/docs/benchmark-dashboard/components/ui/separator.tsx +31 -0
  93. data/docs/benchmark-dashboard/components/ui/sheet.tsx +140 -0
  94. data/docs/benchmark-dashboard/components/ui/sidebar.tsx +763 -0
  95. data/docs/benchmark-dashboard/components/ui/skeleton.tsx +15 -0
  96. data/docs/benchmark-dashboard/components/ui/slider.tsx +28 -0
  97. data/docs/benchmark-dashboard/components/ui/sonner.tsx +31 -0
  98. data/docs/benchmark-dashboard/components/ui/switch.tsx +29 -0
  99. data/docs/benchmark-dashboard/components/ui/table.tsx +117 -0
  100. data/docs/benchmark-dashboard/components/ui/tabs.tsx +55 -0
  101. data/docs/benchmark-dashboard/components/ui/textarea.tsx +22 -0
  102. data/docs/benchmark-dashboard/components/ui/toast.tsx +129 -0
  103. data/docs/benchmark-dashboard/components/ui/toaster.tsx +35 -0
  104. data/docs/benchmark-dashboard/components/ui/toggle-group.tsx +61 -0
  105. data/docs/benchmark-dashboard/components/ui/toggle.tsx +45 -0
  106. data/docs/benchmark-dashboard/components/ui/tooltip.tsx +30 -0
  107. data/docs/benchmark-dashboard/components/ui/use-mobile.tsx +19 -0
  108. data/docs/benchmark-dashboard/components/ui/use-toast.ts +194 -0
  109. data/docs/benchmark-dashboard/components.json +21 -0
  110. data/docs/benchmark-dashboard/dist/benchmark-dashboard.css +1 -0
  111. data/docs/benchmark-dashboard/dist/benchmark-dashboard.iife.js +211 -0
  112. data/docs/benchmark-dashboard/dist/placeholder-logo.png +0 -0
  113. data/docs/benchmark-dashboard/dist/placeholder-logo.svg +1 -0
  114. data/docs/benchmark-dashboard/dist/placeholder-user.jpg +0 -0
  115. data/docs/benchmark-dashboard/dist/placeholder.jpg +0 -0
  116. data/docs/benchmark-dashboard/dist/placeholder.svg +1 -0
  117. data/docs/benchmark-dashboard/embed.tsx +13 -0
  118. data/docs/benchmark-dashboard/hooks/use-mobile.tsx +19 -0
  119. data/docs/benchmark-dashboard/hooks/use-toast.ts +194 -0
  120. data/docs/benchmark-dashboard/lib/benchmark-utils.ts +54 -0
  121. data/docs/benchmark-dashboard/lib/utils.ts +6 -0
  122. data/docs/benchmark-dashboard/next.config.mjs +14 -0
  123. data/docs/benchmark-dashboard/package-lock.json +5859 -0
  124. data/docs/benchmark-dashboard/package.json +72 -0
  125. data/docs/benchmark-dashboard/pnpm-lock.yaml +5 -0
  126. data/docs/benchmark-dashboard/postcss.config.mjs +8 -0
  127. data/docs/benchmark-dashboard/styles/globals.css +94 -0
  128. data/docs/benchmark-dashboard/tailwind.config.ts +96 -0
  129. data/docs/benchmark-dashboard/tsconfig.json +27 -0
  130. data/docs/benchmark-dashboard/vite.config.ts +24 -0
  131. data/docs/build.rb +52 -0
  132. data/docs/content/benchmarks/index.md +96 -0
  133. data/docs/content/features/_index.md +1 -1
  134. data/docs/content/getting_started/_index.md +76 -46
  135. data/docs/hugo.yaml +3 -0
  136. data/docs/static/results.json +1 -0
  137. data/docs/static/scripts/benchmark-dashboard.iife.js +211 -0
  138. data/docs/static/styles/benchmark-dashboard.css +1 -0
  139. data/examples/rails_with_static_assets/Gemfile.lock +1 -1
  140. data/examples/rails_with_static_assets/Itsi.rb +4 -1
  141. data/gems/scheduler/Cargo.lock +15 -15
  142. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  143. data/gems/server/Cargo.lock +75 -73
  144. data/gems/server/exe/itsi +6 -1
  145. data/gems/server/lib/itsi/http_request.rb +31 -39
  146. data/gems/server/lib/itsi/http_response.rb +5 -0
  147. data/gems/server/lib/itsi/rack_env_pool.rb +59 -0
  148. data/gems/server/lib/itsi/server/config/config_helpers.rb +1 -2
  149. data/gems/server/lib/itsi/server/config/dsl.rb +5 -4
  150. data/gems/server/lib/itsi/server/config/middleware/etag.md +3 -7
  151. data/gems/server/lib/itsi/server/config/middleware/etag.rb +2 -4
  152. data/gems/server/lib/itsi/server/config/middleware/proxy.rb +1 -1
  153. data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
  154. data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
  155. data/gems/server/lib/itsi/server/config/options/include.rb +5 -2
  156. data/gems/server/lib/itsi/server/config/options/listen_backlog.rb +1 -1
  157. data/gems/server/lib/itsi/server/config/options/pipeline_flush.md +16 -0
  158. data/gems/server/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
  159. data/gems/server/lib/itsi/server/config/options/send_buffer_size.md +15 -0
  160. data/gems/server/lib/itsi/server/config/options/send_buffer_size.rb +19 -0
  161. data/gems/server/lib/itsi/server/config/options/writev.md +25 -0
  162. data/gems/server/lib/itsi/server/config/options/writev.rb +19 -0
  163. data/gems/server/lib/itsi/server/config.rb +43 -31
  164. data/gems/server/lib/itsi/server/default_config/Itsi.rb +1 -4
  165. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +2 -0
  166. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +2 -2
  167. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -1
  168. data/gems/server/lib/itsi/server/rack_interface.rb +17 -12
  169. data/gems/server/lib/itsi/server/route_tester.rb +1 -1
  170. data/gems/server/lib/itsi/server/scheduler_interface.rb +2 -0
  171. data/gems/server/lib/itsi/server/version.rb +1 -1
  172. data/gems/server/lib/itsi/server.rb +1 -0
  173. data/gems/server/lib/ruby_lsp/itsi/addon.rb +12 -13
  174. data/gems/server/test/helpers/test_helper.rb +12 -13
  175. data/gems/server/test/middleware/etag.rb +3 -3
  176. data/gems/server/test/middleware/grpc/grpc.rb +13 -14
  177. data/gems/server/test/middleware/grpc/test_service_impl.rb +3 -3
  178. data/gems/server/test/middleware/proxy.rb +262 -268
  179. data/gems/server/test/options/ruby_thread_request_backlog_size.rb +2 -3
  180. data/lib/itsi/version.rb +1 -1
  181. metadata +99 -6
  182. data/tasks.txt +0 -27
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Itsi
4
+ module RackEnvPool
5
+
6
+ RACK_ENV_TEMPLATE = {
7
+ "SERVER_SOFTWARE" => "Itsi",
8
+ "rack.errors" => $stderr,
9
+ "rack.multithread" => true,
10
+ "rack.multiprocess" => true,
11
+ "rack.run_once" => false,
12
+ "rack.hijack?" => true,
13
+ "rack.multipart.buffer_size" => 16_384,
14
+ "SCRIPT_NAME" => "",
15
+ "REQUEST_METHOD" => "",
16
+ "PATH_INFO" => "",
17
+ "REQUEST_PATH" => "",
18
+ "QUERY_STRING" => "",
19
+ "REMOTE_ADDR" => "",
20
+ "SERVER_PORT" => "",
21
+ "SERVER_NAME" => "",
22
+ "SERVER_PROTOCOL" => "",
23
+ "HTTP_HOST" => "",
24
+ "HTTP_VERSION" => "",
25
+ "itsi.request" => "",
26
+ "itsi.response" => "",
27
+ "rack.version" => nil,
28
+ "rack.url_scheme" => "",
29
+ "rack.input" => "",
30
+ "rack.hijack" => ""
31
+ }.freeze
32
+
33
+ POOL = []
34
+
35
+ def self.checkout # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
36
+ POOL.pop&.tap do |recycled|
37
+ recycled.keys.each do |key|
38
+ case key
39
+ when "SERVER_SOFTWARE" then recycled[key] = "Itsi"
40
+ when "rack.errors" then recycled[key] = $stderr
41
+ when "rack.multithread", "rack.multiprocess", "rack.hijack?" then recycled[key] = true
42
+ when "rack.run_once" then recycled[key] = false
43
+ when "rack.multipart.buffer_size" then recycled[key] = 16_384
44
+ when "SCRIPT_NAME", "REQUEST_METHOD", "PATH_INFO", "REQUEST_PATH", "QUERY_STRING", "REMOTE_ADDR",
45
+ "SERVER_PORT", "SERVER_NAME", "SERVER_PROTOCOL", "HTTP_HOST", "HTTP_VERSION", "itsi.request",
46
+ "itsi.response", "rack.version", "rack.url_scheme", "rack.input", "rack.hijack"
47
+ nil
48
+ else recycled.delete(key)
49
+ end
50
+ end
51
+ end || RACK_ENV_TEMPLATE.dup
52
+ end
53
+
54
+ def self.checkin(env)
55
+ POOL << env
56
+ end
57
+ end
58
+
59
+ end
@@ -97,14 +97,13 @@ module Itsi
97
97
  if !self.class.ancestors.include?(Middleware) && !location.parent.nil?
98
98
  raise "#{opt_name} must be set at the top level"
99
99
  end
100
-
101
100
  @location = location
102
101
  @params = case schema
103
102
  when TypedStruct::Validation
104
103
  schema.validate!(params)
105
104
  when Array
106
105
  default, validation = schema
107
- params ? validation.validate!(params) : default
106
+ !params.nil? ? validation.validate!(params) : default
108
107
  when nil
109
108
  nil
110
109
  else
@@ -55,8 +55,9 @@ module Itsi
55
55
  nested_locations: [],
56
56
  middleware_loader: lambda do
57
57
  @options[:nested_locations].each(&:call)
58
- @middleware[:app] ||= {}
59
- @middleware[:app][:app_proc] = @middleware[:app]&.[](:preloader)&.call || DEFAULT_APP[]
58
+ if !(@middleware[:app] || @middleware[:static_assets])
59
+ @middleware[:app] = { app_proc: DEFAULT_APP[]}
60
+ end
60
61
  [flatten_routes, Config.errors_to_error_lines(errors)]
61
62
  end
62
63
  }
@@ -74,7 +75,7 @@ module Itsi
74
75
  define_method(option_name) do |*args, **kwargs, &blk|
75
76
  option.new(self, *args, **kwargs, &blk).build!
76
77
  rescue Exception => e # rubocop:disable Lint/RescueException
77
- @errors << [e, caller[1]]
78
+ @errors << [e, e.backtrace.find{|r| !(r =~ /server\/config/) }]
78
79
  end
79
80
  end
80
81
 
@@ -85,7 +86,7 @@ module Itsi
85
86
  rescue Config::Endpoint::InvalidHandlerException => e
86
87
  @errors << [e, "#{e.backtrace[0]}:in #{e.message}"]
87
88
  rescue Exception => e # rubocop:disable Lint/RescueException
88
- @errors << [e, caller[1]]
89
+ @errors << [e, e.backtrace.find{|r| !(r =~ /server\/config/) }]
89
90
  end
90
91
  end
91
92
 
@@ -13,8 +13,7 @@ ETags are useful for optimizing client-side caching, conditional GETs, and reduc
13
13
  etag \
14
14
  type: "strong",
15
15
  algorithm: "sha256",
16
- min_body_size: 0,
17
- handle_if_none_match: true
16
+ min_body_size: 0
18
17
  ```
19
18
 
20
19
  ## ETag Applied to a sub-location
@@ -23,8 +22,7 @@ location "/assets" do
23
22
  etag \
24
23
  type: "weak",
25
24
  algorithm: "md5",
26
- min_body_size: 1024,
27
- handle_if_none_match: true
25
+ min_body_size: 1024
28
26
  end
29
27
  ```
30
28
 
@@ -40,12 +38,10 @@ end
40
38
 
41
39
  - **min_body_size**: Minimum response body size (in bytes) required before an ETag is generated. Use this to skip ETags for small or trivial responses.
42
40
 
43
- - **handle_if_none_match**: When `true`, incoming requests with a matching `If-None-Match` header will receive a `304 Not Modified` response (instead of a full body), if the ETag matches the computed value.
44
-
45
41
  ## How It Works
46
42
 
47
43
  ### Before the Response
48
- If `handle_if_none_match` is enabled and the request includes an `If-None-Match` header, the value is stored in the request context for comparison later.
44
+ If the request includes an `If-None-Match` header, the value is stored in the request context for comparison later.
49
45
 
50
46
  ### After the Response
51
47
 
@@ -7,8 +7,7 @@ module Itsi
7
7
  etag \\
8
8
  type: ${1|"strong","weak"|},
9
9
  algorithm: ${2|"sha256","md5"|},
10
- min_body_size: ${3|0,1024|},
11
- handle_if_none_match: ${4|true,false|}
10
+ min_body_size: ${3|0,1024|}
12
11
  SNIPPET
13
12
 
14
13
  detail "Enables ETag generation for the server."
@@ -17,8 +16,7 @@ module Itsi
17
16
  {
18
17
  type: (Enum(["strong", "weak"]) & Required()).default("strong"),
19
18
  algorithm: (Enum(["sha256", "md5"]) & Required()).default("sha256"),
20
- min_body_size: Range(0...1024 ** 3).default(0),
21
- handle_if_none_match: Bool().default(true)
19
+ min_body_size: Range(0...1024 ** 3).default(0)
22
20
  }
23
21
  end
24
22
  end
@@ -7,7 +7,7 @@ module Itsi
7
7
  to: "${1:http://backend.example.com{path_and_query}",
8
8
  backends: [${2:"127.0.0.1:3001", "127.0.0.1:3002"}],
9
9
  backend_priority: ${3|"round_robin","ordered","random"|},
10
- headers: { ${4| "X-Forwarded-For" => { rewrite: "{addr}" },|} },
10
+ headers: { ${4| "X-Forwarded-For" => "{addr}"|} },
11
11
  verify_ssl: ${5|true,false|},
12
12
  timeout: ${6|30,60|},
13
13
  tls_sni: ${7|true,false|},
@@ -24,12 +24,12 @@ module Itsi
24
24
  super(location, params)
25
25
  raise "Rackup file must be a string" unless app.is_a?(String)
26
26
 
27
- @app = Itsi::Server::RackInterface.for(app)
27
+ @app = app
28
28
  end
29
29
 
30
30
  def build!
31
31
  app_args = {
32
- preloader: -> { @app },
32
+ preloader: -> { Itsi::Server::RackInterface.for(@app) },
33
33
  sendfile: @params[:sendfile],
34
34
  nonblocking: @params[:nonblocking],
35
35
  script_name: @params[:script_name],
@@ -14,11 +14,15 @@ module Itsi
14
14
  end
15
15
 
16
16
  def build!
17
+ return if @auto_reloading
18
+ src = caller.find{|l| !(l =~ /lib\/itsi\/server\/config/) }.split(":").first
19
+
17
20
  location.instance_eval do
18
21
  return if @auto_reloading
19
22
 
20
23
  if @included
21
24
  @included.each do |file|
25
+ next if "#{file}.rb" == src
22
26
  if ENV["BUNDLE_BIN_PATH"]
23
27
  watch "#{file}.rb", [%w[bundle exec itsi restart]]
24
28
  else
@@ -29,9 +33,9 @@ module Itsi
29
33
  @auto_reloading = true
30
34
 
31
35
  if ENV["BUNDLE_BIN_PATH"]
32
- watch "Itsi.rb", [%w[bundle exec itsi restart]]
36
+ watch src, [%w[bundle exec itsi restart]]
33
37
  else
34
- watch "Itsi.rb", [%w[itsi restart]]
38
+ watch src, [%w[itsi restart]]
35
39
  end
36
40
  end
37
41
  end
@@ -26,8 +26,11 @@ module Itsi
26
26
  end
27
27
  end
28
28
 
29
- code = IO.read("#{included_file}.rb")
30
- location.instance_eval(code, "#{included_file}.rb", 1)
29
+ filename = File.expand_path("#{included_file}.rb")
30
+
31
+ code = IO.read(filename)
32
+ location.instance_eval(code, filename, 1)
33
+
31
34
  end
32
35
 
33
36
  end
@@ -4,7 +4,7 @@ module Itsi
4
4
  class ListenBacklog < Option
5
5
 
6
6
  insert_text <<~SNIPPET
7
- listen_backlog ${1|262_144,1_048_576|}
7
+ listen_backlog ${1|1024,2048,4096|}
8
8
  SNIPPET
9
9
 
10
10
  detail "Specifies the size of the listen backlog for the socket. Larger backlog sizes can improve performance for high-throughput applications by allowing more pending connections to queue, but may increase memory usage. The default value is 1024."
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: Pipeline Flush
3
+ url: /options/pipeline_flush
4
+ ---
5
+
6
+ Aggregates flushes to better support pipelined responses. (HTTP1 only)
7
+ The default value is `false`.
8
+
9
+ ## Configuration
10
+ ```ruby {filename=Itsi.rb}
11
+ pipeline_flush true
12
+ ```
13
+
14
+ ```ruby {filename=Itsi.rb}
15
+ pipeline_flush false
16
+ ```
@@ -0,0 +1,19 @@
1
+ module Itsi
2
+ class Server
3
+ module Config
4
+ class PipelineFlush < Option
5
+
6
+ insert_text <<~SNIPPET
7
+ pipeline_flush ${1|true,false|}
8
+ SNIPPET
9
+
10
+ detail "Aggregates flushes to better support pipelined responses. (HTTP1 only)."
11
+
12
+ schema do
13
+ (Bool() & Required()).default(false)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Send Buffer Size
3
+ url: /options/send_buffer_size
4
+ ---
5
+
6
+ Configures the size of the send buffer for the socket. Larger buffer sizes can improve performance for high-throughput applications but may increase memory usage. The default value is 262,144 bytes.
7
+
8
+ ## Configuration
9
+ ```ruby {filename=Itsi.rb}
10
+ send_buffer_size 262_144
11
+ ```
12
+
13
+ ```ruby {filename=Itsi.rb}
14
+ send_buffer_size 1_048_576
15
+ ```
@@ -0,0 +1,19 @@
1
+ module Itsi
2
+ class Server
3
+ module Config
4
+ class SendBufferSize < Option
5
+
6
+ insert_text <<~SNIPPET
7
+ send_buffer_size ${1|262_144,1_048_576|}
8
+ SNIPPET
9
+
10
+ detail "Specifies the size of the send buffer for the socket. Larger buffer sizes can improve performance for high-throughput applications but may increase memory usage. The default value is 262,144 bytes."
11
+
12
+ schema do
13
+ (Type(Integer) & Range(1..Float::INFINITY) & Required()).default(262_144)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: Write Vectored
3
+ url: /options/writev
4
+ ---
5
+
6
+ Set whether HTTP/1 connections should try to use vectored writes,
7
+ or always flatten into a single buffer.
8
+
9
+ Note that setting this to false may mean more copies of body data,
10
+ but may also improve performance when an IO transport doesn't
11
+ support vectored writes well, such as most TLS implementations.
12
+
13
+ Setting this to true will force hyper to use queued strategy
14
+ which may eliminate unnecessary cloning on some TLS backends
15
+
16
+ Default is `nil` in which case hyper will try to guess which mode to use
17
+
18
+ ## Configuration
19
+ ```ruby {filename=Itsi.rb}
20
+ writev true
21
+ ```
22
+
23
+ ```ruby {filename=Itsi.rb}
24
+ writev false
25
+ ```
@@ -0,0 +1,19 @@
1
+ module Itsi
2
+ class Server
3
+ module Config
4
+ class Writev < Option
5
+
6
+ insert_text <<~SNIPPET
7
+ writev ${1|true,false|}
8
+ SNIPPET
9
+
10
+ detail "Set whether HTTP/1 connections should try to use vectored writes"
11
+
12
+ schema do
13
+ (Bool() & Required()).default(false)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -41,39 +41,43 @@ module Itsi
41
41
  DSL.evaluate(&builder_proc)
42
42
  elsif args[:static]
43
43
  DSL.evaluate do
44
- location "*" do
45
- rate_limit key: "address", store_config: "in_memory", requests: 2, seconds: 5
46
- etag type: "strong", algorithm: "md5", min_body_size: 1024 * 1024
47
- compress min_size: 1024 * 1024, level: "fastest", algorithms: %w[zstd gzip br deflate],
48
- mime_types: %w[all], compress_streams: true
49
- log_requests before: { level: "INFO", format: "[{request_id}] {method} {path_and_query} - {addr} " },
50
- after: { level: "INFO",
51
- format: "[{request_id}] └─ {status} in {response_time}" }
52
- static_assets \
53
- relative_path: true,
54
- allowed_extensions: [],
55
- root_dir: ".",
56
- not_found_behavior: { error: "not_found" },
57
- auto_index: true,
58
- try_html_extension: true,
59
- max_file_size_in_memory: 1024 * 1024, # 1MB
60
- max_files_in_memory: 1000,
61
- file_check_interval: 1,
62
- serve_hidden_files: false,
63
- headers: {
64
- "X-Content-Type-Options" => "nosniff"
65
- }
66
- end
44
+ rate_limit key: "address", store_config: "in_memory", requests: 5, seconds: 10
45
+ etag type: "strong", algorithm: "md5", min_body_size: 1024 * 1024
46
+ compress min_size: 1024 * 1024, level: "fastest", algorithms: %w[zstd gzip br deflate],
47
+ mime_types: %w[all], compress_streams: true
48
+ log_requests before: { level: "DEBUG", format: "[{request_id}] {method} {path_and_query} - {addr} " },
49
+ after: { level: "DEBUG",
50
+ format: "[{request_id}] └─ {status} in {response_time}" }
51
+ nodelay false
52
+ static_assets \
53
+ relative_path: true,
54
+ allowed_extensions: [],
55
+ root_dir: ".",
56
+ not_found_behavior: { error: "not_found" },
57
+ auto_index: true,
58
+ try_html_extension: true,
59
+ max_file_size_in_memory: 1024 * 1024, # 1MB
60
+ max_files_in_memory: 1000,
61
+ file_check_interval: 1,
62
+ serve_hidden_files: false,
63
+ headers: {
64
+ "X-Content-Type-Options" => "nosniff"
65
+ }
67
66
  end
68
67
  elsif File.exist?(config_file_path.to_s)
69
- DSL.evaluate(config_file_path)
68
+ DSL.evaluate do
69
+ include config_file_path.gsub(".rb", "")
70
+ rackup_file args[:rackup_file], script_name: "/" if args.key?(:rackup_file)
71
+ end
70
72
  elsif File.exist?("./config.ru")
71
73
  DSL.evaluate do
72
74
  preload true
73
- rackup_file args.fetch(:rackup_file, "./config.ru")
75
+ rackup_file args.fetch(:rackup_file, "./config.ru"), script_name: "/"
74
76
  end
75
77
  else
76
- DSL.evaluate {}
78
+ DSL.evaluate do
79
+ rackup_file args[:rackup_file], script_name: "/" if args.key?(:rackup_file)
80
+ end
77
81
  end
78
82
 
79
83
  itsifile_config.transform_keys!(&:to_sym)
@@ -106,9 +110,8 @@ module Itsi
106
110
  Server.write_pid
107
111
  end
108
112
 
109
-
110
113
  srv_config = {
111
- workers: args.fetch(:workers) { itsifile_config.fetch(:workers, 1) },
114
+ workers: args.fetch(:workers) { itsifile_config.fetch(:workers, nil) },
112
115
  worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) },
113
116
  silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) },
114
117
  shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) },
@@ -129,7 +132,7 @@ module Itsi
129
132
  multithreaded_reactor: args.fetch(:multithreaded_reactor) do
130
133
  itsifile_config.fetch(:multithreaded_reactor, nil)
131
134
  end,
132
- pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, true) },
135
+ pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, false) },
133
136
  scheduler_class: args.fetch(:scheduler_class) { itsifile_config.fetch(:scheduler_class, nil) },
134
137
  oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do
135
138
  itsifile_config.fetch(:oob_gc_responses_threshold, nil)
@@ -141,14 +144,21 @@ module Itsi
141
144
  log_format: args.fetch(:log_format) { itsifile_config.fetch(:log_format, nil) },
142
145
  log_target: args.fetch(:log_target) { itsifile_config.fetch(:log_target, nil) },
143
146
  log_target_filters: args.fetch(:log_target_filters) { itsifile_config.fetch(:log_target_filters, nil) },
147
+ pipeline_flush: itsifile_config.fetch(:pipeline_flush, true),
148
+ writev: itsifile_config.fetch(:writev, false),
149
+ max_concurrent_streams: itsifile_config.fetch(:max_concurrent_streams, nil),
150
+ max_local_error_reset_streams: itsifile_config.fetch(:max_local_error_reset_streams, nil),
151
+ max_header_list_size: itsifile_config.fetch(:max_header_list_size, 2 * 1024 * 1024),
152
+ max_send_buf_size: itsifile_config.fetch(:max_send_buf_size, 64 * 1024),
144
153
  binds: args.fetch(:binds) { itsifile_config.fetch(:binds, ["http://0.0.0.0:3000"]) },
145
154
  middleware_loader: middleware_loader,
146
- listeners: args.fetch(:listeners) { nil },
155
+ listeners: args.fetch(:listeners, nil),
147
156
  reuse_address: itsifile_config.fetch(:reuse_address, true),
148
157
  reuse_port: itsifile_config.fetch(:reuse_port, true),
149
158
  listen_backlog: itsifile_config.fetch(:listen_backlog, 1024),
150
159
  nodelay: itsifile_config.fetch(:nodelay, true),
151
- recv_buffer_size: itsifile_config.fetch(:recv_buffer_size, 262_144)
160
+ recv_buffer_size: itsifile_config.fetch(:recv_buffer_size, 262_144),
161
+ send_buffer_size: itsifile_config.fetch(:send_buffer_size, 262_144)
152
162
  }.transform_keys(&:to_s)
153
163
 
154
164
  [srv_config, errors_to_error_lines(errors)]
@@ -223,6 +233,8 @@ module Itsi
223
233
 
224
234
  # Find config file path, if it exists.
225
235
  def self.config_file_path(config_file_path = nil)
236
+ raise "Config file #{config_file_path} does not exist" if config_file_path && !File.exist?(config_file_path)
237
+
226
238
  config_file_path ||= \
227
239
  if File.exist?(ITSI_DEFAULT_CONFIG_FILE)
228
240
  ITSI_DEFAULT_CONFIG_FILE
@@ -10,10 +10,7 @@ env = ENV.fetch("APP_ENV") { ENV.fetch("RACK_ENV", "development") }
10
10
 
11
11
  # Number of worker processes to spawn
12
12
  # If more than 1, Itsi will be booted in Cluster mode
13
- workers ENV.fetch("ITSI_WORKERS") {
14
- require "etc"
15
- env == "development" ? 1 : nil
16
- }
13
+ workers ENV["ITSI_WORKERS"]&.to_i || env == "development" ? 1 : nil
17
14
 
18
15
  # Number of threads to spawn per worker process
19
16
  # For pure CPU bound applicationss, you'll get the best results keeping this number low
@@ -190,6 +190,8 @@ module Itsi
190
190
  end
191
191
  end
192
192
 
193
+ alias_method :each, :each_remote_read
194
+
193
195
  def bidi_streamer?
194
196
  input_stream? && output_stream?
195
197
  end
@@ -79,7 +79,7 @@ module Itsi
79
79
  end
80
80
 
81
81
  def handle_client_streaming(active_call)
82
- response = service.send(active_call.method_name, active_call.each_remote_read, active_call)
82
+ response = service.send(active_call.method_name, active_call)
83
83
  active_call.remote_send(response)
84
84
  end
85
85
 
@@ -94,7 +94,7 @@ module Itsi
94
94
  end
95
95
 
96
96
  def handle_bidi_streaming(active_call)
97
- result = service.send(active_call.method_name, active_call.each_remote_read, active_call) do |response|
97
+ result = service.send(active_call.method_name, active_call.each, active_call) do |response|
98
98
  active_call.remote_send(response)
99
99
  end
100
100
  return unless result.is_a?(Enumerator)
@@ -8,7 +8,9 @@ module Rack
8
8
  port = options.fetch(:Port, 3001)
9
9
  ::Itsi::Server.start(
10
10
  {
11
- binds: ["http://#{host}:#{port}"]
11
+ binds: ["http://#{host}:#{port}"],
12
+ threads: 5,
13
+ workers: 1
12
14
  }
13
15
  ) do
14
16
  run app
@@ -12,7 +12,7 @@ module Itsi
12
12
  end
13
13
  end
14
14
  lambda do |request|
15
- Server.respond(request, app.call(request.to_rack_env))
15
+ Server.respond(request, app.call(env = request.to_rack_env), env)
16
16
  end
17
17
  end
18
18
 
@@ -20,14 +20,14 @@ module Itsi
20
20
  # Here we build the env, and invoke the Rack app's call method.
21
21
  # We then turn the Rack response into something Itsi server understands.
22
22
  def call(app, request)
23
- respond request, app.call(request.to_rack_env)
23
+ respond request, app.call(env = request.to_rack_env), env
24
24
  end
25
25
 
26
26
  # Itsi responses are asynchronous and can be streamed.
27
27
  # Response chunks are sent using response.send_frame
28
28
  # and the response is finished using response.close_write.
29
29
  # If only a single chunk is written, you can use the #send_and_close method.
30
- def respond(request, (status, headers, body))
30
+ def respond(request, (status, headers, body), env)
31
31
  response = request.response
32
32
 
33
33
  # Don't try and respond if we've been hijacked.
@@ -39,13 +39,16 @@ module Itsi
39
39
 
40
40
  # 2. Set Headers
41
41
  body_streamer = streaming_body?(body) ? body : headers.delete("rack.hijack")
42
- headers.each do |key, value|
43
- if value.is_a?(Array)
42
+
43
+ response.reserve_headers(headers.size)
44
+
45
+ for key, value in headers
46
+ case value
47
+ when String then response[key] = value
48
+ when Array
44
49
  value.each do |v|
45
50
  response[key] = v
46
51
  end
47
- elsif value.is_a?(String)
48
- response[key] = value
49
52
  end
50
53
  end
51
54
 
@@ -53,15 +56,15 @@ module Itsi
53
56
  # As soon as we start setting the response
54
57
  # the server will begin to stream it to the client.
55
58
 
56
- # If we're partially hijacked or returned a streaming body,
57
- # stream this response.
58
59
 
59
60
  if body_streamer
61
+ # If we're partially hijacked or returned a streaming body,
62
+ # stream this response.
60
63
  body_streamer.call(response)
61
64
 
62
- # If we're enumerable with more than one chunk
63
- # also stream, otherwise write in a single chunk
64
65
  elsif body.respond_to?(:each) || body.respond_to?(:to_ary)
66
+ # If we're enumerable with more than one chunk
67
+ # also stream, otherwise write in a single chunk
65
68
  unless body.respond_to?(:each)
66
69
  body = body.to_ary
67
70
  raise "Body #to_ary didn't return an array" unless body.is_a?(Array)
@@ -78,8 +81,10 @@ module Itsi
78
81
  else
79
82
  response.send_and_close(body.to_s)
80
83
  end
84
+ rescue EOFError
85
+ response.close
81
86
  ensure
82
- response.close_write
87
+ RackEnvPool.checkin(env)
83
88
  body.close if body.respond_to?(:close)
84
89
  end
85
90
 
@@ -23,7 +23,7 @@ module Itsi
23
23
  when "cors"
24
24
  "\e[33mcors\e[0m(#{mw_args["allow_origins"].join(" ")}, #{mw_args["allow_methods"].join(" ")})"
25
25
  when "etag"
26
- "\e[33metag\e[0m(#{mw_args["type"]}/#{mw_args["algorithm"]}, #{mw_args["handle_if_none_match"] ? "if_none_match" : ""})"
26
+ "\e[33metag\e[0m(#{mw_args["type"]}/#{mw_args["algorithm"]})"
27
27
  when "cache_control"
28
28
  "\e[33mcache_control\e[0m(max_age: #{mw_args["max_age"]}, #{mw_args.select do |_, v|
29
29
  v == true
@@ -14,6 +14,8 @@ module Itsi
14
14
  def schedule(app, request)
15
15
  Fiber.schedule do
16
16
  app.call(request)
17
+ rescue StandardError => e
18
+ request.server_error(e.message)
17
19
  end
18
20
  end
19
21
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.2.15"
5
+ VERSION = "0.2.17"
6
6
  end
7
7
  end
@@ -12,6 +12,7 @@ require_relative "server/rack/handler/itsi"
12
12
  require_relative "server/config"
13
13
  require_relative "server/typed_handlers"
14
14
  require_relative "standard_headers"
15
+ require_relative "rack_env_pool"
15
16
  require_relative "http_request"
16
17
  require_relative "http_response"
17
18
  require_relative "passfile"