itsi-server 0.2.11 → 0.2.12

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1 -1
  3. data/ext/itsi_error/Cargo.toml +1 -1
  4. data/ext/itsi_rb_helpers/Cargo.toml +1 -1
  5. data/ext/itsi_scheduler/Cargo.toml +1 -1
  6. data/ext/itsi_server/Cargo.toml +1 -1
  7. data/ext/itsi_server/src/default_responses/mod.rs +3 -0
  8. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +15 -10
  9. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +2 -2
  10. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +6 -0
  11. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +13 -7
  12. data/ext/itsi_server/src/server/thread_worker.rs +4 -1
  13. data/lib/itsi/server/config/config_helpers.rb +5 -2
  14. data/lib/itsi/server/config/middleware/endpoint/delete.rb +3 -2
  15. data/lib/itsi/server/config/middleware/endpoint/endpoint.rb +14 -3
  16. data/lib/itsi/server/config/middleware/endpoint/get.rb +3 -2
  17. data/lib/itsi/server/config/middleware/endpoint/patch.rb +3 -2
  18. data/lib/itsi/server/config/middleware/endpoint/post.rb +3 -2
  19. data/lib/itsi/server/config/middleware/endpoint/put.rb +3 -2
  20. data/lib/itsi/server/config/middleware/rackup_file.md +18 -0
  21. data/lib/itsi/server/config/middleware/rackup_file.rb +2 -0
  22. data/lib/itsi/server/config/middleware/run.md +20 -1
  23. data/lib/itsi/server/config/middleware/run.rb +2 -0
  24. data/lib/itsi/server/config/options/ruby_thread_request_backlog_size.md +18 -0
  25. data/lib/itsi/server/config/options/ruby_thread_request_backlog_size.rb +19 -0
  26. data/lib/itsi/server/config/options/scheduler_threads.md +7 -0
  27. data/lib/itsi/server/config.rb +3 -0
  28. data/lib/itsi/server/rack_interface.rb +5 -6
  29. data/lib/itsi/server/version.rb +1 -1
  30. metadata +3 -2
  31. data/sig/itsi_server.rbs +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb707c4c3974d9d6898c48c24f707931d701a9d06a6f8c921f1ef0bdbf32d372
4
- data.tar.gz: 9bb6bbecd4f4e292b2696a185bff4ff78af30f21f76aaca328df07b27928d9a0
3
+ metadata.gz: 4a7ed672076127c57c4691c730e0a1545edaa1659573652b91e4a5db2bab3f02
4
+ data.tar.gz: 438a6d6e9693ae19080604e642709aa0381cd3486e4f562b175300549cf417f9
5
5
  SHA512:
6
- metadata.gz: 9c37e1ecca3741c856714ce86f2e7865693de22c5add1eef2d93697c894b6d7d50b9e9bb115f637affd61565f77f922c586db4bd7975911fb78c7ac4f4266330
7
- data.tar.gz: fcc796258023503fca0baadfe45e71ddd84210f51a3db5d13b143d94e89b538b15eab74b82c496ad42adaf28787627f27d967724a4fecce0cd7b41126df70bca
6
+ metadata.gz: 7c203ef2a97ed945256f138561359f7c4a32f79417b3caaded682f12a56ff2737b2becfd5e9994e133c547a2e455974e2b1255749ff61ada8e5c8f20aaa95721
7
+ data.tar.gz: 87908e166b10ef36fd3404d5f80cfca03baa5ac12e974947d3e8d9e65b93d73919c0159c0d6d77d62ff33e1eb100dc37ab3bf96fa455e7eec370bc582986c9e7
data/Cargo.lock CHANGED
@@ -1644,7 +1644,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1644
1644
 
1645
1645
  [[package]]
1646
1646
  name = "itsi-server"
