itsi 0.2.10 → 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.
- checksums.yaml +4 -4
- data/Cargo.lock +2 -2
- data/LICENSE.txt +169 -0
- data/README.md +8 -0
- data/crates/itsi_error/Cargo.toml +1 -1
- data/crates/itsi_rb_helpers/Cargo.toml +1 -1
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_server/Cargo.toml +1 -1
- data/crates/itsi_server/src/default_responses/mod.rs +3 -0
- data/crates/itsi_server/src/lib.rs +2 -0
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +15 -10
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +10 -2
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +6 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +13 -7
- data/crates/itsi_server/src/server/thread_worker.rs +4 -1
- data/docs/content/features/_index.md +2 -2
- data/docs/content/itsi_scheduler/_index.md +31 -1
- data/examples/hybrid_scheduler_mode/Itsi.rb +60 -0
- data/examples/hybrid_scheduler_mode/config.ru +9 -0
- data/examples/hybrid_scheduler_mode/slow_service/Itsi.rb +12 -0
- data/gems/scheduler/Cargo.lock +1 -1
- data/gems/scheduler/lib/itsi/schedule_refinement.rb +96 -0
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +1 -0
- data/gems/server/Cargo.lock +1 -1
- data/gems/server/lib/itsi/http_request.rb +19 -20
- data/gems/server/lib/itsi/server/config/config_helpers.rb +5 -2
- data/gems/server/lib/itsi/server/config/middleware/endpoint/_index.md +2 -1
- data/gems/server/lib/itsi/server/config/middleware/endpoint/delete.rb +3 -2
- data/gems/server/lib/itsi/server/config/middleware/endpoint/endpoint.rb +14 -3
- data/gems/server/lib/itsi/server/config/middleware/endpoint/get.rb +3 -2
- data/gems/server/lib/itsi/server/config/middleware/endpoint/patch.rb +3 -2
- data/gems/server/lib/itsi/server/config/middleware/endpoint/post.rb +3 -2
- data/gems/server/lib/itsi/server/config/middleware/endpoint/put.rb +3 -2
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.md +18 -0
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +2 -0
- data/gems/server/lib/itsi/server/config/middleware/run.md +20 -1
- data/gems/server/lib/itsi/server/config/middleware/run.rb +2 -0
- data/gems/server/lib/itsi/server/config/options/certificates.md +2 -2
- data/gems/server/lib/itsi/server/config/options/ruby_thread_request_backlog_size.md +18 -0
- data/gems/server/lib/itsi/server/config/options/ruby_thread_request_backlog_size.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/scheduler_threads.md +8 -1
- data/gems/server/lib/itsi/server/config.rb +3 -0
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +2 -2
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -2
- data/gems/server/lib/itsi/server/rack_interface.rb +5 -6
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/test/helpers/test_helper.rb +4 -1
- data/gems/server/test/options/ruby_thread_request_backlog_size.rb +37 -0
- data/gems/server/test/rack/test_rack_server.rb +127 -47
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +3 -2
- metadata +12 -9
- data/gems/scheduler/README.md +0 -76
- data/gems/scheduler/sig/itsi_scheduler.rbs +0 -4
- data/gems/server/README.md +0 -49
- data/gems/server/sig/itsi_server.rbs +0 -4
@@ -0,0 +1,96 @@
|
|
1
|
+
module Itsi
|
2
|
+
module ScheduleRefinement
|
3
|
+
# Useful helper functions for using cooperative multi-tasking in Ruby.
|
4
|
+
# Opt-in to usage by executing `using Itsi::ScheduleRefinement` in any places
|
5
|
+
# you intend to use it.
|
6
|
+
#
|
7
|
+
# After this you can do things like the following
|
8
|
+
#
|
9
|
+
# 1. Launch batch concurrent fire-and-forget jobs.
|
10
|
+
# * 100.times.schedule_each{ sleep 0.1 }
|
11
|
+
#
|
12
|
+
# 2. Launch batch concurrent transofmrs
|
13
|
+
# See how `schedule_map` retains ordering, despite sleeping for randomized amount of time.
|
14
|
+
#
|
15
|
+
# * 100.times.schedule_map{|i| sleep Random.rand(0.0..0.05); i }
|
16
|
+
#
|
17
|
+
# 3. Manually organize fibers to run concurrently.
|
18
|
+
#
|
19
|
+
# require "net/http"
|
20
|
+
# schedule do
|
21
|
+
# req1, req2 = Queue.new, Queue.new
|
22
|
+
# schedule do
|
23
|
+
# puts "Making request 1"
|
24
|
+
# req1 << Net::HTTP.get(URI("http://httpbin.org/get"))
|
25
|
+
# puts "Finished request 1"
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# schedule do
|
29
|
+
# puts "Making request 2"
|
30
|
+
# req2 << Net::HTTP.get(URI("http://httpbin.org/get"))
|
31
|
+
# puts "Finished request 2"
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# res1, res2 = [req1, req2].map(&:pop)
|
35
|
+
# end
|
36
|
+
refine Kernel do
|
37
|
+
private def schedule(&blk) # rubocop:disable Metrics/MethodLength
|
38
|
+
return unless blk
|
39
|
+
|
40
|
+
if Fiber.scheduler.nil?
|
41
|
+
result = nil
|
42
|
+
Thread.new do
|
43
|
+
Fiber.set_scheduler Itsi::Scheduler.new
|
44
|
+
Fiber.schedule { result = blk.call }
|
45
|
+
end.join
|
46
|
+
result
|
47
|
+
else
|
48
|
+
Fiber.schedule(&blk)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module EnumerableExtensions
|
54
|
+
using ScheduleRefinement
|
55
|
+
def schedule_each(&block)
|
56
|
+
enum = Enumerator.new do |y|
|
57
|
+
schedule do
|
58
|
+
each { |item| schedule{ y.yield(item) } }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
block_given? ? enum.each(&block) : enum.each
|
63
|
+
end
|
64
|
+
|
65
|
+
def schedule_map(&block)
|
66
|
+
return Enumerator.new do |y|
|
67
|
+
schedule do
|
68
|
+
with_index.each_with_object([]) do |(item, index), agg|
|
69
|
+
schedule do
|
70
|
+
agg[index] = (y << item)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end.map unless block_given?
|
75
|
+
schedule do
|
76
|
+
with_index.each_with_object([]) do |(item, index), agg|
|
77
|
+
schedule do
|
78
|
+
agg[index] = block[item]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
refine Enumerator do
|
87
|
+
define_method(:schedule_each, EnumerableExtensions.instance_method(:schedule_each))
|
88
|
+
define_method(:schedule_map, EnumerableExtensions.instance_method(:schedule_map))
|
89
|
+
end
|
90
|
+
|
91
|
+
refine Enumerable do
|
92
|
+
define_method(:schedule_each, EnumerableExtensions.instance_method(:schedule_each))
|
93
|
+
define_method(:schedule_map, EnumerableExtensions.instance_method(:schedule_map))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/gems/server/Cargo.lock
CHANGED
@@ -11,7 +11,8 @@ module Itsi
|
|
11
11
|
include ResponseStatusShortcodes
|
12
12
|
attr_accessor :hijacked
|
13
13
|
|
14
|
-
EMPTY_IO = StringIO.new("")
|
14
|
+
EMPTY_IO = StringIO.new("").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
|
15
|
+
|
15
16
|
RACK_HEADER_MAP = StandardHeaders::ALL.map do |header|
|
16
17
|
rack_form = if header == "content-type"
|
17
18
|
"CONTENT_TYPE"
|
@@ -49,9 +50,7 @@ module Itsi
|
|
49
50
|
"rack.version" => nil,
|
50
51
|
"rack.url_scheme" => "",
|
51
52
|
"rack.input" => "",
|
52
|
-
"rack.hijack" => ""
|
53
|
-
"CONTENT_TYPE" => nil,
|
54
|
-
"CONTENT_LENGTH" => nil
|
53
|
+
"rack.hijack" => ""
|
55
54
|
}.freeze
|
56
55
|
|
57
56
|
def to_rack_env
|
@@ -75,20 +74,20 @@ module Itsi
|
|
75
74
|
env["rack.hijack"] = method(:hijack)
|
76
75
|
headers.each do |(k, v)|
|
77
76
|
env[case k
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
77
|
+
when "content-type" then "CONTENT_TYPE"
|
78
|
+
when "content-length" then "CONTENT_LENGTH"
|
79
|
+
when "accept" then "HTTP_ACCEPT"
|
80
|
+
when "accept-encoding" then "HTTP_ACCEPT_ENCODING"
|
81
|
+
when "accept-language" then "HTTP_ACCEPT_LANGUAGE"
|
82
|
+
when "user-agent" then "HTTP_USER_AGENT"
|
83
|
+
when "referer" then "HTTP_REFERER"
|
84
|
+
when "origin" then "HTTP_ORIGIN"
|
85
|
+
when "cookie" then "HTTP_COOKIE"
|
86
|
+
when "authorization" then "HTTP_AUTHORIZATION"
|
87
|
+
when "x-forwarded-for" then "HTTP_X_FORWARDED_FOR"
|
88
|
+
when "x-forwarded-proto" then "HTTP_X_FORWARDED_PROTO"
|
89
|
+
else RACK_HEADER_MAP[k]
|
90
|
+
end
|
92
91
|
] = v
|
93
92
|
end
|
94
93
|
env
|
@@ -154,8 +153,8 @@ module Itsi
|
|
154
153
|
def build_input_io
|
155
154
|
case body_parts
|
156
155
|
when nil then EMPTY_IO
|
157
|
-
when String then StringIO.new(body_parts)
|
158
|
-
when Array then File.open(body_parts.first, "rb")
|
156
|
+
when String then StringIO.new(body_parts).tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
|
157
|
+
when Array then File.open(body_parts.first, "rb").tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
|
159
158
|
else body_parts
|
160
159
|
end
|
161
160
|
end
|
@@ -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
|
-
|
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
|
@@ -5,9 +5,10 @@ prev: deny_list/
|
|
5
5
|
next: controller/
|
6
6
|
---
|
7
7
|
|
8
|
+
> If you're after running a rack app using a fully-featured framework, e.g. a Ruby on Rails or Sinatra, take a look at the [Rackup File](/middleware/rackup_file) middleware instead.
|
9
|
+
|
8
10
|
The **endpoint** middleware allows you to define an ultra light-weight, inline, Ruby endpoint.
|
9
11
|
|
10
|
-
> If you're after running a rack app using a fully-featured framework, e.g. a Ruby on Rails or Sinatra, take a look at the [Rackup File](/middleware/rackup_file) middleware instead.
|
11
12
|
This feature can be useful for quickly prototyping, building small pieces of isolated functionality, or minimal endpoints where high throughput is essential.
|
12
13
|
|
13
14
|
`endpoint` has several variants, that further restrict the endpoint to respond to specific HTTP methods:
|
@@ -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
|
-
{
|
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 = {
|
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
|
@@ -9,11 +9,11 @@ Itsi can automatically generate TLS certificates for you, both in development an
|
|
9
9
|
To automatically generate a TLS certificate in development, just bind using the `https` scheme.
|
10
10
|
E.g.
|
11
11
|
```ruby {filename=Itsi.rb}
|
12
|
-
bind "https://
|
12
|
+
bind "https://0.0.0.0"
|
13
13
|
|
14
14
|
or
|
15
15
|
|
16
|
-
bind "https://
|
16
|
+
bind "https://0.0.0.0:8443"
|
17
17
|
```
|
18
18
|
Itsi will generate a local development CA for you if it does not yet exist, then use this to
|
19
19
|
sign a just-in-time certificate for your server.
|
@@ -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
|
@@ -3,7 +3,7 @@ title: Scheduler Threads
|
|
3
3
|
url: /options/scheduler_threads
|
4
4
|
---
|
5
5
|
|
6
|
-
You can explicitly spawn a pool of non-blocking scheduler threads and divide work across traditional
|
6
|
+
You can explicitly spawn a pool of non-blocking scheduler threads and divide work across traditional/blocking and non-blocking threads, using [location](/middleware/location) blocks.
|
7
7
|
|
8
8
|
This allows you to safely dip your toes into using non-blocking threads for specific I/O heavy operations without having to port an entire application to non-blocking I/O.
|
9
9
|
|
@@ -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) },
|
@@ -31,8 +31,8 @@ fiber_scheduler nil
|
|
31
31
|
|
32
32
|
# If you bind to https, without specifying a certificate, Itsi will use a self-signed certificate.
|
33
33
|
# The self-signed certificate will use a CA generated for your host and stored inside `ITSI_LOCAL_CA_DIR` (Defaults to ~/.itsi)
|
34
|
-
# bind "https://
|
35
|
-
# bind "https://
|
34
|
+
# bind "https://0.0.0.0:3000"
|
35
|
+
# bind "https://0.0.0.0:3000?domains=dev.itsi.fyi"
|
36
36
|
#
|
37
37
|
# If you want to use let's encrypt to generate you a real certificate you and pass cert=acme and an acme_email address to generate one.
|
38
38
|
# bind "https://itsi.fyi?cert=acme&acme_email=admin@itsi.fyi"
|
@@ -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
|
-
|
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
|
|
@@ -3,6 +3,7 @@
|
|
3
3
|
ENV["ITSI_LOG"] = "off"
|
4
4
|
|
5
5
|
require "minitest/reporters"
|
6
|
+
require "rackup"
|
6
7
|
require "itsi/server"
|
7
8
|
require "itsi/scheduler"
|
8
9
|
require "socket"
|
@@ -24,7 +25,9 @@ def free_bind(protocol = "http", unix_socket: false)
|
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
27
|
-
def server(app: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true,
|
28
|
+
def server(app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true,
|
29
|
+
timeout: 5, &blk)
|
30
|
+
app ||= Rack::Lint.new(app_with_lint) if app_with_lint
|
28
31
|
itsi_rb ||= lambda do
|
29
32
|
# Inline Itsi.rb
|
30
33
|
bind bind
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "../helpers/test_helper"
|
2
|
+
|
3
|
+
class TestRubyThreadRequestBacklogSize < Minitest::Test
|
4
|
+
|
5
|
+
def test_ruby_thread_request_backlog_size
|
6
|
+
server(
|
7
|
+
itsi_rb: lambda do
|
8
|
+
threads 1
|
9
|
+
workers 1
|
10
|
+
ruby_thread_request_backlog_size 1
|
11
|
+
get("/foo"){|r| sleep 0.1; r.ok "ok" }
|
12
|
+
end) do
|
13
|
+
responses = 3.times.map{ Thread.new{ get_resp("/foo") } }.map(&:value)
|
14
|
+
|
15
|
+
assert_equal "ok", responses.first.body
|
16
|
+
assert_equal "200", responses.first.code
|
17
|
+
assert_equal "503", responses.last.code
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_ruby_thread_request_backlog_size_default
|
22
|
+
server(
|
23
|
+
itsi_rb: lambda do
|
24
|
+
threads 1
|
25
|
+
workers 1
|
26
|
+
# ruby_thread_request_backlog_size 1 - Disabled. Should revert to more generous default
|
27
|
+
get("/foo"){|r| sleep 0.01; r.ok "ok" }
|
28
|
+
end) do
|
29
|
+
responses = 29.times.map{ Thread.new{ get_resp("/foo") } }.map(&:value)
|
30
|
+
|
31
|
+
assert_equal "ok", responses.first.body
|
32
|
+
assert_equal "200", responses.first.code
|
33
|
+
assert_equal "ok", responses.last.body
|
34
|
+
assert_equal "200", responses.last.code
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|