itsi-server 0.1.1 → 0.1.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.
Files changed (184) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +7 -0
  4. data/Cargo.lock +3937 -0
  5. data/Cargo.toml +7 -0
  6. data/README.md +4 -0
  7. data/Rakefile +8 -1
  8. data/_index.md +6 -0
  9. data/exe/itsi +141 -46
  10. data/ext/itsi_error/Cargo.toml +3 -0
  11. data/ext/itsi_error/src/lib.rs +98 -24
  12. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  15. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  16. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  26. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  27. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  28. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  29. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  30. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  31. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  35. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  41. data/ext/itsi_scheduler/Cargo.toml +24 -0
  42. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  46. data/ext/itsi_scheduler/src/lib.rs +38 -0
  47. data/ext/itsi_server/Cargo.lock +2956 -0
  48. data/ext/itsi_server/Cargo.toml +72 -14
  49. data/ext/itsi_server/extconf.rb +1 -1
  50. data/ext/itsi_server/src/default_responses/html/401.html +68 -0
  51. data/ext/itsi_server/src/default_responses/html/403.html +68 -0
  52. data/ext/itsi_server/src/default_responses/html/404.html +68 -0
  53. data/ext/itsi_server/src/default_responses/html/413.html +71 -0
  54. data/ext/itsi_server/src/default_responses/html/429.html +68 -0
  55. data/ext/itsi_server/src/default_responses/html/500.html +71 -0
  56. data/ext/itsi_server/src/default_responses/html/502.html +71 -0
  57. data/ext/itsi_server/src/default_responses/html/503.html +68 -0
  58. data/ext/itsi_server/src/default_responses/html/504.html +69 -0
  59. data/ext/itsi_server/src/default_responses/html/index.html +238 -0
  60. data/ext/itsi_server/src/default_responses/json/401.json +6 -0
  61. data/ext/itsi_server/src/default_responses/json/403.json +6 -0
  62. data/ext/itsi_server/src/default_responses/json/404.json +6 -0
  63. data/ext/itsi_server/src/default_responses/json/413.json +6 -0
  64. data/ext/itsi_server/src/default_responses/json/429.json +6 -0
  65. data/ext/itsi_server/src/default_responses/json/500.json +6 -0
  66. data/ext/itsi_server/src/default_responses/json/502.json +6 -0
  67. data/ext/itsi_server/src/default_responses/json/503.json +6 -0
  68. data/ext/itsi_server/src/default_responses/json/504.json +6 -0
  69. data/ext/itsi_server/src/default_responses/mod.rs +11 -0
  70. data/ext/itsi_server/src/env.rs +43 -0
  71. data/ext/itsi_server/src/lib.rs +132 -40
  72. data/ext/itsi_server/src/prelude.rs +2 -0
  73. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  74. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
  75. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  76. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
  77. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
  78. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
  79. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  80. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
  81. data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
  82. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  83. data/ext/itsi_server/src/server/binds/bind.rs +201 -0
  84. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  85. data/ext/itsi_server/src/server/binds/listener.rs +432 -0
  86. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  87. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  88. data/ext/itsi_server/src/server/binds/tls.rs +270 -0
  89. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  90. data/ext/itsi_server/src/server/http_message_types.rs +97 -0
  91. data/ext/itsi_server/src/server/io_stream.rs +105 -0
  92. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
  99. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
  100. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
  101. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
  102. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  103. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
  104. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
  105. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  106. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
  107. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  108. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  109. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
  110. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
  111. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
  112. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  113. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
  114. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
  115. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
  116. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
  117. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  118. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
  119. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  120. data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
  121. data/ext/itsi_server/src/server/mod.rs +12 -5
  122. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  123. data/ext/itsi_server/src/server/request_job.rs +11 -0
  124. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
  125. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  126. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  127. data/ext/itsi_server/src/server/signal.rs +76 -0
  128. data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
  129. data/ext/itsi_server/src/server/thread_worker.rs +475 -0
  130. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  131. data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
  132. data/ext/itsi_server/src/services/mime_types.rs +1416 -0
  133. data/ext/itsi_server/src/services/mod.rs +6 -0
  134. data/ext/itsi_server/src/services/password_hasher.rs +83 -0
  135. data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
  136. data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
  137. data/ext/itsi_tracing/Cargo.toml +5 -0
  138. data/ext/itsi_tracing/src/lib.rs +315 -7
  139. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  140. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  141. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  142. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  143. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  144. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  145. data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  146. data/lib/itsi/http_request.rb +186 -0
  147. data/lib/itsi/http_response.rb +41 -0
  148. data/lib/itsi/passfile.rb +109 -0
  149. data/lib/itsi/server/config/dsl.rb +565 -0
  150. data/lib/itsi/server/config.rb +166 -0
  151. data/lib/itsi/server/default_app/default_app.rb +34 -0
  152. data/lib/itsi/server/default_app/index.html +115 -0
  153. data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
  154. data/lib/itsi/server/default_config/Itsi.rb +107 -0
  155. data/lib/itsi/server/grpc/grpc_call.rb +246 -0
  156. data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  157. data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  158. data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  159. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  160. data/lib/itsi/server/rack_interface.rb +94 -0
  161. data/lib/itsi/server/route_tester.rb +107 -0
  162. data/lib/itsi/server/scheduler_interface.rb +21 -0
  163. data/lib/itsi/server/scheduler_mode.rb +10 -0
  164. data/lib/itsi/server/signal_trap.rb +29 -0
  165. data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  166. data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  167. data/lib/itsi/server/typed_handlers.rb +17 -0
  168. data/lib/itsi/server/version.rb +1 -1
  169. data/lib/itsi/server.rb +160 -9
  170. data/lib/itsi/standard_headers.rb +86 -0
  171. data/lib/ruby_lsp/itsi/addon.rb +111 -0
  172. data/lib/shell_completions/completions.rb +26 -0
  173. metadata +182 -25
  174. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  175. data/ext/itsi_server/src/request/mod.rs +0 -1
  176. data/ext/itsi_server/src/server/bind.rs +0 -138
  177. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  178. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  179. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  180. data/ext/itsi_server/src/server/listener.rs +0 -218
  181. data/ext/itsi_server/src/server/tls.rs +0 -138
  182. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  183. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  184. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,69 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Gateway Timeout</title>
