itsi 0.1.14 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +126 -272
  3. data/Cargo.toml +6 -0
  4. data/crates/itsi_error/Cargo.toml +1 -0
  5. data/crates/itsi_error/src/lib.rs +100 -10
  6. data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
  7. data/crates/itsi_server/Cargo.toml +12 -11
  8. data/crates/itsi_server/src/default_responses/html/401.html +68 -0
  9. data/crates/itsi_server/src/default_responses/html/403.html +68 -0
  10. data/crates/itsi_server/src/default_responses/html/404.html +68 -0
  11. data/crates/itsi_server/src/default_responses/html/413.html +71 -0
  12. data/crates/itsi_server/src/default_responses/html/429.html +68 -0
  13. data/crates/itsi_server/src/default_responses/html/500.html +71 -0
  14. data/crates/itsi_server/src/default_responses/html/502.html +71 -0
  15. data/crates/itsi_server/src/default_responses/html/503.html +68 -0
  16. data/crates/itsi_server/src/default_responses/html/504.html +69 -0
  17. data/crates/itsi_server/src/default_responses/html/index.html +238 -0
  18. data/crates/itsi_server/src/default_responses/json/401.json +6 -0
  19. data/crates/itsi_server/src/default_responses/json/403.json +6 -0
  20. data/crates/itsi_server/src/default_responses/json/404.json +6 -0
  21. data/crates/itsi_server/src/default_responses/json/413.json +6 -0
  22. data/crates/itsi_server/src/default_responses/json/429.json +6 -0
  23. data/crates/itsi_server/src/default_responses/json/500.json +6 -0
  24. data/crates/itsi_server/src/default_responses/json/502.json +6 -0
  25. data/crates/itsi_server/src/default_responses/json/503.json +6 -0
  26. data/crates/itsi_server/src/default_responses/json/504.json +6 -0
  27. data/crates/itsi_server/src/default_responses/mod.rs +11 -0
  28. data/crates/itsi_server/src/lib.rs +58 -26
  29. data/crates/itsi_server/src/prelude.rs +2 -0
  30. data/crates/itsi_server/src/ruby_types/README.md +21 -0
  31. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
  32. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  33. data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
  34. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
  35. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
  36. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
  37. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
  38. data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
  39. data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
  40. data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
  41. data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
  42. data/crates/itsi_server/src/server/binds/mod.rs +4 -0
  43. data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
  44. data/crates/itsi_server/src/server/http_message_types.rs +97 -0
  45. data/crates/itsi_server/src/server/io_stream.rs +2 -1
  46. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
  70. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  71. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
  72. data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
  73. data/crates/itsi_server/src/server/mod.rs +3 -9
  74. data/crates/itsi_server/src/server/process_worker.rs +21 -3
  75. data/crates/itsi_server/src/server/request_job.rs +2 -2
  76. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
  77. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
  78. data/crates/itsi_server/src/server/signal.rs +24 -41
  79. data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
  80. data/crates/itsi_server/src/server/thread_worker.rs +59 -28
  81. data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
  82. data/crates/itsi_server/src/services/mime_types.rs +1416 -0
  83. data/crates/itsi_server/src/services/mod.rs +6 -0
  84. data/crates/itsi_server/src/services/password_hasher.rs +83 -0
  85. data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
  86. data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
  87. data/crates/itsi_tracing/src/lib.rs +145 -55
  88. data/{Itsi.rb → foo/Itsi.rb} +6 -9
  89. data/gems/scheduler/Cargo.lock +7 -0
  90. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  91. data/gems/scheduler/test/helpers/test_helper.rb +0 -1
  92. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  93. data/gems/scheduler/test/test_network_io.rb +1 -1
  94. data/gems/scheduler/test/test_process_wait.rb +0 -1
  95. data/gems/server/Cargo.lock +126 -272
  96. data/gems/server/exe/itsi +65 -19
  97. data/gems/server/itsi-server.gemspec +4 -3
  98. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  99. data/gems/server/lib/itsi/http_request.rb +117 -17
  100. data/gems/server/lib/itsi/http_response.rb +2 -0
  101. data/gems/server/lib/itsi/passfile.rb +109 -0
  102. data/gems/server/lib/itsi/server/config/dsl.rb +171 -99
  103. data/gems/server/lib/itsi/server/config.rb +58 -23
  104. data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
  105. data/gems/server/lib/itsi/server/default_app/index.html +113 -89
  106. data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
  107. data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
  108. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
  109. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  110. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  111. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  112. data/gems/server/lib/itsi/server/route_tester.rb +107 -0
  113. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  114. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  115. data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
  116. data/gems/server/lib/itsi/server/version.rb +1 -1
  117. data/gems/server/lib/itsi/server.rb +82 -12
  118. data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
  119. data/gems/server/lib/shell_completions/completions.rb +26 -0
  120. data/gems/server/test/helpers/test_helper.rb +2 -1
  121. data/lib/itsi/version.rb +1 -1
  122. data/sandbox/README.md +5 -0
  123. data/sandbox/itsi_file/Gemfile +4 -2
  124. data/sandbox/itsi_file/Gemfile.lock +48 -6
  125. data/sandbox/itsi_file/Itsi.rb +327 -129
  126. data/sandbox/itsi_file/call.json +1 -0
  127. data/sandbox/itsi_file/echo_client/Gemfile +10 -0
  128. data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
  129. data/sandbox/itsi_file/echo_client/README.md +95 -0
  130. data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
  131. data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
  132. data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
  133. data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
  134. data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
  135. data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
  136. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
  137. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
  138. data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
  139. data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
  140. data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
  141. data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
  142. data/sandbox/itsi_sandbox_async/config.ru +0 -1
  143. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  144. data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
  145. data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
  146. data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
  147. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
  148. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
  149. data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
  150. data/sandbox/itsi_sinatra/app.rb +0 -1
  151. data/sandbox/static_files/.env +1 -0
  152. data/sandbox/static_files/404.html +25 -0
  153. data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
  154. data/sandbox/static_files/about.html +68 -0
  155. data/sandbox/static_files/tiny.html +1 -0
  156. data/sandbox/static_files/writebook.zip +0 -0
  157. data/tasks.txt +28 -33
  158. metadata +87 -26
  159. data/crates/itsi_error/src/from.rs +0 -68
  160. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
  161. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
  162. data/crates/itsi_server/src/server/itsi_service.rs +0 -172
  163. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
  164. data/crates/itsi_server/src/server/types.rs +0 -43
  165. data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
  166. data/sandbox/itsi_file/public/assets/index.html +0 -1
  167. /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
  168. /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
  169. /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