1647
- version = "0.2.11"
1647
+ version = "0.2.12"
1648
1648
  dependencies = [
1649
1649
  "argon2",
1650
1650
  "async-channel",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "itsi_error"
3
3
  version = "0.1.0"
4
- edition = "2024"
4
+ edition = "2021"
5
5
 
6
6
  [dependencies]
7
7
  thiserror = "2.0.11"
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "itsi_rb_helpers"
3
3
  version = "0.1.0"
4
- edition = "2024"
4
+ edition = "2021"
5
5
 
6
6
  [dependencies]
7
7
  cfg-if = "1.0.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.11"
3
+ version = "0.2.12"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.11"
3
+ version = "0.2.12"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -9,3 +9,6 @@ pub static NOT_FOUND_RESPONSE: LazyLock<ErrorResponse> = LazyLock::new(ErrorResp
9
9
 
10
10
  pub static INTERNAL_SERVER_ERROR_RESPONSE: LazyLock<ErrorResponse> =
11
11
  LazyLock::new(ErrorResponse::internal_server_error);
12
+
13
+ pub static SERVICE_UNAVAILABLE_RESPONSE: LazyLock<ErrorResponse> =
14
+ LazyLock::new(ErrorResponse::service_unavailable);
@@ -22,6 +22,7 @@ use super::{
22
22
  itsi_http_response::ItsiHttpResponse,
23
23
  };
24
24
  use crate::{
25
+ default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, SERVICE_UNAVAILABLE_RESPONSE},
25
26
  server::{
26
27
  byte_frame::ByteFrame,
27
28
  http_message_types::{HttpRequest, HttpResponse},
@@ -186,16 +187,20 @@ impl ItsiHttpRequest {
186
187
  } else {
187
188
  &context.sender
188
189
  };
189
- match sender
190
- .send(RequestJob::ProcessHttpRequest(request, app))
191
- .await
192
- {
193
- Err(err) => {
194
- error!("Error occurred: {}", err);
195
- let mut response = Response::new(BoxBody::new(Empty::new()));
196
- *response.status_mut() = StatusCode::BAD_REQUEST;
197
- Ok(response)
198
- }
190
+ match sender.try_send(RequestJob::ProcessHttpRequest(request, app)) {
191
+ Err(err) => match err {
192
+ async_channel::TrySendError::Full(_) => {
193
+ Ok(SERVICE_UNAVAILABLE_RESPONSE
194
+ .to_http_response(context.accept.clone())
195
+ .await)
196
+ }
197
+ async_channel::TrySendError::Closed(err) => {
198
+ error!("Error occurred: {:?}", err);
199
+ Ok(INTERNAL_SERVER_ERROR_RESPONSE
200
+ .to_http_response(context.accept.clone())
201
+ .await)
202
+ }
203
+ },
199
204
  _ => match receiver.recv().await {
200
205
  Some(first_frame) => Ok(response
201
206
  .build(first_frame, receiver, shutdown_channel)
@@ -347,7 +347,7 @@ impl ItsiHttpResponse {
347
347
  })?;
348
348
  let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
349
349
  if let Some(ref mut resp) = *self.data.response.write() {
350
- resp.headers_mut().insert(header_name, header_value);
350
+ resp.headers_mut().append(header_name, header_value);
351
351
  }
352
352
  Ok(())
353
353
  }
@@ -364,7 +364,7 @@ impl ItsiHttpResponse {
364
364
  })?;
365
365
  for value in values {
366
366
  let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
367
- headers_mut.insert(&header_name, header_value);
367
+ headers_mut.append(&header_name, header_value);
368
368
  }
369
369
  }
370
370
  }
@@ -69,6 +69,7 @@ pub struct ServerParams {
69
69
  pub pin_worker_cores: bool,
70
70
  pub scheduler_class: Option<String>,
71
71
  pub oob_gc_responses_threshold: Option<u64>,
72
+ pub ruby_thread_request_backlog_size: Option<usize>,
72
73
  pub middleware_loader: HeapValue<Proc>,
73
74
  pub middleware: OnceLock<MiddlewareSet>,
74
75
  pub binds: Vec<Bind>,
@@ -222,6 +223,10 @@ impl ServerParams {
222
223
  let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
223
224
  let oob_gc_responses_threshold: Option<u64> =
224
225
  rb_param_hash.fetch("oob_gc_responses_threshold")?;
226
+
227
+ let ruby_thread_request_backlog_size: Option<usize> =
228
+ rb_param_hash.fetch("ruby_thread_request_backlog_size")?;
229
+
225
230
  let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
226
231
  let log_level: Option<String> = rb_param_hash.fetch("log_level")?;
227
232
  let log_target: Option<String> = rb_param_hash.fetch("log_target")?;
@@ -310,6 +315,7 @@ impl ServerParams {
310
315
  scheduler_threads,
311
316
  streamable_body,
312
317
  scheduler_class,
318
+ ruby_thread_request_backlog_size,
313
319
  oob_gc_responses_threshold,
314
320
  binds,
315
321
  itsi_server_token_preference,
@@ -18,6 +18,7 @@ use std::sync::Arc;
18
18
  pub struct RubyApp {
19
19
  app: Arc<HeapValue<Proc>>,
20
20
  request_type: RequestType,
21
+ script_name: Option<String>,
21
22
  sendfile: bool,
22
23
  nonblocking: bool,
23
24
  base_path: Regex,
@@ -53,6 +54,9 @@ impl RubyApp {
53
54
  let base_path_src = params
54
55
  .funcall::<_, _, String>(Symbol::new("[]"), ("base_path",))
55
56
  .unwrap_or("".to_owned());
57
+ let script_name = params
58
+ .funcall::<_, _, Option<String>>(Symbol::new("[]"), ("script_name",))
59
+ .unwrap_or(None);
56
60
  let base_path = Regex::new(&base_path_src).unwrap();
57
61
 
58
62
  let request_type: RequestType = params
@@ -65,6 +69,7 @@ impl RubyApp {
65
69
  app: Arc::new(app.into()),
66
70
  sendfile,
67
71
  nonblocking,
72
+ script_name,
68
73
  request_type,
69
74
  base_path,
70
75
  }))
@@ -82,13 +87,14 @@ impl MiddlewareLayer for RubyApp {
82
87
  match self.request_type {
83
88
  RequestType::Http => {
84
89
  let uri = req.uri().path();
85
- let script_name = self
86
- .base_path
87
- .captures(uri)
88
- .and_then(|caps| caps.name("base_path"))
89
- .map(|m| m.as_str())
90
- .unwrap_or("/")
91
- .to_owned();
90
+ let script_name = self.script_name.clone().unwrap_or_else(|| {
91
+ self.base_path
92
+ .captures(uri)
93
+ .and_then(|caps| caps.name("base_path"))
94
+ .map(|m| m.as_str())
95
+ .unwrap_or("/")
96
+ .to_owned()
97
+ });
92
98
  ItsiHttpRequest::process_request(
93
99
  self.app.clone(),
94
100
  req,
@@ -65,9 +65,12 @@ type ThreadWorkerBuildResult = Result<(
65
65
  pub fn build_thread_workers(params: Arc<ServerParams>, pid: Pid) -> ThreadWorkerBuildResult {
66
66
  let blocking_thread_count = params.threads;
67
67
  let nonblocking_thread_count = params.scheduler_threads;
68
+ let ruby_thread_request_backlog_size: usize = params
69
+ .ruby_thread_request_backlog_size
70
+ .unwrap_or_else(|| (blocking_thread_count as u16 * 30) as usize);
68
71
 
69
72
  let (blocking_sender, blocking_receiver) =
70
- async_channel::bounded((blocking_thread_count as u16 * 30) as usize);
73
+ async_channel::bounded(ruby_thread_request_backlog_size);
71
74
  let blocking_receiver_ref = Arc::new(blocking_receiver);
72
75
  let blocking_sender_ref = blocking_sender;
73
76
  let scheduler_class = load_scheduler_class(params.scheduler_class.clone())?;
@@ -11,14 +11,17 @@ module Itsi
11
11
  ].flatten
12
12
 
13
13
  listing.each do |file|
14
- current = klass.subclasses
14
+ current = klass.subclasses.dup
15
15
  require file
16
16
  following = klass.subclasses
17
17
  new_class = (following - current).first
18
18
 
19
19
  documentation_file = "#{file[/(.*)\.rb/, 1]}.md"
20
20
  documentation_file = "#{file[%r{(.*)/[^/]+\.rb}, 1]}/_index.md" unless File.exist?(documentation_file)
21
- next unless File.exist?(documentation_file) && new_class
21
+ unless File.exist?(documentation_file) && new_class
22
+ new_class&.documentation "Documentation not found"
23
+ next
24
+ end
22
25
 
23
26
  new_class.documentation IO.read(documentation_file)
24
27
  .gsub(/^---.*?\n.*?-+/m, "") # Strip frontmatter
@@ -26,12 +26,13 @@ module Itsi
26
26
  paths: Array(Or(Type(String), Type(Regexp))),
27
27
  handler: Type(Proc) & Required(),
28
28
  http_methods: Array(Type(String)),
29
+ script_name: Type(String).default(nil),
29
30
  nonblocking: Bool()
30
31
  }
31
32
  end
32
33
 
33
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
34
- location.endpoint(path, handler, http_methods: ["DELETE"], nonblocking: nonblocking, &handler_proc)
34
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc) # rubocop:disable Metrics/ParameterLists,Lint/MissingSuper
35
+ location.endpoint(path, handler, http_methods: ["DELETE"], nonblocking: nonblocking, script_name: script_name, &handler_proc)
35
36
  end
36
37
 
37
38
  def build!
@@ -28,17 +28,24 @@ module Itsi
28
28
  paths: Array(Or(Type(String), Type(Regexp))),
29
29
  handler: Type(Proc) & Required(),
30
30
  http_methods: Array(Type(String)),
31
+ script_name: Type(String).default(nil),
31
32
  nonblocking: Bool()
32
33
  }
33
34
  end
34
35
 
35
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
36
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc)
36
37
  raise "Can not combine a controller method and inline handler" if handler && handler_proc
37
38
  handler_proc = location.controller.method(handler).to_proc if handler.is_a?(Symbol) || handler.is_a?(String)
38
39
 
39
40
  super(
40
41
  location,
41
- { paths: Array(path), handler: handler_proc, http_methods: http_methods, nonblocking: nonblocking }
42
+ {
43
+ paths: Array(path),
44
+ handler: handler_proc,
45
+ http_methods: http_methods,
46
+ nonblocking: nonblocking,
47
+ script_name: script_name
48
+ }
42
49
  )
43
50
 
44
51
  num_required, keywords = Itsi::Server::TypedHandlers::SourceParser.extract_expr_from_source_location(handler_proc)
@@ -75,7 +82,11 @@ module Itsi
75
82
 
76
83
  def build!
77
84
  params = @params
78
- app = { preloader: -> { params[:handler] }, nonblocking: @params[:nonblocking] }
85
+ app = {
86
+ preloader: -> { params[:handler] },
87
+ nonblocking: @params[:nonblocking],
88
+ script_name: @params[:script_name]
89
+ }
79
90
 
80
91
  if @params[:paths] == [""] && @params[:http_methods].empty?
81
92
  location.middleware[:app] = app
@@ -26,12 +26,13 @@ module Itsi
26
26
  paths: Array(Or(Type(String), Type(Regexp))),
27
27
  handler: Type(Proc) & Required(),
28
28
  http_methods: Array(Type(String)),
29
+ script_name: Type(String).default(nil),
29
30
  nonblocking: Bool()
30
31
  }
31
32
  end
32
33
 
33
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
34
- location.endpoint(path, handler, http_methods: ["GET", "HEAD"], nonblocking: nonblocking, &handler_proc)
34
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc) # rubocop:disable Lint/MissingSuper,Metrics/ParameterLists
35
+ location.endpoint(path, handler, http_methods: ["GET", "HEAD"], nonblocking: nonblocking, script_name: script_name, &handler_proc)
35
36
  end
36
37
 
37
38
  def build!
@@ -26,12 +26,13 @@ module Itsi
26
26
  paths: Array(Or(Type(String), Type(Regexp))),
27
27
  handler: Type(Proc) & Required(),
28
28
  http_methods: Array(Type(String)),
29
+ script_name: Type(String).default(nil),
29
30
  nonblocking: Bool()
30
31
  }
31
32
  end
32
33
 
33
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
34
- location.endpoint(path, handler, http_methods: ["PATCH"], nonblocking: nonblocking, &handler_proc)
34
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc) # rubocop:disable Lint/MissingSuper
35
+ location.endpoint(path, handler, http_methods: ["PATCH"], nonblocking: nonblocking, script_name: script_name, &handler_proc)
35
36
  end
36
37
 
37
38
  def build!
@@ -26,12 +26,13 @@ module Itsi
26
26
  paths: Array(Or(Type(String), Type(Regexp))),
27
27
  handler: Type(Proc) & Required(),
28
28
  http_methods: Array(Type(String)),
29
+ script_name: Type(String).default(nil),
29
30
  nonblocking: Bool()
30
31
  }
31
32
  end
32
33
 
33
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
34
- location.endpoint(path, handler, http_methods: ["POST"], nonblocking: nonblocking, &handler_proc)
34
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc) # rubocop:disable Lint/MissingSuper
35
+ location.endpoint(path, handler, http_methods: ["POST"], nonblocking: nonblocking, script_name: script_name, &handler_proc)
35
36
  end