7
+ <style>
8
+ :root {
9
+ --bg-color: #f0f2f5;
10
+ --text-color: #333;
11
+ --accent-color: #0052cc;
12
+ --link-color: #0052cc;
13
+ }
14
+ body {
15
+ margin: 0;
16
+ font-family: "Helvetica Neue", Arial, sans-serif;
17
+ background: var(--bg-color);
18
+ color: var(--text-color);
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ min-height: 100vh;
23
+ padding: 2rem;
24
+ }
25
+ .error-container {
26
+ text-align: center;
27
+ max-width: 800px;
28
+ width: 100%;
29
+ }
30
+ h1 {
31
+ font-size: 5rem;
32
+ font-weight: 300;
33
+ margin-bottom: 0.5rem;
34
+ }
35
+ p {
36
+ font-size: 1.5rem;
37
+ line-height: 1.6;
38
+ margin-bottom: 2rem;
39
+ }
40
+ a {
41
+ font-size: 1rem;
42
+ text-decoration: none;
43
+ color: var(--link-color);
44
+ border-bottom: 1px solid transparent;
45
+ transition: border-color 0.2s ease-in-out;
46
+ }
47
+ a:hover {
48
+ border-color: var(--link-color);
49
+ }
50
+ @media (max-width: 480px) {
51
+ h1 {
52
+ font-size: 3.5rem;
53
+ }
54
+ p {
55
+ font-size: 1.2rem;
56
+ }
57
+ }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <div class="error-container">
62
+ <h1>504</h1>
63
+ <p>The service did not respond within the expected time frame.</p>
64
+ <p>Please try again later.</p>
65
+ <p><a href="/">Return Home</a></p>
66
+ <p><a href="javascript:history.back()">Go Back</a></p>
67
+ </div>
68
+ </body>
69
+ </html>
@@ -0,0 +1,238 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{title}}</title>
7
+ <style>
8
+ :root {
9
+ --bg-color: #f0f2f5;
10
+ --text-color: #333;
11
+ --link-color: #0052cc;
12
+ --accent-color: #0052cc;
13
+ }
14
+ * {
15
+ box-sizing: border-box;
16
+ }
17
+ body {
18
+ margin: 0;
19
+ font-family: "Helvetica Neue", Arial, sans-serif;
20
+ background: var(--bg-color);
21
+ color: var(--text-color);
22
+ padding: 2rem;
23
+ }
24
+ h1 {
25
+ font-size: 2.5rem;
26
+ margin-bottom: 1rem;
27
+ text-align: center;
28
+ }
29
+ table {
30
+ width: 100%;
31
+ border-collapse: collapse;
32
+ margin-top: 1rem;
33
+ }
34
+ th,
35
+ td {
36
+ padding: 0.5rem;
37
+ text-align: left;
38
+ }
39
+ tr:nth-child(even) {
40
+ background-color: #e9ecef;
41
+ }
42
+ th {
43
+ border-bottom: 2px solid #ccc;
44
+ position: relative;
45
+ cursor: pointer;
46
+ }
47
+ /* Sorted header arrow */
48
+ th .sort-arrow {
49
+ font-size: 0.8rem;
50
+ display: none;
51
+ }
52
+ th.sorted .sort-arrow {
53
+ display: inline;
54
+ }
55
+ a {
56
+ color: var(--link-color);
57
+ text-decoration: none;
58
+ }
59
+ a:hover {
60
+ text-decoration: underline;
61
+ }
62
+ .size {
63
+ text-align: right;
64
+ }
65
+ .date {
66
+ white-space: nowrap;
67
+ }
68
+ footer {
69
+ margin-top: 2rem;
70
+ text-align: center;
71
+ font-size: 0.9rem;
72
+ color: #555;
73
+ }
74
+ th.name-header > span {
75
+ display: inline-block;
76
+ width: 100%;
77
+ }
78
+ th.name-header input {
79
+ margin-top: 0.3rem;
80
+ font-size: 0.8rem;
81
+ width: 100%;
82
+ max-width: 200px;
83
+ border: 1px solid #ccc;
84
+ background: none;
85
+ border-radius: 12px;
86
+ padding: 5px 15px;
87
+ }
88
+ /* Responsive adjustments */
89
+ @media (max-width: 480px) {
90
+ h1 {
91
+ font-size: 2.5rem;
92
+ }
93
+ th,
94
+ td {
95
+ padding: 0.4rem;
96
+ }
97
+ th {
98
+ vertical-align: top;
99
+ }
100
+ th.name-header input {
101
+ max-width: 150px;
102
+ font-size: 0.75rem;
103
+ }
104
+ }
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <h1>Directory listing for {{directory}}</h1>
109
+ <table data-sort-asc="true" data-sorted-column="0">
110
+ <thead>
111
+ <tr>
112
+ <th data-type="text" data-column="0" class="name-header">
113
+ <span>Name <span class="sort-arrow">▲</span></span>
114
+ <input type="text" id="nameFilter" placeholder="Filter names" />
115
+ </th>
116
+ <th data-type="size" data-column="1">
117
+ Size <span class="sort-arrow"></span>
118
+ </th>
119
+ <th data-type="text" data-column="2">
120
+ Last Modified <span class="sort-arrow"></span>
121
+ </th>
122
+ </tr>
123
+ </thead>
124
+ <tbody>
125
+ {{rows}}
126
+ </tbody>
127
+ </table>
128
+ <footer>
129
+ <p>Served by Itsi Static Assets</p>
130
+ </footer>
131
+ <script>
132
+ document.addEventListener("DOMContentLoaded", function () {
133
+ const table = document.querySelector("table");
134
+ const headers = table.querySelectorAll("th");
135
+ const nameFilter = document.getElementById("nameFilter");
136
+ let currentSortedColumn = 0;
137
+ let ascending = true;
138
+
139
+ // Add sorting listeners
140
+ headers.forEach((header, index) => {
141
+ header.addEventListener("click", function (e) {
142
+ // Prevent clicks on the filter input from triggering a sort.
143
+ if (e.target.tagName.toLowerCase() === "input") return;
144
+ sortTable(index);
145
+ updateSortArrows(index, ascending);
146
+ });
147
+ });
148
+
149
+ // Filter rows as the user types.
150
+ nameFilter.addEventListener("input", function () {
151
+ filterTable(this.value);
152
+ });
153
+
154
+ function sortTable(columnIndex) {
155
+ const tbody = table.querySelector("tbody");
156
+ let rows = Array.from(tbody.querySelectorAll("tr"));
157
+
158
+ // If sorting on a new column, default to ascending.
159
+ if (currentSortedColumn !== columnIndex) {
160
+ ascending = true;
161
+ currentSortedColumn = columnIndex;
162
+ } else {
163
+ ascending = !ascending;
164
+ }
165
+
166
+ rows.sort((a, b) => {
167
+ let aText = a.cells[columnIndex].textContent.trim().toLowerCase();
168
+ let bText = b.cells[columnIndex].textContent.trim().toLowerCase();
169
+
170
+ // If sorting the file size column, convert sizes to bytes.
171
+ if (headers[columnIndex].getAttribute("data-type") === "size") {
172
+ aText = parseSize(aText);
173
+ bText = parseSize(bText);
174
+ }
175
+
176
+ if (aText < bText) return ascending ? -1 : 1;
177
+ if (aText > bText) return ascending ? 1 : -1;
178
+ return 0;
179
+ });
180
+
181
+ rows.forEach((row) => tbody.appendChild(row));
182
+ table.setAttribute("data-sort-asc", ascending.toString());
183
+ table.setAttribute("data-sorted-column", columnIndex.toString());
184
+ }
185
+
186
+ function updateSortArrows(sortedIndex, ascending) {
187
+ headers.forEach((header, index) => {
188
+ header.classList.remove("sorted");
189
+ const arrow = header.querySelector(".sort-arrow");
190
+ if (index === sortedIndex) {
191
+ header.classList.add("sorted");
192
+ arrow.textContent = ascending ? "▲" : "▼";
193
+ } else {
194
+ arrow.textContent = "";
195
+ }
196
+ });
197
+ }
198
+
199
+ function filterTable(filterValue) {
200
+ const tbody = table.querySelector("tbody");
201
+ const rows = Array.from(tbody.querySelectorAll("tr"));
202
+ const lowerFilter = filterValue.toLowerCase();
203
+
204
+ rows.forEach((row) => {
205
+ const nameCell = row.cells[0];
206
+ row.style.display = nameCell.textContent
207
+ .toLowerCase()
208
+ .includes(lowerFilter)
209
+ ? ""
210
+ : "none";
211
+ });
212
+ }
213
+
214
+ // Parse file size string (e.g., "12.3 KB") into a number (bytes)
215
+ function parseSize(sizeStr) {
216
+ sizeStr = sizeStr.trim();
217
+ if (sizeStr === "-" || sizeStr === "") return 0;
218
+ const parts = sizeStr.split(" ");
219
+ if (parts.length < 2) return parseFloat(parts[0]);
220
+ const value = parseFloat(parts[0]);
221
+ const unit = parts[1].toLowerCase();
222
+ switch (unit) {
223
+ case "b":
224
+ return value;
225
+ case "kb":
226
+ return value * 1024;
227
+ case "mb":
228
+ return value * 1024 * 1024;
229
+ case "gb":
230
+ return value * 1024 * 1024 * 1024;
231
+ default:
232
+ return value;
233
+ }
234
+ }
235
+ });
236
+ </script>
237
+ </body>
238
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Unauthorized",
3
+ "message": "You are not authorized to access this resource.",
4
+ "code": 401,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Forbidden",
3
+ "message": "You are forbidden to access this resource.",
4
+ "code": 403,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Not Found",
3
+ "message": "The requested resource could not be found.",
4
+ "code": 404,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Payload Too Large",
3
+ "message": "The request entity is larger than the server is willing or able to process.",
4
+ "code": 413,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Too Many Requests",
3
+ "message": "Too many requests within a limited time frame.",
4
+ "code": 429,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Internal Server Error",
3
+ "message": "The server encountered an unexpected condition that prevented it from fulfilling the request.",
4
+ "code": 500,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Bad Gateway",
3
+ "message": "The server was acting as a gateway or proxy and received an invalid response from the upstream server.",
4
+ "code": 502,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Service Unavailable",
3
+ "message": "The server is currently unable to handle the request.",
4
+ "code": 503,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "error": "Gateway Timeout",
3
+ "message": "The server did not respond in time.",
4
+ "code": 504,
5
+ "status": "error"
6
+ }
@@ -0,0 +1,11 @@
1
+ use std::sync::LazyLock;
2
+
3
+ use crate::server::middleware_stack::ErrorResponse;
4
+
5
+ pub static TIMEOUT_RESPONSE: LazyLock<ErrorResponse> =
6
+ LazyLock::new(ErrorResponse::gateway_timeout);
7
+
8
+ pub static NOT_FOUND_RESPONSE: LazyLock<ErrorResponse> = LazyLock::new(ErrorResponse::not_found);
9
+
10
+ pub static INTERNAL_SERVER_ERROR_RESPONSE: LazyLock<ErrorResponse> =
11
+ LazyLock::new(ErrorResponse::internal_server_error);
@@ -0,0 +1,43 @@
1
+ use std::{
2
+ env::{var, VarError},
3
+ path::PathBuf,
4
+ sync::LazyLock,
5
+ };
6
+
7
+ type StringVar = LazyLock<String>;
8
+ type MaybeStringVar = LazyLock<Result<String, VarError>>;
9
+ type PathVar = LazyLock<PathBuf>;
10
+
11
+ /// ACME Configuration for auto-generating production certificates
12
+ /// *ITSI_ACME_CACHE_DIR* - Directory to store cached certificates
13
+ /// so that these are not regenerated every time the server starts
14
+ pub static ITSI_ACME_CACHE_DIR: StringVar = LazyLock::new(|| {
15
+ var("ITSI_ACME_CACHE_DIR").unwrap_or_else(|_| "./.rustls_acme_cache".to_string())
16
+ });
17
+
18
+ /// *ITSI_ACME_CONTACT_EMAIL* - Contact Email address to provide to ACME server during certificate renewal
19
+ pub static ITSI_ACME_CONTACT_EMAIL: MaybeStringVar =
20
+ LazyLock::new(|| var("ITSI_ACME_CONTACT_EMAIL"));
21
+
22
+ /// *ITSI_ACME_CA_PEM_PATH* - Optional CA Pem path, used for testing with non-trusted CAs for certifcate generation.
23
+ pub static ITSI_ACME_CA_PEM_PATH: MaybeStringVar = LazyLock::new(|| var("ITSI_ACME_CA_PEM_PATH"));
24
+
25
+ /// *ITSI_ACME_DIRECTORY_URL* - Directory URL to use for ACME certificate generation.
26
+ pub static ITSI_ACME_DIRECTORY_URL: StringVar = LazyLock::new(|| {
27
+ var("ITSI_ACME_DIRECTORY_URL")
28
+ .unwrap_or_else(|_| "https://acme-v02.api.letsencrypt.org/directory".to_string())
29
+ });
30
+
31
+ /// *ITSI_ACME_LOCK_FILE_NAME* - Name of the lock file used to prevent concurrent certificate generation.
32
+ pub static ITSI_ACME_LOCK_FILE_NAME: StringVar =
33
+ LazyLock::new(|| var("ITSI_ACME_LOCK_FILE_NAME").unwrap_or(".acme.lock".to_string()));
34
+
35
+ pub static ITSI_LOCAL_CA_DIR: PathVar = LazyLock::new(|| {
36
+ var("ITSI_LOCAL_CA_DIR")
37
+ .map(PathBuf::from)
38
+ .unwrap_or_else(|_| {
39
+ dirs::home_dir()
40
+ .expect("Failed to find HOME directory when initializing ITSI_LOCAL_CA_DIR")
41
+ .join(".itsi")
42
+ })
43
+ });
@@ -1,52 +1,144 @@
1
- use magnus::{error::Result, function, method, value::Lazy, Module, Object, RClass, RModule, Ruby};
2
- use request::itsi_request::ItsiRequest;
3
- use server::itsi_server::Server;
4
- use stream_writer::StreamWriter;
5
-
6
- pub mod request;
1
+ #![deny(unused_crate_dependencies)]
2
+ pub mod default_responses;
3
+ pub mod env;
4
+ pub mod prelude;
5
+ pub mod ruby_types;
7
6
  pub mod server;