@@ -1,91 +1,115 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <title>Itsi - Default</title>
6
- <style>
7
- * {
8
- box-sizing: border-box;
9
- margin: 0;
10
- padding: 0;
11
- }
12
- body {
13
- font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
14
- background-color: #f4f4f4;
15
- color: #333;
16
- line-height: 1.6;
17
- }
18
- .container {
19
- max-width: 700px;
20
- margin: 3rem auto;
21
- background: #fff;
22
- border-radius: 8px;
23
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
24
- padding: 2rem;
25
- }
26
- h1 {
27
- font-size: 1.8rem;
28
- margin-bottom: 1rem;
29
- text-align: center;
30
- color: #444;
31
- }
32
- p {
33
- margin-bottom: 1rem;
34
- text-align: center;
35
- color: #666;
36
- }
37
- ul.fields {
38
- list-style: none;
39
- margin-top: 1.5rem;
40
- padding: 0;
41
- }
42
- ul.fields li {
43
- background: #fafafa;
44
- border: 1px solid #eee;
45
- border-radius: 5px;
46
- padding: 0.75rem;
47
- margin-bottom: 0.75rem;
48
- display: flex;
49
- justify-content: space-between;
50
- align-items: center;
51
- }
52
- .label {
53
- font-weight: bold;
54
- margin-right: 1rem;
55
- }
56
- </style>
57
- </head>
58
- <body>
59
- <div class="container">
60
- <h1>You're running on Itsi!</h1>
61
- <p>RACK environment:</p>
62
-
63
- <ul class="fields">
64
- <li>
65
- <span class="label">REQUEST_METHOD:</span>
66
- <span>%{REQUEST_METHOD}</span>
67
- </li>
68
- <li>
69
- <span class="label">PATH_INFO:</span>
70
- <span>%{PATH_INFO}</span>
71
- </li>
72
- <li>
73
- <span class="label">SERVER_NAME:</span>
74
- <span>%{SERVER_NAME}</span>
75
- </li>
76
- <li>
77
- <span class="label">SERVER_PORT:</span>
78
- <span>%{SERVER_PORT}</span>
79
- </li>
80
- <li>
81
- <span class="label">REMOTE_ADDR:</span>
82
- <span>%{REMOTE_ADDR}</span>
83
- </li>
84
- <li>
85
- <span class="label">HTTP_USER_AGENT:</span>
86
- <span>%{HTTP_USER_AGENT}</span>
87
- </li>
88
- </ul>
89
- </div>
90
- </body>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Itsi - Default</title>
7
+ <style>
8
+ :root {
9
+ --bg-color: #f0f2f5;
10
+ --text-color: #333;
11
+ --link-color: #0052cc;
12
+ }
13
+ *,
14
+ *::before,
15
+ *::after {
16
+ box-sizing: border-box;
17
+ }
18
+ body {
19
+ margin: 0;
20
+ font-family: "Helvetica Neue", Arial, sans-serif;
21
+ background: var(--bg-color);
22
+ color: var(--text-color);
23
+ line-height: 1.6;
24
+ display: flex;
25
+ flex-direction: column;
26
+ align-items: center;
27
+ padding: 3rem 1rem;
28
+ }
29
+ header {
30
+ text-align: center;
31
+ margin-bottom: 2rem;
32
+ }
33
+ h1 {
34
+ font-size: 2.5rem;
35
+ margin-bottom: 0.5rem;
36
+ }
37
+ p {
38
+ font-size: 1.5rem;
39
+ margin-bottom: 2rem;
40
+ color: #555;
41
+ }
42
+ ul.fields {
43
+ list-style: none;
44
+ padding: 0;
45
+ margin: 0;
46
+ max-width: 100%%;
47
+ width: 100%%;
48
+ }
49
+ ul.fields li {
50
+ display: flex;
51
+ justify-content: space-between;
52
+ padding: 1rem;
53
+ border-bottom: 1px solid #ddd;
54
+ }
55
+ ul.fields li:last-child {
56
+ border-bottom: none;
57
+ }
58
+ .label {
59
+ font-weight: bold;
60
+ margin-right: 1rem;
61
+ }
62
+ /* Make it expansive: let content use more horizontal space on larger screens */
63
+ main {
64
+ max-width: 1200px;
65
+ width: 100%%;
66
+ }
67
+ /* Responsive adjustments for small devices */
68
+ @media (max-width: 480px) {
69
+ h1 {
70
+ font-size: 2.5rem;
71
+ }
72
+ p {
73
+ font-size: 1.2rem;
74
+ }
75
+ ul.fields li {
76
+ padding: 0.75rem;
77
+ }
78
+ }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <main>
83
+ <header>
84
+ <h1>You're running on Itsi!</h1>
85
+ <p>RACK environment:</p>
86
+ </header>
87
+ <ul class="fields">
88
+ <li>
89
+ <span class="label">REQUEST_METHOD:</span>
90
+ <span>%{REQUEST_METHOD}</span>
91
+ </li>
92
+ <li>
93
+ <span class="label">PATH_INFO:</span>
94
+ <span>%{PATH_INFO}</span>
95
+ </li>
96
+ <li>
97
+ <span class="label">SERVER_NAME:</span>
98
+ <span>%{SERVER_NAME}</span>
99
+ </li>
100
+ <li>
101
+ <span class="label">SERVER_PORT:</span>
102
+ <span>%{SERVER_PORT}</span>
103
+ </li>
104
+ <li>
105
+ <span class="label">REMOTE_ADDR:</span>
106
+ <span>%{REMOTE_ADDR}</span>
107
+ </li>
108
+ <li>
109
+ <span class="label">HTTP_USER_AGENT:</span>
110
+ <span>%{HTTP_USER_AGENT}</span>
111
+ </li>
112
+ </ul>
113
+ </main>
114
+ </body>
91
115
  </html>