36
37
 
37
38
  def build!
@@ -26,12 +26,13 @@ module Itsi
26
26
  paths: Array(Or(Type(String), Type(Regexp))),
27
27
  handler: Type(Proc) & Required(),
28
28
  http_methods: Array(Type(String)),
29
+ script_name: Type(String).default(nil),
29
30
  nonblocking: Bool()
30
31
  }
31
32
  end
32
33
 
33
- def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, &handler_proc)
34
- location.endpoint(path, handler, http_methods: ["PUT"], nonblocking: nonblocking, &handler_proc)
34
+ def initialize(location, path="", handler=nil, http_methods: [], nonblocking: false, script_name: nil, &handler_proc) # rubocop:disable Lint/MissingSuper,Metrics/ParameterLists
35
+ location.endpoint(path, handler, http_methods: ["PUT"], nonblocking: nonblocking, script_name: script_name, &handler_proc)
35
36
  end
36
37
 
37
38
  def build!
@@ -44,6 +44,24 @@ $ curl http://0.0.0.0:3000/root/child_path
44
44
  :/root/child_path
45
45
  ```
46
46
 
47
+ ### `SCRIPT_NAME` and `PATH_INFO` Rack ENV variables.
48
+ Rack applications mounted at a subpath will, by default, receive a `SCRIPT_NAME` value that includes the subpath at which the app is mounted, and a `PATH_INFO` value that is relative to the subpath at which the rack app is mounted.
49
+ If you wish to use location blocks only to control the middleware that applies to a rack app, but still have it behave as if it were mounted elsewhere (e.g. at the root), you can explicitly set the `script_name` option.
50
+ E.g.
51
+
52
+ ```ruby
53
+ location "/subpath/*" do
54
+ rackup_file "config.ru", script_name: '/'
55
+ end
56
+ ```
57
+
58
+ ```bash
59
+ # Our script-name override kicks in here, even though the app is mounted under `/subpath`
60
+ $ curl http://0.0.0.0:3000/subpath/child_path
61
+ /:/subpath/child_path
62
+ ```
63
+
64
+
47
65
  ### Options
48
66
  * `nonblocking` (default false). Determines whether requests sent to this Rack application should be run on non-blocking threads. Only applies if running in hybrid (non-blocking and blocking thread pool) mode. Otherwise this is a no-op and will run in whatever mode is set globally.
49
67
  * `sendfile` (default true). Determines whether Itsi should respect the `X-Sendfile` header set by the Rack application and use the `sendfile` function to efficiently send files. (Despite the name, this does not use the OS-level `sendfile` system call). Note. Itsi enforces the restriction that the referenced file must be within a child directory of the application root.
@@ -15,6 +15,7 @@ module Itsi
15
15
  schema do
16
16
  {
17
17
  nonblocking: Bool().default(false),
18
+ script_name: Type(String).default(nil),
18
19
  sendfile: Bool().default(true)
19
20
  }
20
21
  end
@@ -31,6 +32,7 @@ module Itsi
31
32
  preloader: -> { @app },
32
33
  sendfile: @params[:sendfile],
33
34
  nonblocking: @params[:nonblocking],
35
+ script_name: @params[:script_name],
34
36
  base_path: "^(?<base_path>#{location.paths_from_parent.gsub(/\.\*\)$/, ")")}).*$"
35
37
  }
36
38
  location.middleware[:app] = app_args
@@ -39,7 +39,6 @@ run(Rack::Builder.app do
39
39
  use Rack::CommonLogger
40
40
  run ->(env) { [200, { 'content-type' => 'text/plain' }, [[env['SCRIPT_NAME'], env['PATH_INFO']].join(":") ] ] }
41
41
  end)
42
-
43
42
  ```