8
- pub mod stream_writer;
9
-
10
- pub static ITSI_MODULE: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module("Itsi").unwrap());
11
- pub static ITSI_SERVER: Lazy<RClass> = Lazy::new(|ruby| {
12
- ruby.get_inner(&ITSI_MODULE)
13
- .define_class("Server", ruby.class_object())
14
- .unwrap()
15
- });
16
- pub static ITSI_REQUEST: Lazy<RClass> = Lazy::new(|ruby| {
17
- ruby.get_inner(&ITSI_MODULE)
18
- .define_class("Request", ruby.class_object())
19
- .unwrap()
20
- });
21
- pub static ITSI_STREAM_WRITER: Lazy<RClass> = Lazy::new(|ruby| {
22
- ruby.get_inner(&ITSI_MODULE)
23
- .define_class("StreamWriter", ruby.class_object())
24
- .unwrap()
25
- });
7
+ pub mod services;
8
+
9
+ use magnus::{error::Result, function, method, Module, Object, Ruby};
10
+ use prelude::*;
11
+ use ruby_types::{
12
+ itsi_body_proxy::ItsiBodyProxy, itsi_grpc_call::ItsiGrpcCall,
13
+ itsi_grpc_response_stream::ItsiGrpcResponseStream, itsi_http_request::ItsiHttpRequest,
14
+ itsi_http_response::ItsiHttpResponse, itsi_server::ItsiServer, ITSI_BODY_PROXY, ITSI_GRPC_CALL,
15
+ ITSI_GRPC_RESPONSE_STREAM, ITSI_MODULE, ITSI_REQUEST, ITSI_RESPONSE, ITSI_SERVER,
16
+ };
17
+ use server::signal::reset_signal_handlers;
18
+ use services::password_hasher;
26
19
 
