itsi 0.2.16 → 0.2.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.
- checksums.yaml +4 -4
- data/.zed/settings.json +32 -0
- data/CHANGELOG.md +21 -0
- data/Cargo.lock +4 -2
- data/crates/itsi_acme/Cargo.toml +1 -1
- data/crates/itsi_scheduler/Cargo.toml +1 -1
- data/crates/itsi_server/Cargo.toml +3 -1
- data/crates/itsi_server/src/lib.rs +6 -1
- data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
- data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
- data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
- data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +422 -110
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +62 -15
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/crates/itsi_server/src/server/binds/listener.rs +45 -7
- data/crates/itsi_server/src/server/frame_stream.rs +142 -0
- data/crates/itsi_server/src/server/http_message_types.rs +142 -9
- data/crates/itsi_server/src/server/io_stream.rs +28 -5
- data/crates/itsi_server/src/server/lifecycle_event.rs +1 -1
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
- data/crates/itsi_server/src/server/mod.rs +1 -0
- data/crates/itsi_server/src/server/process_worker.rs +3 -4
- data/crates/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +83 -31
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +166 -142
- data/crates/itsi_server/src/server/signal.rs +37 -9
- data/crates/itsi_server/src/server/thread_worker.rs +84 -69
- data/crates/itsi_server/src/services/itsi_http_service.rs +43 -43
- data/crates/itsi_server/src/services/static_file_server.rs +28 -47
- data/docs/benchmark-dashboard/.gitignore +27 -0
- data/docs/benchmark-dashboard/app/api/benchmarks/route.ts +22 -0
- data/docs/benchmark-dashboard/app/globals.css +94 -0
- data/docs/benchmark-dashboard/app/layout.tsx +20 -0
- data/docs/benchmark-dashboard/app/page.tsx +252 -0
- data/docs/benchmark-dashboard/components/benchmark-dashboard.tsx +1663 -0
- data/docs/benchmark-dashboard/components/theme-provider.tsx +11 -0
- data/docs/benchmark-dashboard/components/ui/accordion.tsx +58 -0
- data/docs/benchmark-dashboard/components/ui/alert-dialog.tsx +141 -0
- data/docs/benchmark-dashboard/components/ui/alert.tsx +59 -0
- data/docs/benchmark-dashboard/components/ui/aspect-ratio.tsx +7 -0
- data/docs/benchmark-dashboard/components/ui/avatar.tsx +50 -0
- data/docs/benchmark-dashboard/components/ui/badge.tsx +36 -0
- data/docs/benchmark-dashboard/components/ui/breadcrumb.tsx +115 -0
- data/docs/benchmark-dashboard/components/ui/button.tsx +56 -0
- data/docs/benchmark-dashboard/components/ui/calendar.tsx +66 -0
- data/docs/benchmark-dashboard/components/ui/card.tsx +79 -0
- data/docs/benchmark-dashboard/components/ui/carousel.tsx +262 -0
- data/docs/benchmark-dashboard/components/ui/chart.tsx +365 -0
- data/docs/benchmark-dashboard/components/ui/checkbox.tsx +30 -0
- data/docs/benchmark-dashboard/components/ui/collapsible.tsx +11 -0
- data/docs/benchmark-dashboard/components/ui/command.tsx +153 -0
- data/docs/benchmark-dashboard/components/ui/context-menu.tsx +200 -0
- data/docs/benchmark-dashboard/components/ui/dialog.tsx +122 -0
- data/docs/benchmark-dashboard/components/ui/drawer.tsx +118 -0
- data/docs/benchmark-dashboard/components/ui/dropdown-menu.tsx +200 -0
- data/docs/benchmark-dashboard/components/ui/form.tsx +178 -0
- data/docs/benchmark-dashboard/components/ui/hover-card.tsx +29 -0
- data/docs/benchmark-dashboard/components/ui/input-otp.tsx +71 -0
- data/docs/benchmark-dashboard/components/ui/input.tsx +22 -0
- data/docs/benchmark-dashboard/components/ui/label.tsx +26 -0
- data/docs/benchmark-dashboard/components/ui/loading-spinner.tsx +12 -0
- data/docs/benchmark-dashboard/components/ui/menubar.tsx +236 -0
- data/docs/benchmark-dashboard/components/ui/navigation-menu.tsx +128 -0
- data/docs/benchmark-dashboard/components/ui/pagination.tsx +117 -0
- data/docs/benchmark-dashboard/components/ui/popover.tsx +31 -0
- data/docs/benchmark-dashboard/components/ui/progress.tsx +28 -0
- data/docs/benchmark-dashboard/components/ui/radio-group.tsx +44 -0
- data/docs/benchmark-dashboard/components/ui/resizable.tsx +45 -0
- data/docs/benchmark-dashboard/components/ui/scroll-area.tsx +48 -0
- data/docs/benchmark-dashboard/components/ui/select.tsx +160 -0
- data/docs/benchmark-dashboard/components/ui/separator.tsx +31 -0
- data/docs/benchmark-dashboard/components/ui/sheet.tsx +140 -0
- data/docs/benchmark-dashboard/components/ui/sidebar.tsx +763 -0
- data/docs/benchmark-dashboard/components/ui/skeleton.tsx +15 -0
- data/docs/benchmark-dashboard/components/ui/slider.tsx +28 -0
- data/docs/benchmark-dashboard/components/ui/sonner.tsx +31 -0
- data/docs/benchmark-dashboard/components/ui/switch.tsx +29 -0
- data/docs/benchmark-dashboard/components/ui/table.tsx +117 -0
- data/docs/benchmark-dashboard/components/ui/tabs.tsx +55 -0
- data/docs/benchmark-dashboard/components/ui/textarea.tsx +22 -0
- data/docs/benchmark-dashboard/components/ui/toast.tsx +129 -0
- data/docs/benchmark-dashboard/components/ui/toaster.tsx +35 -0
- data/docs/benchmark-dashboard/components/ui/toggle-group.tsx +61 -0
- data/docs/benchmark-dashboard/components/ui/toggle.tsx +45 -0
- data/docs/benchmark-dashboard/components/ui/tooltip.tsx +30 -0
- data/docs/benchmark-dashboard/components/ui/use-mobile.tsx +19 -0
- data/docs/benchmark-dashboard/components/ui/use-toast.ts +194 -0
- data/docs/benchmark-dashboard/components.json +21 -0
- data/docs/benchmark-dashboard/dist/benchmark-dashboard.css +1 -0
- data/docs/benchmark-dashboard/dist/benchmark-dashboard.iife.js +211 -0
- data/docs/benchmark-dashboard/dist/placeholder-logo.png +0 -0
- data/docs/benchmark-dashboard/dist/placeholder-logo.svg +1 -0
- data/docs/benchmark-dashboard/dist/placeholder-user.jpg +0 -0
- data/docs/benchmark-dashboard/dist/placeholder.jpg +0 -0
- data/docs/benchmark-dashboard/dist/placeholder.svg +1 -0
- data/docs/benchmark-dashboard/embed.tsx +13 -0
- data/docs/benchmark-dashboard/hooks/use-mobile.tsx +19 -0
- data/docs/benchmark-dashboard/hooks/use-toast.ts +194 -0
- data/docs/benchmark-dashboard/lib/benchmark-utils.ts +54 -0
- data/docs/benchmark-dashboard/lib/utils.ts +6 -0
- data/docs/benchmark-dashboard/next.config.mjs +14 -0
- data/docs/benchmark-dashboard/package-lock.json +5859 -0
- data/docs/benchmark-dashboard/package.json +72 -0
- data/docs/benchmark-dashboard/pnpm-lock.yaml +5 -0
- data/docs/benchmark-dashboard/postcss.config.mjs +8 -0
- data/docs/benchmark-dashboard/styles/globals.css +94 -0
- data/docs/benchmark-dashboard/tailwind.config.ts +96 -0
- data/docs/benchmark-dashboard/tsconfig.json +27 -0
- data/docs/benchmark-dashboard/vite.config.ts +24 -0
- data/docs/build.rb +52 -0
- data/docs/content/acknowledgements/_index.md +1 -1
- data/docs/content/benchmarks/index.md +96 -0
- data/docs/content/configuration/_index.md +2 -2
- data/docs/content/getting_started/_index.md +76 -46
- data/docs/content/itsi_scheduler/_index.md +2 -2
- data/docs/hugo.yaml +10 -1
- data/docs/static/results.json +1 -0
- data/docs/static/scripts/benchmark-dashboard.iife.js +211 -0
- data/docs/static/styles/benchmark-dashboard.css +1 -0
- data/examples/api_with_schema_and_controllers/README.md +1 -1
- data/gems/scheduler/Cargo.lock +1 -1
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/server/Cargo.lock +3 -1
- data/gems/server/exe/itsi +8 -5
- data/gems/server/lib/itsi/http_request.rb +31 -39
- data/gems/server/lib/itsi/http_response.rb +5 -0
- data/gems/server/lib/itsi/rack_env_pool.rb +59 -0
- data/gems/server/lib/itsi/server/config/dsl.rb +6 -4
- data/gems/server/lib/itsi/server/config/middleware/compression.md +3 -3
- data/gems/server/lib/itsi/server/config/middleware/endpoint/controller.md +1 -1
- data/gems/server/lib/itsi/server/config/middleware/proxy.md +2 -2
- data/gems/server/lib/itsi/server/config/middleware/proxy.rb +1 -1
- data/gems/server/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
- data/gems/server/lib/itsi/server/config/options/auto_reload_config.rb +11 -6
- data/gems/server/lib/itsi/server/config/options/include.md +1 -0
- data/gems/server/lib/itsi/server/config/options/include.rb +13 -8
- data/gems/server/lib/itsi/server/config/options/pipeline_flush.md +16 -0
- data/gems/server/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
- data/gems/server/lib/itsi/server/config/options/reuse_port.rb +2 -4
- data/gems/server/lib/itsi/server/config/options/writev.md +25 -0
- data/gems/server/lib/itsi/server/config/options/writev.rb +19 -0
- data/gems/server/lib/itsi/server/config.rb +22 -9
- data/gems/server/lib/itsi/server/default_config/Itsi.rb +9 -8
- data/gems/server/lib/itsi/server/grpc/grpc_call.rb +2 -0
- data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +2 -2
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -1
- data/gems/server/lib/itsi/server/rack_interface.rb +17 -12
- data/gems/server/lib/itsi/server/scheduler_interface.rb +2 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +1 -0
- data/gems/server/lib/ruby_lsp/itsi/addon.rb +12 -13
- data/gems/server/test/helpers/test_helper.rb +12 -13
- data/gems/server/test/middleware/grpc/grpc.rb +13 -14
- data/gems/server/test/middleware/grpc/test_service_impl.rb +3 -3
- data/gems/server/test/middleware/proxy.rb +262 -268
- data/lib/itsi/version.rb +1 -1
- metadata +97 -6
- data/tasks.txt +0 -28
@@ -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
|
@@ -2,17 +2,15 @@ module Itsi
|
|
2
2
|
class Server
|
3
3
|
module Config
|
4
4
|
class ReusePort < Option
|
5
|
-
|
6
5
|
insert_text <<~SNIPPET
|
7
|
-
|
6
|
+
reuse_port ${1|true,false|}
|
8
7
|
SNIPPET
|
9
8
|
|
10
9
|
detail "Configures whether the server should set the reuse_port option on the underlying socket."
|
11
10
|
|
12
11
|
schema do
|
13
|
-
(Bool() & Required()).default(
|
12
|
+
(Bool() & Required()).default(true)
|
14
13
|
end
|
15
|
-
|
16
14
|
end
|
17
15
|
end
|
18
16
|
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
|
@@ -44,9 +44,9 @@ module Itsi
|
|
44
44
|
rate_limit key: "address", store_config: "in_memory", requests: 5, seconds: 10
|
45
45
|
etag type: "strong", algorithm: "md5", min_body_size: 1024 * 1024
|
46
46
|
compress min_size: 1024 * 1024, level: "fastest", algorithms: %w[zstd gzip br deflate],
|
47
|
-
|
47
|
+
mime_types: %w[all], compress_streams: true
|
48
48
|
log_requests before: { level: "DEBUG", format: "[{request_id}] {method} {path_and_query} - {addr} " },
|
49
|
-
|
49
|
+
after: { level: "DEBUG",
|
50
50
|
format: "[{request_id}] └─ {status} in {response_time}" }
|
51
51
|
nodelay false
|
52
52
|
static_assets \
|
@@ -65,14 +65,19 @@ module Itsi
|
|
65
65
|
}
|
66
66
|
end
|
67
67
|
elsif File.exist?(config_file_path.to_s)
|
68
|
-
DSL.evaluate
|
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
|
69
72
|
elsif File.exist?("./config.ru")
|
70
73
|
DSL.evaluate do
|
71
74
|
preload true
|
72
|
-
rackup_file args.fetch(:rackup_file, "./config.ru")
|
75
|
+
rackup_file args.fetch(:rackup_file, "./config.ru"), script_name: "/"
|
73
76
|
end
|
74
77
|
else
|
75
|
-
DSL.evaluate
|
78
|
+
DSL.evaluate do
|
79
|
+
rackup_file args[:rackup_file], script_name: "/" if args.key?(:rackup_file)
|
80
|
+
end
|
76
81
|
end
|
77
82
|
|
78
83
|
itsifile_config.transform_keys!(&:to_sym)
|
@@ -92,7 +97,7 @@ module Itsi
|
|
92
97
|
errors << [e, e.backtrace[0]]
|
93
98
|
end
|
94
99
|
# If we're just preloading a specific gem group, we'll do that here too
|
95
|
-
when Symbol
|
100
|
+
when Symbol, String
|
96
101
|
Itsi.log_debug("Preloading gem group #{preload}")
|
97
102
|
Bundler.require(preload)
|
98
103
|
end
|
@@ -106,7 +111,7 @@ module Itsi
|
|
106
111
|
end
|
107
112
|
|
108
113
|
srv_config = {
|
109
|
-
workers: args.fetch(:workers) { itsifile_config.fetch(:workers,
|
114
|
+
workers: args.fetch(:workers) { itsifile_config.fetch(:workers, nil) },
|
110
115
|
worker_memory_limit: args.fetch(:worker_memory_limit) { itsifile_config.fetch(:worker_memory_limit, nil) },
|
111
116
|
silence: args.fetch(:silence) { itsifile_config.fetch(:silence, false) },
|
112
117
|
shutdown_timeout: args.fetch(:shutdown_timeout) { itsifile_config.fetch(:shutdown_timeout, 5) },
|
@@ -127,7 +132,7 @@ module Itsi
|
|
127
132
|
multithreaded_reactor: args.fetch(:multithreaded_reactor) do
|
128
133
|
itsifile_config.fetch(:multithreaded_reactor, nil)
|
129
134
|
end,
|
130
|
-
pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores,
|
135
|
+
pin_worker_cores: args.fetch(:pin_worker_cores) { itsifile_config.fetch(:pin_worker_cores, false) },
|
131
136
|
scheduler_class: args.fetch(:scheduler_class) { itsifile_config.fetch(:scheduler_class, nil) },
|
132
137
|
oob_gc_responses_threshold: args.fetch(:oob_gc_responses_threshold) do
|
133
138
|
itsifile_config.fetch(:oob_gc_responses_threshold, nil)
|
@@ -139,9 +144,15 @@ module Itsi
|
|
139
144
|
log_format: args.fetch(:log_format) { itsifile_config.fetch(:log_format, nil) },
|
140
145
|
log_target: args.fetch(:log_target) { itsifile_config.fetch(:log_target, nil) },
|
141
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),
|
142
153
|
binds: args.fetch(:binds) { itsifile_config.fetch(:binds, ["http://0.0.0.0:3000"]) },
|
143
154
|
middleware_loader: middleware_loader,
|
144
|
-
listeners: args.fetch(:listeners
|
155
|
+
listeners: args.fetch(:listeners, nil),
|
145
156
|
reuse_address: itsifile_config.fetch(:reuse_address, true),
|
146
157
|
reuse_port: itsifile_config.fetch(:reuse_port, true),
|
147
158
|
listen_backlog: itsifile_config.fetch(:listen_backlog, 1024),
|
@@ -222,6 +233,8 @@ module Itsi
|
|
222
233
|
|
223
234
|
# Find config file path, if it exists.
|
224
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
|
+
|
225
238
|
config_file_path ||= \
|
226
239
|
if File.exist?(ITSI_DEFAULT_CONFIG_FILE)
|
227
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
|
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
|
@@ -30,11 +27,13 @@ threads ENV.fetch("ITSI_THREADS", 3)
|
|
30
27
|
fiber_scheduler nil
|
31
28
|
|
32
29
|
# If you bind to https, without specifying a certificate, Itsi will use a self-signed certificate.
|
33
|
-
# The self-signed certificate will use a CA generated for your
|
30
|
+
# The self-signed certificate will use a CA generated for your
|
31
|
+
# host and stored inside `ITSI_LOCAL_CA_DIR` (Defaults to ~/.itsi)
|
34
32
|
# bind "https://0.0.0.0:3000"
|
35
33
|
# bind "https://0.0.0.0:3000?domains=dev.itsi.fyi"
|
36
34
|
#
|
37
|
-
# If you want to use let's encrypt to generate you a real certificate you
|
35
|
+
# If you want to use let's encrypt to generate you a real certificate you
|
36
|
+
# and pass cert=acme and an acme_email address to generate one.
|
38
37
|
# bind "https://itsi.fyi?cert=acme&acme_email=admin@itsi.fyi"
|
39
38
|
# You can generate certificates for multiple domains at once, by passing a comma-separated list of domains
|
40
39
|
# bind "https://0.0.0.0?domains=foo.itsi.fyi,bar.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
|
@@ -71,7 +70,8 @@ preload true
|
|
71
70
|
# all of them at once, if they reach the threshold simultaneously.
|
72
71
|
worker_memory_limit 1024 * 1024 * 1024
|
73
72
|
|
74
|
-
# You can provide an optional block of code to run, when a worker hits its memory threshold
|
73
|
+
# You can provide an optional block of code to run, when a worker hits its memory threshold
|
74
|
+
# (Use this to send yourself an alert,
|
75
75
|
# write metrics to disk etc. etc.)
|
76
76
|
after_memory_limit_reached do |pid|
|
77
77
|
puts "Worker #{pid} has reached its memory threshold and will restart"
|
@@ -88,7 +88,8 @@ after_fork {}
|
|
88
88
|
shutdown_timeout 5
|
89
89
|
|
90
90
|
# Set this to false for application environments that require rack.input to be a rewindable body
|
91
|
-
# (like Rails). For rack applications that can stream inputs, you can set this to true for a more
|
91
|
+
# (like Rails). For rack applications that can stream inputs, you can set this to true for a more
|
92
|
+
# memory-efficient approach.
|
92
93
|
stream_body false
|
93
94
|
|
94
95
|
# OOB GC responses threshold
|
@@ -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
|
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.
|
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)
|
@@ -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
|
-
|
43
|
-
|
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
|
-
|
87
|
+
RackEnvPool.checkin(env)
|
83
88
|
body.close if body.respond_to?(:close)
|
84
89
|
end
|
85
90
|
|
@@ -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"
|
@@ -5,8 +5,8 @@ require "itsi/server"
|
|
5
5
|
|
6
6
|
module RubyLsp
|
7
7
|
module Itsi
|
8
|
-
class Addon < ::RubyLsp::Addon
|
9
|
-
def activate(
|
8
|
+
class Addon < ::RubyLsp::Addon # rubocop:disable Style/Documentation
|
9
|
+
def activate(_global_state, message_queue)
|
10
10
|
@message_queue = message_queue
|
11
11
|
end
|
12
12
|
|
@@ -20,33 +20,34 @@ module RubyLsp
|
|
20
20
|
"0.1.0"
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
23
|
def create_completion_listener(response_builder, node_context, dispatcher, uri)
|
25
|
-
return unless uri.to_s.
|
24
|
+
return unless uri.to_s =~ /itsi.rb$/i
|
25
|
+
|
26
26
|
@in_itsi_file = true
|
27
27
|
CompletionListener.new(response_builder, node_context, dispatcher, uri)
|
28
28
|
end
|
29
29
|
|
30
30
|
def create_hover_listener(response_builder, node_context, dispatcher)
|
31
31
|
hl = dispatcher.listeners[:on_call_node_enter].find { |c| c.is_a?(RubyLsp::Listeners::Hover) }
|
32
|
-
return unless hl.instance_variable_get("@path").to_s.
|
32
|
+
return unless hl.instance_variable_get("@path").to_s =~ /itsi.rb$/i
|
33
|
+
|
33
34
|
HoverListener.new(response_builder, node_context, dispatcher)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
class HoverListener
|
38
|
+
class HoverListener # rubocop:disable Style/Documentation
|
38
39
|
def initialize(response_builder, node_context, dispatcher)
|
39
40
|
@response_builder = response_builder
|
40
41
|
@node_context = node_context
|
41
42
|
@dispatcher = dispatcher
|
42
43
|
|
43
|
-
@options_by_name = ::Itsi::Server::Config::Option.subclasses.group_by(&:option_name).
|
44
|
-
@middlewares_by_name = ::Itsi::Server::Config::Middleware.subclasses.group_by(&:middleware_name).
|
44
|
+
@options_by_name = ::Itsi::Server::Config::Option.subclasses.group_by(&:option_name).transform_values(&:first)
|
45
|
+
@middlewares_by_name = ::Itsi::Server::Config::Middleware.subclasses.group_by(&:middleware_name).transform_values(&:first)
|
45
46
|
# Register for call nodes for hover events
|
46
47
|
dispatcher.register(self, :on_call_node_enter)
|
47
48
|
end
|
48
49
|
|
49
|
-
def on_call_node_enter(node)
|
50
|
+
def on_call_node_enter(node) # rubocop:disable Metrics/MethodLength
|
50
51
|
if (matched_class = @options_by_name[node.message.to_sym])
|
51
52
|
@response_builder.push(
|
52
53
|
matched_class.documentation,
|
@@ -61,7 +62,7 @@ module RubyLsp
|
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
64
|
-
class CompletionListener
|
65
|
+
class CompletionListener # rubocop:disable Style/Documentation
|
65
66
|
def initialize(response_builder, node_context, dispatcher, uri)
|
66
67
|
@response_builder = response_builder
|
67
68
|
@node_context = node_context
|
@@ -74,7 +75,7 @@ module RubyLsp
|
|
74
75
|
dispatcher.register(self, :completion_item_resolve)
|
75
76
|
end
|
76
77
|
|
77
|
-
def on_call_node_enter(node)
|
78
|
+
def on_call_node_enter(node) # rubocop:disable Metrics/AbcSize
|
78
79
|
# Only handle method calls that are being typed (i.e. no arguments yet)
|
79
80
|
return unless node.arguments.nil?
|
80
81
|
|
@@ -122,8 +123,6 @@ module RubyLsp
|
|
122
123
|
end
|
123
124
|
end
|
124
125
|
end
|
125
|
-
|
126
|
-
|
127
126
|
end
|
128
127
|
end
|
129
128
|
end
|
@@ -25,17 +25,10 @@ def free_bind(protocol = "http", unix_socket: false)
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def server(
|
29
|
-
|
28
|
+
def server(
|
29
|
+
app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(protocol), itsi_rb: nil, cleanup: true,
|
30
|
+
&blk)
|
30
31
|
app ||= Rack::Lint.new(app_with_lint) if app_with_lint
|
31
|
-
itsi_rb ||= lambda do
|
32
|
-
# Inline Itsi.rb
|
33
|
-
bind bind
|
34
|
-
workers 1
|
35
|
-
threads 1
|
36
|
-
log_level :warn
|
37
|
-
run app if app
|
38
|
-
end
|
39
32
|
|
40
33
|
cli_params = {}
|
41
34
|
cli_params[:binds] = [bind] if bind
|
@@ -46,13 +39,18 @@ def server(app: nil, app_with_lint: nil, protocol: "http", bind: free_bind(proto
|
|
46
39
|
sync.push(true)
|
47
40
|
end
|
48
41
|
|
49
|
-
Itsi::Server.start_in_background_thread(cli_params
|
42
|
+
Itsi::Server.start_in_background_thread(cli_params) do
|
43
|
+
bind bind
|
44
|
+
workers 1
|
45
|
+
threads 1
|
46
|
+
log_level :warn
|
47
|
+
run app if app
|
48
|
+
instance_exec(&itsi_rb) if itsi_rb
|
49
|
+
end
|
50
50
|
|
51
51
|
sync.pop
|
52
52
|
uri = URI(bind)
|
53
|
-
# Timeout.timeout(timeout) do
|
54
53
|
RequestContext.new(uri, self).instance_exec(uri, &blk)
|
55
|
-
# end
|
56
54
|
rescue StandardError => e
|
57
55
|
puts e
|
58
56
|
# puts e.message
|
@@ -119,6 +117,7 @@ class RequestContext
|
|
119
117
|
def patch(path, data = "", headers = {})
|
120
118
|
request = Net::HTTP::Patch.new(uri_for(path))
|
121
119
|
request.body = data
|
120
|
+
headers.each { |k, v| request[k] = v }
|
122
121
|
client.request(request)
|
123
122
|
end
|
124
123
|
|
@@ -5,14 +5,13 @@ class TestGrpc < Minitest::Test
|
|
5
5
|
Stub = Test::TestService::Stub
|
6
6
|
|
7
7
|
def new_stub(uri)
|
8
|
-
address = uri.to_s[
|
8
|
+
address = uri.to_s[%r{//(.*)}, 1] # e.g. "127.0.0.1:12345"
|
9
9
|
channel = GRPC::Core::Channel.new(address,
|
10
|
-
{},
|
11
|
-
:this_channel_is_insecure
|
12
|
-
)
|
10
|
+
{}, # channel_args hash
|
11
|
+
:this_channel_is_insecure)
|
13
12
|
stub = Stub.new(nil,
|
14
|
-
|
15
|
-
|
13
|
+
nil,
|
14
|
+
channel_override: channel)
|
16
15
|
end
|
17
16
|
|
18
17
|
def test_unary_echo
|
@@ -46,7 +45,7 @@ class TestGrpc < Minitest::Test
|
|
46
45
|
req = Test::EchoRequest.new(message: "xy")
|
47
46
|
# expect two StreamResponse frames, one per character
|
48
47
|
chars = stub.server_stream(req).map(&:messages).flatten
|
49
|
-
assert_equal [
|
48
|
+
assert_equal %w[x y], chars
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
@@ -58,7 +57,7 @@ class TestGrpc < Minitest::Test
|
|
58
57
|
inputs = %w[test1 test2].map { |m| Test::EchoRequest.new(message: m) }
|
59
58
|
# each response uppercases its incoming message
|
60
59
|
results = stub.bidi_stream(inputs.each).map(&:message)
|
61
|
-
assert_equal [
|
60
|
+
assert_equal %w[TEST1 TEST2], results
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
@@ -82,7 +81,6 @@ class TestGrpc < Minitest::Test
|
|
82
81
|
end
|
83
82
|
end
|
84
83
|
|
85
|
-
|
86
84
|
def test_unary_echo_json
|
87
85
|
server(itsi_rb: lambda do
|
88
86
|
grpc TestServiceImpl.new
|
@@ -99,7 +97,7 @@ class TestGrpc < Minitest::Test
|
|
99
97
|
server(itsi_rb: lambda do
|
100
98
|
grpc TestServiceImpl.new
|
101
99
|
end) do
|
102
|
-
input = [{"message" => "a"}, {"message" => "b"}, {"message" => "c"}]
|
100
|
+
input = [{ "message" => "a" }, { "message" => "b" }, { "message" => "c" }]
|
103
101
|
res = post("test.TestService/ClientStream", input.to_json, { "Content-Type" => "application/json" })
|
104
102
|
json = JSON.parse(res.body)
|
105
103
|
assert_equal 200, res.code.to_i
|
@@ -117,7 +115,7 @@ class TestGrpc < Minitest::Test
|
|
117
115
|
# service streams back one JSON object per character
|
118
116
|
messages = arr.flat_map { |frame| frame["messages"] }
|
119
117
|
assert_equal 200, res.code.to_i
|
120
|
-
assert_equal [
|
118
|
+
assert_equal %w[x y], messages
|
121
119
|
end
|
122
120
|
end
|
123
121
|
|
@@ -125,13 +123,13 @@ class TestGrpc < Minitest::Test
|
|
125
123
|
server(itsi_rb: lambda do
|
126
124
|
grpc TestServiceImpl.new
|
127
125
|
end) do
|
128
|
-
inputs = [{"message":"foo"}, {"message":"Bar"}]
|
126
|
+
inputs = [{ "message": "foo" }, { "message": "Bar" }]
|
129
127
|
res = post("test.TestService/BidiStream", inputs.to_json, { "Content-Type" => "application/json" })
|
130
128
|
arr = JSON.parse(res.body)
|
131
129
|
# bidi uppercases each incoming message
|
132
130
|
results = arr.map { |frame| frame["message"] }
|
133
131
|
assert_equal 200, res.code.to_i
|
134
|
-
assert_equal [
|
132
|
+
assert_equal %w[FOO BAR], results
|
135
133
|
end
|
136
134
|
end
|
137
135
|
|
@@ -145,7 +143,8 @@ class TestGrpc < Minitest::Test
|
|
145
143
|
assert_nil json1["messages"]
|
146
144
|
|
147
145
|
# empty server-stream
|
148
|
-
res2 = post("test.TestService/ServerStream", { "message" => "" }.to_json,
|
146
|
+
res2 = post("test.TestService/ServerStream", { "message" => "" }.to_json,
|
147
|
+
{ "Content-Type" => "application/json" })
|
149
148
|
arr2 = JSON.parse(res2.body)
|
150
149
|
assert arr2.empty?
|
151
150
|
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "test_service_services_pb" # generated by grpc_tools_ruby_protoc
|
2
2
|
|
3
3
|
class TestServiceImpl < Test::TestService::Service
|
4
4
|
def unary_echo(req, _unused_call)
|
5
5
|
Test::EchoResponse.new(message: req.message)
|
6
6
|
end
|
7
7
|
|
8
|
-
def client_stream(
|
9
|
-
msgs =
|
8
|
+
def client_stream(call)
|
9
|
+
msgs = call.each_remote_read.map(&:message)
|
10
10
|
Test::StreamResponse.new(messages: msgs)
|
11
11
|
end
|
12
12
|
|