44
43
 
45
44
  ```bash
@@ -50,6 +49,26 @@ $ curl http://0.0.0.0:3000/root/child_path
50
49
  :/root/child_path
51
50
  ```
52
51
 
52
+ ### `SCRIPT_NAME` and `PATH_INFO` Rack ENV variables.
53
+ Rack applications mounted at a subpath will, by default, receive a `SCRIPT_NAME` value that includes the subpath at which the app is mounted, and a `PATH_INFO` value that is relative to the subpath at which the rack app is mounted.
54
+ If you wish to use location blocks only to control the middleware that applies to a rack app, but still have it behave as if it were mounted elsewhere (e.g. at the root), you can explicitly set the `script_name` option.
55
+ E.g.
56
+
57
+ ```ruby
58
+ location "/subpath/*" do
59
+ run(Rack::Builder.app do
60
+ use Rack::CommonLogger
61
+ run ->(env) { [200, { 'content-type' => 'text/plain' }, [[env['SCRIPT_NAME'], env['PATH_INFO']].join(":") ] ] }
62
+ end, script_name: '/')
63
+ end
64
+ ```
65
+
66
+ ```bash
67
+ # Our script-name override kicks in here, even though the app is mounted under `/subpath`
68
+ $ curl http://0.0.0.0:3000/subpath/child_path
69
+ /:/subpath/child_path
70
+ ```
71
+
53
72
  ### Options