27
20
  #[magnus::init]
28
21
  fn init(ruby: &Ruby) -> Result<()> {
29
22
  itsi_tracing::init();
23
+ rustls::crypto::aws_lc_rs::default_provider()
24
+ .install_default()
25
+ .ok();
26
+
27
+ let itsi = ruby.get_inner(&ITSI_MODULE);
28
+ itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
29
+ itsi.define_singleton_method("log_info", function!(log_info, 1))?;
30
+ itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
31
+ itsi.define_singleton_method("log_error", function!(log_error, 1))?;
32
+ itsi.define_singleton_method(
33
+ "create_password_hash",
34
+ function!(password_hasher::create_password_hash, 2),
35
+ )?;
30
36
 
31
37
  let server = ruby.get_inner(&ITSI_SERVER);
32
- server.define_singleton_method("new", function!(Server::new, -1))?;
33
- server.define_method("start", method!(Server::start, 0))?;
38
+ server.define_singleton_method("new", function!(ItsiServer::new, 3))?;
39
+ server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
40
+ server.define_method("start", method!(ItsiServer::start, 0))?;
41
+ server.define_method("stop", method!(ItsiServer::stop, 0))?;
34
42
 
35
43
  let request = ruby.get_inner(&ITSI_REQUEST);
36
- request.define_method("path", method!(ItsiRequest::path, 0))?;
37
- request.define_method("script_name", method!(ItsiRequest::script_name, 0))?;
38
- request.define_method("query_string", method!(ItsiRequest::query_string, 0))?;
39
- request.define_method("method", method!(ItsiRequest::method, 0))?;
40
- request.define_method("version", method!(ItsiRequest::version, 0))?;
41
- request.define_method("rack_protocol", method!(ItsiRequest::rack_protocol, 0))?;
42
- request.define_method("host", method!(ItsiRequest::host, 0))?;
43
- request.define_method("headers", method!(ItsiRequest::headers, 0))?;
44
- request.define_method("remote_addr", method!(ItsiRequest::remote_addr, 0))?;
45
- request.define_method("port", method!(ItsiRequest::port, 0))?;
46
- request.define_method("body", method!(ItsiRequest::body, 0))?;
47
-
48
- let stream_writer = ruby.get_inner(&ITSI_STREAM_WRITER);
49
- stream_writer.define_method("write", method!(StreamWriter::write, 1))?;
44
+ request.define_method("path", method!(ItsiHttpRequest::path, 0))?;
45
+ request.define_method("script_name", method!(ItsiHttpRequest::script_name, 0))?;
46
+ request.define_method("query_string", method!(ItsiHttpRequest::query_string, 0))?;
47
+ request.define_method(
48
+ "content_type",
49
+ method!(ItsiHttpRequest::content_type_str, 0),
50
+ )?;
51
+ request.define_method(
52
+ "content_length",
53
+ method!(ItsiHttpRequest::content_length, 0),
54
+ )?;
55
+ request.define_method("request_method", method!(ItsiHttpRequest::method, 0))?;
56
+ request.define_method("version", method!(ItsiHttpRequest::version, 0))?;
57
+ request.define_method("rack_protocol", method!(ItsiHttpRequest::rack_protocol, 0))?;
58
+ request.define_method("host", method!(ItsiHttpRequest::host, 0))?;
59
+ request.define_method("headers", method!(ItsiHttpRequest::headers, 0))?;
60
+ request.define_method("header", method!(ItsiHttpRequest::header, 1))?;
61
+ request.define_method("[]", method!(ItsiHttpRequest::header, 1))?;
62
+ request.define_method("scheme", method!(ItsiHttpRequest::scheme, 0))?;
63
+ request.define_method("remote_addr", method!(ItsiHttpRequest::remote_addr, 0))?;
64
+ request.define_method("port", method!(ItsiHttpRequest::port, 0))?;
65
+ request.define_method("body", method!(ItsiHttpRequest::body, 0))?;
66
+ request.define_method("response", method!(ItsiHttpRequest::response, 0))?;
67
+ request.define_method("json?", method!(ItsiHttpRequest::is_json, 0))?;
68
+ request.define_method("html?", method!(ItsiHttpRequest::is_html, 0))?;
69
+ request.define_method("url_encoded?", method!(ItsiHttpRequest::is_url_encoded, 0))?;
70
+ request.define_method("multipart?", method!(ItsiHttpRequest::is_multipart, 0))?;
71
+ request.define_method("url_params", method!(ItsiHttpRequest::url_params, 0))?;
72
+
73
+ let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
74
+ body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
75
+ body_proxy.define_method("each", method!(ItsiBodyProxy::each, 0))?;
76
+ body_proxy.define_method("read", method!(ItsiBodyProxy::read, -1))?;
77
+ body_proxy.define_method("close", method!(ItsiBodyProxy::close, 0))?;
78
+
79
+ let response = ruby.get_inner(&ITSI_RESPONSE);
80
+ response.define_method("[]=", method!(ItsiHttpResponse::add_header, 2))?;
81
+ response.define_method("add_header", method!(ItsiHttpResponse::add_header, 2))?;
82
+ response.define_method("add_headers", method!(ItsiHttpResponse::add_headers, 1))?;
83
+ response.define_method("status=", method!(ItsiHttpResponse::set_status, 1))?;
84
+ response.define_method("send_frame", method!(ItsiHttpResponse::send_frame, 1))?;
85
+ response.define_method("<<", method!(ItsiHttpResponse::send_frame, 1))?;
86
+ response.define_method("write", method!(ItsiHttpResponse::send_frame, 1))?;
87
+ response.define_method("read", method!(ItsiHttpResponse::recv_frame, 0))?;
88
+ response.define_method(
89
+ "send_and_close",
90
+ method!(ItsiHttpResponse::send_and_close, 1),
91
+ )?;
92
+ response.define_method("close_write", method!(ItsiHttpResponse::close_write, 0))?;
93
+ response.define_method("close_read", method!(ItsiHttpResponse::close_read, 0))?;
94
+ response.define_method("close", method!(ItsiHttpResponse::close, 0))?;
95
+ response.define_method("hijack", method!(ItsiHttpResponse::hijack, 1))?;
96
+ response.define_method("accept", method!(ItsiHttpResponse::accept_str, 0))?;
97
+ response.define_method("json?", method!(ItsiHttpResponse::is_json, 0))?;
98
+ response.define_method("html?", method!(ItsiHttpResponse::is_html, 0))?;
99
+
100
+ let grpc_call = ruby.get_inner(&ITSI_GRPC_CALL);
101
+ grpc_call.define_method("service_name", method!(ItsiGrpcCall::service_name, 0))?;
102
+ grpc_call.define_method("method_name", method!(ItsiGrpcCall::method_name, 0))?;
103
+ grpc_call.define_method("stream", method!(ItsiGrpcCall::stream, 0))?;
104
+ grpc_call.define_method("json?", method!(ItsiGrpcCall::is_json, 0))?;
105
+ grpc_call.define_method("content_type", method!(ItsiGrpcCall::content_type_str, 0))?;
106
+ grpc_call.define_method("timeout", method!(ItsiGrpcCall::timeout, 0))?;
107
+ grpc_call.define_method("cancelled?", method!(ItsiGrpcCall::is_cancelled, 0))?;
108
+ grpc_call.define_method("add_headers", method!(ItsiGrpcCall::add_headers, 1))?;
109
+ grpc_call.define_method("compress_output", method!(ItsiGrpcCall::compress_output, 1))?;
110
+ grpc_call.define_method(
111
+ "decompress_input",
112
+ method!(ItsiGrpcCall::decompress_input, 1),
113
+ )?;
114
+ grpc_call.define_method(
115
+ "should_compress_output?",
116
+ method!(ItsiGrpcCall::should_compress_output, 1),
117
+ )?;
118
+
119
+ let grpc_response_stream = ruby.get_inner(&ITSI_GRPC_RESPONSE_STREAM);
120
+ grpc_response_stream
121
+ .define_method("reader_fileno", method!(ItsiGrpcResponseStream::reader, 0))?;
122
+ grpc_response_stream.define_method("write", method!(ItsiGrpcResponseStream::write, 1))?;
123
+ grpc_response_stream.define_method("flush", method!(ItsiGrpcResponseStream::flush, 0))?;
124
+ grpc_response_stream.define_method(
125
+ "send_trailers",
126
+ method!(ItsiGrpcResponseStream::send_trailers, 1),
127
+ )?;
128
+ grpc_response_stream.define_method("close", method!(ItsiGrpcResponseStream::close, 0))?;
50
129
 
51
130
  Ok(())
52
131
  }
132
+
133
+ pub fn log_debug(msg: String) {
134
+ debug!(msg);
135
+ }
136
+ pub fn log_info(msg: String) {
137
+ info!(msg);
138
+ }
139
+ pub fn log_warn(msg: String) {
140
+ warn!(msg);
141
+ }
142
+ pub fn log_error(msg: String) {
143
+ error!(msg);
144
+ }
@@ -0,0 +1,2 @@
1
+ pub use itsi_error::*;
2
+ pub use itsi_tracing::{debug, error, info, trace, warn};