@@ -12,7 +12,7 @@ env = ENV.fetch("APP_ENV") { ENV.fetch("RACK_ENV", "development") }
12
12
  # If more than 1, Itsi will be booted in Cluster mode
13
13
  workers ENV.fetch("ITSI_WORKERS") {
14
14
  require "etc"
15
- env == "development" ? 1 : Etc.nprocessors
15
+ env == "development" ? 1 : nil
16
16
  }
17
17
 
18
18
  # Number of threads to spawn per worker process
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is the default Itsi configuration file, installed when you run `itsi init`
4
+ # It contains a sane starting point for configuring your Itsi server.
5
+ # You can use this file in both development and production environments.
6
+ # Most of the options in this file can be overridden by command line options.
7
+ # Check out itsi -h to learn more about the command line options available to you.
8
+
9
+ env = ENV.fetch("APP_ENV") { ENV.fetch("RACK_ENV", "development") }
10
+
11
+ # Number of worker processes to spawn
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
+ }
17
+
18
+ # Number of threads to spawn per worker process
19
+ # For pure CPU bound applicationss, you'll get the best results keeping this number low
20
+ # Setting a value of 1 is great for superficial benchmarks, but in reality
21
+ # it's better to set this a bit higher to allow expensive requests to get overtaken and minimize head-of-line blocking
22
+ threads ENV.fetch("ITSI_THREADS", 3)
23
+
24
+ # If your application is IO bound (e.g. performing a lot of proxied HTTP requests, or heavy queries etc)
25
+ # you can see *substantial* benefits from enabling this option.
26
+ # To set this option, pass a string, not a class (as we will not have loaded the class yet)
27
+ # E.g.
28
+ # `fiber_scheduler "Itsi::Scheduler"` - The default fast and light-weight scheduler that comes with Itsi
29
+ # `fiber_scheduler "Async::Scheduler"` - Bring your own scheduler!
30
+ fiber_scheduler nil
31
+
32
+ # 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 host and stored inside `ITSI_LOCAL_CA_DIR` (Defaults to ~/.itsi)
34
+ # bind "https://localhost:3000"
35
+ # bind "https://localhost:3000?domains=dev.itsi.fyi"
36
+ #
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
+ # bind "https://itsi.fyi?cert=acme&acme_email=admin@itsi.fyi"
39
+ # You can generate certificates for multiple domains at once, by passing a comma-separated list of domains
40
+ # bind "https://0.0.0.0?domains=foo.itsi.fyi,bar.itsi.fyi&cert=acme&acme_email=admin@itsi.fyi"
41
+ #
42
+ # If you already have a certificate you can specify it using the cert and key parameters
43
+ # bind "https://itsi.fyi?cert=/path/to/cert.pem&key=/path/to/key.pem"
44
+ #
45
+ # You can also bind to a unix socket or a tls unix socket. E.g.
46
+ # bind "unix:///tmp/itsi.sock"
47
+ # bind "tls:///tmp/itsi.secure.sock"
48
+
49
+ if env == "development"
50
+ bind "http://localhost:3000"
51
+ else
52
+ bind "https://0.0.0.0?domains=#{ENV["PRODUCTION_DOMAINS"]}&cert=acme&acme_email=admin@itsi.fyi"
53
+ end
54
+
55
+ # If you want to preload the application, set preload to true
56
+ # to load the entire rack-app defined in rack_file_name before forking.
57
+ # Alternatively, you can preload just a specific set of gems in a group in your gemfile,
58
+ # by providing the group name here.
59
+ # E.g.
60
+ #
61
+ # preload :preload # Load gems inside the preload group
62
+ # preload false # Don't preload.
63
+ #
64
+ # If you want to be able to perform zero-downtime deploys using a single itsi process,
65
+ # you should disable preloads, so that the application is loaded fresh each time a new worker boots
66
+ preload true
67
+
68
+ # Set the maximum memory limit for each worker process in bytes
69
+ # When this limit is reached, the worker will be gracefully restarted.
70
+ # Only one worker is restarted at a time to ensure we don't take down
71
+ # all of them at once, if they reach the threshold simultaneously.
72
+ worker_memory_limit 1024 * 1024 * 1024
73
+
74
+ # You can provide an optional block of code to run, when a worker hits its memory threshold (Use this to send yourself an alert,
75
+ # write metrics to disk etc. etc.)
76
+ after_memory_threshold_reached do |pid|
77
+ puts "Worker #{pid} has reached its memory threshold and will restart"
78
+ end
79
+
80
+ # Do clean up of any non-threadsafe resources before forking a new worker here.
81
+ before_fork {}
82
+
83
+ # Reinitialize any non-threadsafe resources after forking a new worker here.
84
+ after_fork {}
85
+
86
+ # Shutdown timeout
87
+ # Number of seconds to wait for workers to gracefully shutdown before killing them.
88
+ shutdown_timeout 5
89
+
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 memory-efficient approach.
92
+ stream_body false
93
+
94
+ # OOB GC responses threshold
95
+ # Specifies how frequently OOB gc should be triggered during periods where there is a gap in queued requests.
96
+ # Setting this too low can substantially worsen performance
97
+ oob_gc_responses_threshold 512
98
+
99
+ # Log level
100
+ # Set this to one of the following values: debug, info, warn, error, fatal
101
+ # Can also be set using the ITSI_LOG environment variable
102
+ log_level :info
103
+
104
+ # Log Format
105
+ # Set this to be either :ansi or :json. If you leave it blank Itsi will try
106
+ # and auto-detect the format based on the TTY environment.
107
+ log_format :auto
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+
5
+ module Itsi
6
+ class GrpcCall
7
+ attr_accessor :rpc_desc
8
+
9
+ def input_stream?
10
+ @input_stream ||= @rpc_desc&.input&.is_a?(GRPC::RpcDesc::Stream) || false
11
+ end
12
+
13
+ def output_stream?
14
+ @output_stream ||= @rpc_desc&.output&.is_a?(GRPC::RpcDesc::Stream) || false
15
+ end
16
+
17
+ def input_type
18
+ @input_type ||= input_stream? ? rpc_desc.input.type : rpc_desc.input
19
+ end
20
+
21
+ def output_type
22
+ @output_type ||= output_stream? ? rpc_desc.output.type : rpc_desc.output
23
+ end
24
+
25
+ def reader
26
+ @reader ||= IO.open(stream.reader_fileno, "rb")
27
+ end
28
+
29
+ def close
30
+ if output_stream? && content_type == "application/json"
31
+ stream.write("[") unless @opened
32
+ stream.write("]")
33
+ end
34
+
35
+ @reader&.close
36
+ stream.close
37
+ end
38
+
39
+ def deadline
40
+ return @deadline if defined?(@deadline)
41
+ return @deadline = nil unless timeout
42
+
43
+ @deadline = Time.now + timeout
44
+ end
45
+
46
+ def parse_from_json_stream(json_stream)
47
+ first_char = nil
48
+ loop do
49
+ char = json_stream.read(1)
50
+ break if char.nil?
51
+ if char =~ /\s/
52
+ next
53
+ elsif ["[", ","].include?(char)
54
+ first_char = char
55
+ break
56
+ elsif char == "]"
57
+ return nil
58
+ else
59
+ # If the first non-whitespace character is not '[' or comma, return nil.
60
+ return nil
61
+ end
62
+ end
63
+
64
+ return nil if first_char.nil?
65
+
66
+ # Step 2: Process objects until we hit the end of the JSON stream or array.
67
+ loop do
68
+ # Skip any whitespace or commas preceding an object.
69
+ char = nil
70
+ loop do
71
+ char = json_stream.read(1)
72
+ break if char.nil?
73
+ next if char =~ /\s/
74
+
75
+ break
76
+ end
77
+
78
+ # The next non-whitespace, non-comma character should be the start of an object.
79
+ return nil unless char == "{"
80
+
81
+ # Step 3: Start buffering the JSON object.
82
+ buffer = "{".dup
83
+ stack = ["{"]
84
+ in_string = false
85
+ escape = false
86
+
87
+ while stack.any?
88
+ ch = json_stream.read(1)
89
+ return nil if ch.nil? # premature end of stream
90
+
91
+ buffer << ch
92
+
93
+ if in_string
94
+ if escape
95
+ escape = false
96
+ next
97
+ end
98
+ if ch == "\\"
99
+ escape = true
100
+ elsif ch == '"'
101
+ in_string = false
102
+ end
103
+ elsif ch == '"'
104
+ in_string = true
105
+ elsif ["{", "["].include?(ch)
106
+ stack.push(ch)
107
+ elsif ["}", "]"].include?(ch)
108
+ expected = (ch == "}" ? "{" : "[")
109
+ # Check for matching bracket.
110
+ return nil unless stack.last == expected
111
+
112
+ stack.pop
113
+ end
114
+ end
115
+ # Yield the complete JSON object (as a string).
116
+ return buffer
117
+ end
118
+ end
119
+
120
+ def remote_read
121
+ if content_type == "application/json"
122
+ if input_stream?
123
+ if next_item = parse_from_json_stream(reader)
124
+ input_type.decode_json(next_item)
125
+ end
126
+ else
127
+ input_type.decode_json(reader.read)
128
+ end
129
+ else
130
+ header = reader.read(5)
131
+ return nil if header.nil? || header.bytesize < 5
132
+
133
+ compressed = header.bytes[0] == 1
134
+ length = header[1..4].unpack1("N")
135
+
136
+ data = reader.read(length)
137
+ return nil if data.nil?
138
+
139
+ data = decompress_input(data) if compressed
140
+
141
+ input_type.decode(data)
142
+ end
143
+ end
144
+
145
+ def send_framed_message(message_data, compressed = nil)
146
+ if content_type == "application/json"
147
+ if output_stream?
148
+ if @opened
149
+ stream.write(",\n")
150
+ else
151
+ stream.write("[")
152
+ @opened = true
153
+ end
154
+ end
155
+ message_data = output_type.encode_json(message_data)
156
+ stream.write(message_data)
157
+ else
158
+ message_data = output_type.encode(message_data)
159
+ should_compress = compressed.nil? ? should_compress_output?(message_data.bytesize) : compressed
160
+
161
+ if should_compress
162
+ message_data = compress_output(message_data)
163
+ compressed_flag = 1
164
+ else
165
+ compressed_flag = 0
166
+ end
167
+
168
+ message = [compressed_flag, message_data.bytesize].pack("CN") << message_data
169
+ stream.write(message)
170
+
171
+ @body_written = true
172
+ end
173
+ rescue IOError
174
+ close
175
+ end
176
+
177
+ def remote_send(response)
178
+ send_framed_message(response)
179
+ end
180
+
181
+ def deadline_exceeded?
182
+ @deadline && @deadline <= Time.now
183
+ end
184
+
185
+ def each_remote_read
186
+ return enum_for(:each_remote_read) unless block_given?
187
+
188
+ while (resp = remote_read) || !cancelled?
189
+
190
+ yield resp
191
+ end
192
+ end
193
+
194
+ def bidi_streamer?
195
+ input_stream? && output_stream?
196
+ end
197
+
198
+ def client_streamer?
199
+ input_stream? && !output_stream?
200
+ end
201
+
202
+ def server_streamer?
203
+ !input_stream? && output_stream?
204
+ end
205
+
206
+ def request_response?
207
+ !input_stream? && !output_stream?
208
+ end
209
+
210
+ def send_initial_metadata(kv_pairs)
211
+ add_headers(kv_pairs.transform_values { |v| Array(v) })
212
+ end
213
+
214
+ def send_empty
215
+ remote_send(output_type.new) unless @body_written
216
+ end
217
+
218
+ def send_status(status_code, status_details, trailing_metadata = {})
219
+ trailers = {
220
+ "grpc-status" => status_code.to_s
221
+ }
222
+
223
+ unless status_details.nil? || status_details.empty?
224
+ encoded_message = status_details.gsub(/[ #%&+,:;<=>?@\[\]^`{|}~\\"]/) do |c|
225
+ "%" + c.ord.to_s(16).upcase
226
+ end
227
+ trailers["grpc-message"] = encoded_message
228
+ end
229
+
230
+ trailing_metadata.each do |key, value|
231
+ trailers[key] = \
232
+ if key.to_s.end_with?("-bin")
233
+ Base64.strict_encode64(value.to_s)
234
+ else
235
+ value.to_s
236
+ end
237
+ end
238
+
239
+ send_trailers(trailers)
240
+ end
241
+
242
+ def send_trailers(trailers)
243
+ stream.send_trailers(trailers)
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,100 @@
1
+ module Itsi
2
+ class Server
3
+ class GrpcInterface
4
+ DeadlineExceeded = Class.new(StandardError)
5
+
6
+ attr_accessor :service, :service_class
7
+
8
+ def self.for(service)
9
+ interface = new(service)
10
+ lambda do |request|
11
+ interface.handle_request(request)
12
+ end
13
+ end
14
+
15
+ def self.reflection_for(handlers)
16
+ require_relative "reflection/v1/reflection_services_pb"
17
+ interface = new(Grpc::Reflection::V1::ServerReflection::Service.new(handlers))
18
+ lambda do |request|
19
+ interface.handle_request(request)
20
+ end
21
+ end
22
+
23
+ def initialize(service)
24
+ @service = service
25
+ @service_class = service.class
26
+ @service_class.rpc_descs.transform_keys! do |k|
27
+ k.to_s.gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase.to_sym
28
+ end
29
+ end
30
+
31
+ def handle_request(active_call)
32
+ unless (active_call.rpc_desc = service_class.rpc_descs[active_call.method_name])
33
+ active_call.stream.write("\n")
34
+ active_call.send_status(13, "Method not found")
35
+ active_call.close
36
+ return
37
+ end
38
+
39
+ active_call.send_initial_metadata(
40
+ {
41
+ "grpc-accept-encoding" => "gzip, deflate, identity",
42
+ "content-type" => active_call.json? ? "application/json" : "application/grpc"
43
+ }
44
+ )
45
+
46
+ begin
47
+ if active_call.bidi_streamer?
48
+ handle_bidi_streaming(active_call)
49
+ elsif active_call.client_streamer?
50
+ handle_client_streaming(active_call)
51
+ elsif active_call.server_streamer?
52
+ handle_server_streaming(active_call)
53
+ elsif active_call.request_response?
54
+ handle_unary(active_call)
55
+ end
56
+ active_call.send_status(0, "Success")
57
+ rescue Google::Protobuf::ParseError => e
58
+ active_call.send_empty
59
+ active_call.send_status(3, e.message)
60
+ rescue DeadlineExceeded => e
61
+ active_call.send_empty
62
+ active_call.send_status(4, e.message)
63
+ rescue StandardError => e
64
+ active_call.send_empty
65
+ active_call.send_status(13, e.message)
66
+ end
67
+ rescue StandardError => e
68
+ Itsi.log_warn("Unhandled error in grpc_interface: #{e.message}")
69
+ ensure
70
+ active_call.close
71
+ end
72
+
73
+ private
74
+
75
+ def handle_unary(active_call)
76
+ message = active_call.remote_read
77
+ response = service.send(active_call.method_name, message, active_call)
78
+ active_call.remote_send(response)
79
+ end
80
+
81
+ def handle_client_streaming(active_call)
82
+ response = service.send(active_call.method_name, active_call.each_remote_read, active_call)
83
+ active_call.remote_send(response)
84
+ end
85
+
86
+ def handle_server_streaming(active_call)
87
+ message = active_call.remote_read
88
+ service.send(active_call.method_name, message, active_call) do |response|
89
+ active_call.remote_send(response)
90
+ end
91
+ end
92
+
93
+ def handle_bidi_streaming(active_call)
94
+ service.send(active_call.method_name, active_call.each_remote_read, active_call) do |response|
95
+ active_call.remote_send(response)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end