54
73
  * `nonblocking` (default false). Determines whether requests sent to this Rack application should be run on non-blocking threads. Only applies if running in hybrid (non-blocking and blocking thread pool) mode. Otherwise this is a no-op and will run in whatever mode is set globally.
55
74
  * `sendfile` (default true). Determines whether Itsi should respect the `X-Sendfile` header set by the Rack application and use the `sendfile` function to efficiently send files. (Despite the name, this does not use the OS-level `sendfile` system call). Note. Itsi enforces the restriction that the referenced file must be within a child directory of the application root.
@@ -15,6 +15,7 @@ module Itsi
15
15
  schema do
16
16
  {
17
17
  nonblocking: Bool().default(false),
18
+ script_name: Type(String).default(nil),
18
19
  sendfile: Bool().default(true)
19
20
  }
20
21
  end
@@ -30,6 +31,7 @@ module Itsi
30
31
  preloader: -> { Itsi::Server::RackInterface.for(@app) },
31
32
  sendfile: @params[:sendfile],
32
33
  nonblocking: @params[:nonblocking],
34
+ script_name: @params[:script_name],
33
35
  base_path: "^(?<base_path>#{location.paths_from_parent.gsub(/\.\*\)$/, ')')}).*$"
34
36
  }
35
37
  location.middleware[:app] = app_args
@@ -0,0 +1,18 @@
1
+ ---
2
+ title: Ruby Thread Request Backlog Size
3
+ url: /options/ruby_thread_request_backlog_size
4
+ ---
5
+
6
+ Configures the size of the backlog queue for incoming requests in the Ruby thread pool.
7
+ Up to this many requests can be queued at once before the server rejects further requests to Ruby workers (note this does not block other requests to proxied hosts or for static assets).
8
+
9
+ The default value is `30 x number of threads`.
10
+
11
+ ## Configuration
12
+ ```ruby {filename=Itsi.rb}
13
+ ruby_thread_request_backlog_size 20
14
+ ```
15
+
16
+ ```ruby {filename=Itsi.rb}
17
+ ruby_thread_request_backlog_size 100
18
+ ```
@@ -0,0 +1,19 @@
1
+ module Itsi
2
+ class Server
3
+ module Config
4
+ class RubyThreadRequestBacklogSize < Option
5
+
6
+ insert_text <<~SNIPPET
7
+ ruby_thread_request_backlog_size ${1|10,25,50,100|}
8
+ SNIPPET
9
+
10
+ detail "The maximum number of requests that can be queued for processing by the Ruby thread."
11
+
12
+ schema do
13
+ (Type(Integer)).default(nil)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -25,10 +25,17 @@ fiber_scheduler true
25
25
  # We mount the same app *twice*.
26
26
  # For a specific route prefix, all requests will be sent to non blocking threads.
27
27
  # All others fall through to the default mount
28
+
28
29
  location "/heavy_io/*" do
30
+ # You can optionally use the `script_name: ""` option here to set the base path for the mounted app (useful if a nested app
31
+ # should still serve requests as if it was mounted at the root).
32
+ # Otherwise it will infer the script-name based on the parent location block.
29
33
  rackup_file "./config.ru", nonblocking: true
30
34
  end
31
35
 
32
36
  rackup_file "./config.ru"
33
37
 
34
38
  ```
39
+ ## Examples.
40
+
41
+ See [https://github.com/wouterken/itsi/tree/main/examples/hybrid_scheduler_mode](hybrid_scheduler_mode) example in the Git repository.
@@ -134,6 +134,9 @@ module Itsi
134
134
  oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do
135
135
  itsifile_config.fetch(:oob_gc_responses_threshold, nil)
136
136
  end,
137
+ ruby_thread_request_backlog_size: args.fetch(:ruby_thread_request_backlog_size) do
138
+ itsifile_config.fetch(:ruby_thread_request_backlog_size, nil)
139
+ end,
137
140
  log_level: args.fetch(:log_level) { itsifile_config.fetch(:log_level, nil) },
138
141
  log_format: args.fetch(:log_format) { itsifile_config.fetch(:log_format, nil) },
139
142
  log_target: args.fetch(:log_target) { itsifile_config.fetch(:log_target, nil) },
@@ -40,13 +40,12 @@ module Itsi
40
40
  # 2. Set Headers
41
41
  body_streamer = streaming_body?(body) ? body : headers.delete("rack.hijack")
42
42
  headers.each do |key, value|
43
- unless value.is_a?(Array)
43
+ if value.is_a?(Array)
44
+ value.each do |v|
45
+ response[key] = v
46
+ end
47
+ elsif value.is_a?(String)
44
48
  response[key] = value
45
- next
46
- end
47
-
48
- value.each do |v|
49
- response[key] = v
50
49
  end
51
50
  end
52
51
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.2.11"
5
+ VERSION = "0.2.12"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi-server
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
@@ -497,6 +497,8 @@ files:
497
497
  - lib/itsi/server/config/options/reuse_address.rb
498
498
  - lib/itsi/server/config/options/reuse_port.md
499
499
  - lib/itsi/server/config/options/reuse_port.rb
500
+ - lib/itsi/server/config/options/ruby_thread_request_backlog_size.md
501
+ - lib/itsi/server/config/options/ruby_thread_request_backlog_size.rb
500
502
  - lib/itsi/server/config/options/scheduler_threads.md
501
503
  - lib/itsi/server/config/options/scheduler_threads.rb
502
504
  - lib/itsi/server/config/options/shutdown_timeout.md
@@ -532,7 +534,6 @@ files:
532
534
  - lib/itsi/standard_headers.rb
533
535
  - lib/ruby_lsp/itsi/addon.rb
534
536
  - lib/shell_completions/completions.rb
535
- - sig/itsi_server.rbs
536
537
  homepage: https://itsi.fyi
537
538
  licenses:
538
539
  - MIT
data/sig/itsi_server.rbs DELETED
@@ -1,4 +0,0 @@
1
- module ItsiServer
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end