itsi-server 0.1.1 → 0.1.13

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.

Potentially problematic release.


This version of itsi-server might be problematic. Click here for more details.

Files changed (143) 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 +4417 -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 +94 -45
  10. data/ext/itsi_error/Cargo.toml +2 -0
  11. data/ext/itsi_error/src/from.rs +68 -0
  12. data/ext/itsi_error/src/lib.rs +18 -34
  13. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  14. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  15. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  16. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  17. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  18. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  19. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  20. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  21. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  22. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  23. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  24. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  25. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  26. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  27. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  28. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  29. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  30. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  31. data/ext/itsi_rb_helpers/src/lib.rs +140 -10
  32. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  33. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  34. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  35. 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
  36. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  37. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  38. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  39. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  40. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  41. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  42. data/ext/itsi_scheduler/Cargo.toml +24 -0
  43. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  44. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  45. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  46. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  47. data/ext/itsi_scheduler/src/lib.rs +38 -0
  48. data/ext/itsi_server/Cargo.lock +2956 -0
  49. data/ext/itsi_server/Cargo.toml +73 -13
  50. data/ext/itsi_server/extconf.rb +1 -1
  51. data/ext/itsi_server/src/env.rs +43 -0
  52. data/ext/itsi_server/src/lib.rs +100 -40
  53. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
  59. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
  60. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
  61. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
  62. data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
  63. data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
  64. data/ext/itsi_server/src/server/bind.rs +75 -31
  65. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/cache_store.rs +74 -0
  68. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  69. data/ext/itsi_server/src/server/itsi_service.rs +172 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/listener.rs +332 -132
  72. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
  97. data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
  98. data/ext/itsi_server/src/server/mod.rs +15 -2
  99. data/ext/itsi_server/src/server/process_worker.rs +229 -0
  100. data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
  101. data/ext/itsi_server/src/server/request_job.rs +11 -0
  102. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
  103. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  104. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
  105. data/ext/itsi_server/src/server/signal.rs +93 -0
  106. data/ext/itsi_server/src/server/static_file_server.rs +984 -0
  107. data/ext/itsi_server/src/server/thread_worker.rs +444 -0
  108. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  109. data/ext/itsi_server/src/server/tls.rs +187 -60
  110. data/ext/itsi_server/src/server/types.rs +43 -0
  111. data/ext/itsi_tracing/Cargo.toml +5 -0
  112. data/ext/itsi_tracing/src/lib.rs +225 -7
  113. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  114. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  115. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  116. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  117. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  118. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  119. data/lib/itsi/http_request.rb +87 -0
  120. data/lib/itsi/http_response.rb +39 -0
  121. data/lib/itsi/server/Itsi.rb +119 -0
  122. data/lib/itsi/server/config/dsl.rb +506 -0
  123. data/lib/itsi/server/config.rb +131 -0
  124. data/lib/itsi/server/default_app/default_app.rb +38 -0
  125. data/lib/itsi/server/default_app/index.html +91 -0
  126. data/lib/itsi/server/grpc_interface.rb +213 -0
  127. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  128. data/lib/itsi/server/rack_interface.rb +94 -0
  129. data/lib/itsi/server/scheduler_interface.rb +21 -0
  130. data/lib/itsi/server/scheduler_mode.rb +10 -0
  131. data/lib/itsi/server/signal_trap.rb +29 -0
  132. data/lib/itsi/server/version.rb +1 -1
  133. data/lib/itsi/server.rb +90 -9
  134. data/lib/itsi/standard_headers.rb +86 -0
  135. metadata +122 -31
  136. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  137. data/ext/itsi_server/src/request/mod.rs +0 -1
  138. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  139. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  140. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  141. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  142. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  143. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,91 @@
1
+ <!DOCTYPE html>
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>
91
+ </html>
@@ -0,0 +1,213 @@
1
+ module Itsi
2
+ class Server
3
+ class GrpcActiveCall
4
+ attr_reader :stream, :method_rpc, :output_metadata, :method_name
5
+
6
+ def initialize(stream, method_rpc, service_class, method_name)
7
+ @stream = stream
8
+ @method_rpc = method_rpc
9
+ @service_class = service_class
10
+ @output_metadata = {}
11
+ @method_name = method_name
12
+ end
13
+ end
14
+
15
+ class GrpcInterface
16
+ def self.for(services)
17
+ interface = new(services)
18
+ lambda do |request|
19
+ interface.handle_request(request)
20
+ end
21
+ end
22
+
23
+ def initialize(services = [])
24
+ @services = {}
25
+ register_services(services)
26
+ end
27
+
28
+ def register_services(services)
29
+ services.each do |service|
30
+ service_class = service.class
31
+ @services[service_class.service_name] = {
32
+ implementation: service,
33
+ class: service_class
34
+ }
35
+ end
36
+ end
37
+
38
+ def handle_request(request)
39
+ service_info = @services[request.service_name]
40
+ raise "Unknown service: #{request.service_name}" unless service_info
41
+
42
+ service_impl = service_info[:implementation]
43
+ service_class = service_info[:class]
44
+
45
+ method_rpc = RpcDescWrapper.new(service_class, request.content_type, request.method_name)
46
+
47
+ active_call = GrpcActiveCall.new(request.stream, method_rpc, service_class, request.method_name)
48
+
49
+ if method_rpc.bidi_streamer?
50
+ puts "Calling bidirectional streaming handler"
51
+ handle_bidi_streaming(active_call, service_impl)
52
+ elsif method_rpc.client_streamer?
53
+ puts "Calling client streaming handler"
54
+ handle_client_streaming(active_call, service_impl)
55
+ elsif method_rpc.server_streamer?
56
+ puts "Calling server streaming handler"
57
+ handle_server_streaming(active_call, service_impl)
58
+ elsif method_rpc.request_response?
59
+ handle_unary(active_call, service_impl)
60
+ end
61
+ active_call.stream.send_trailers({ "grpc-status" => "0" })
62
+ rescue StandardError => e
63
+ active_call.stream.send_trailers({ "grpc-status" => "13", "grpc-message" => e.message })
64
+ end
65
+
66
+ private
67
+
68
+ # Read a framed message from the stream
69
+ def read_message(stream)
70
+ # Read the gRPC frame header (5 bytes)
71
+ header = stream.read(5)
72
+ return nil if header.nil? || header.bytesize < 5
73
+
74
+ compressed = header.bytes[0] == 1
75
+ length = header[1..4].unpack1("N")
76
+
77
+ # Read the message body
78
+ stream.read(length)
79
+ end
80
+
81
+ # Send a response
82
+ def send_response(active_call, response)
83
+ response_data = active_call.method_rpc.marshal_response(response)
84
+ send_framed_message(active_call.stream, response_data)
85
+ end
86
+
87
+ # Send a framed message
88
+ def send_framed_message(stream, message_data, compressed = false)
89
+ compressed_flag = compressed ? 1 : 0
90
+ header = [compressed_flag, message_data.bytesize].pack("CN")
91
+
92
+ stream.write(header)
93
+ stream.write(message_data)
94
+ stream.flush
95
+ end
96
+
97
+ # Create an enumerator for client streaming requests
98
+ def create_request_enum(active_call)
99
+ Enumerator.new do |yielder|
100
+ loop do
101
+ message_data = read_message(active_call.stream)
102
+ break if message_data.nil?
103
+
104
+ request = active_call.method_rpc.unmarshal_request(message_data)
105
+ yielder << request
106
+ end
107
+ end
108
+ end
109
+
110
+ # Handlers for different RPC types
111
+
112
+ def handle_unary(active_call, service)
113
+ # Read the request message
114
+ message_data = read_message(active_call.stream)
115
+ request = active_call.method_rpc.unmarshal_request(message_data)
116
+
117
+ # Call the service implementation
118
+ underscore_method = GRPC::GenericService.underscore(active_call.method_name)
119
+ response = service.send(underscore_method, request, active_call)
120
+
121
+ # Send response
122
+ send_response(active_call, response)
123
+ end
124
+
125
+ def handle_client_streaming(active_call, service)
126
+ # Create an enumerable to read the incoming stream
127
+ request_enum = create_request_enum(active_call)
128
+
129
+ # Call the service implementation
130
+ underscore_method = GRPC::GenericService.underscore(active_call.method_name)
131
+ response = service.send(underscore_method, request_enum, active_call)
132
+
133
+ # Send response
134
+ send_response(active_call, response)
135
+ end
136
+
137
+ def handle_server_streaming(active_call, service)
138
+ # Read the request message
139
+ message_data = read_message(active_call.stream)
140
+ request = active_call.method_rpc.unmarshal_request(message_data)
141
+
142
+ # Call the service implementation with a block to handle streaming
143
+ underscore_method = GRPC::GenericService.underscore(active_call.method_name)
144
+ service.send(underscore_method, request, active_call) do |response|
145
+ send_response(active_call, response)
146
+ end
147
+ end
148
+
149
+ def handle_bidi_streaming(active_call, service)
150
+ # Create an enumerable to read the incoming stream
151
+ request_enum = create_request_enum(active_call)
152
+
153
+ # Call the service implementation with a block
154
+ underscore_method = GRPC::GenericService.underscore(active_call.method_name)
155
+ service.send(underscore_method, request_enum, active_call) do |response|
156
+ send_response(active_call, response)
157
+ end
158
+ end
159
+ end
160
+
161
+ class RpcDescWrapper
162
+ def initialize(service_class, content_type, method_name)
163
+ rpc_descs = service_class.rpc_descs
164
+ @rpc_desc = rpc_descs[method_name.to_sym]
165
+ raise "Method not found: #{method_name}" unless @rpc_desc
166
+
167
+ @input_type = @rpc_desc.input
168
+ @content_type = content_type
169
+ @input_is_stream = @input_type.is_a?(GRPC::RpcDesc::Stream)
170
+ @input_type = @input_type.type if @input_is_stream
171
+
172
+ @output_type = @rpc_desc.output
173
+ @output_is_stream = @output_type.is_a?(GRPC::RpcDesc::Stream)
174
+ @output_type = @output_type.type if @output_is_stream
175
+
176
+ @client_streamer = @input_is_stream
177
+ @server_streamer = @output_is_stream
178
+ end
179
+
180
+ def client_streamer?
181
+ @client_streamer
182
+ end
183
+
184
+ def server_streamer?
185
+ @server_streamer
186
+ end
187
+
188
+ def bidi_streamer?
189
+ @client_streamer && @server_streamer
190
+ end
191
+
192
+ def request_response?
193
+ !@client_streamer && !@server_streamer
194
+ end
195
+
196
+ def unmarshal_request(data)
197
+ if @content_type == "application/json"
198
+ @input_type.decode_json(data)
199
+ else
200
+ @input_type.decode(data)
201
+ end
202
+ end
203
+
204
+ def marshal_response(response)
205
+ if @content_type == "application/json"
206
+ response.to_json
207
+ else
208
+ response.to_proto
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,27 @@
1
+ return unless defined?(::Rackup::Handler) || defined?(Rack::Handler)
2
+
3
+ module Rack
4
+ module Handler
5
+ module Itsi
6
+ def self.run(app, options = {})
7
+ host = options.fetch(:host, "127.0.0.1")
8
+ port = options.fetch(:Port, 3001)
9
+ Itsi::Server.start(
10
+ {
11
+ app: app,
12
+ binds: ["http://#{host}:#{port}"]
13
+ },
14
+ Itsi::Server::Config.config_file_path
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ if defined?(Rackup)
22
+ ::Rackup::Handler.register("itsi", Rack::Handler::Itsi)
23
+ ::Rackup::Handler.register("Itsi", Rack::Handler::Itsi)
24
+ elsif defined?(Rack)
25
+ ::Rack::Handler.register("itsi", Rack::Handler::Itsi)
26
+ ::Rack::Handler.register("Itsi", Rack::Handler::Itsi)
27
+ end
@@ -0,0 +1,94 @@
1
+ module Itsi
2
+ class Server
3
+ module RackInterface
4
+
5
+ # Builds a handler proc that is compatible with Rack applications.
6
+ def self.for(app)
7
+ require "rack"
8
+ if app.is_a?(String)
9
+ dir = File.expand_path(File.dirname(app))
10
+ Dir.chdir(dir) do
11
+ loaded_app = ::Rack::Builder.parse_file(app)
12
+ app = loaded_app.is_a?(Array) ? loaded_app.first : loaded_app
13
+ end
14
+ end
15
+ lambda do |request|
16
+ Server.respond(request, app.call(request.to_rack_env))
17
+ end
18
+ end
19
+
20
+ # Interface to Rack applications.
21
+ # Here we build the env, and invoke the Rack app's call method.
22
+ # We then turn the Rack response into something Itsi server understands.
23
+ def call(app, request)
24
+ respond request, app.call(request.to_rack_env)
25
+ end
26
+
27
+ # Itsi responses are asynchronous and can be streamed.
28
+ # Response chunks are sent using response.send_frame
29
+ # and the response is finished using response.close_write.
30
+ # If only a single chunk is written, you can use the #send_and_close method.
31
+ def respond(request, (status, headers, body))
32
+ response = request.response
33
+
34
+ # Don't try and respond if we've been hijacked.
35
+ # The hijacker is now responsible for this.
36
+ return if request.hijacked
37
+
38
+ # 1. Set Status
39
+ response.status = status
40
+
41
+ # 2. Set Headers
42
+ body_streamer = streaming_body?(body) ? body : headers.delete("rack.hijack")
43
+ headers.each do |key, value|
44
+ unless value.is_a?(Array)
45
+ response[key] = value
46
+ next
47
+ end
48
+
49
+ value.each do |v|
50
+ response[key] = v
51
+ end
52
+ end
53
+
54
+ # 3. Set Body
55
+ # As soon as we start setting the response
56
+ # the server will begin to stream it to the client.
57
+
58
+ # If we're partially hijacked or returned a streaming body,
59
+ # stream this response.
60
+
61
+ if body_streamer
62
+ body_streamer.call(response)
63
+
64
+ # If we're enumerable with more than one chunk
65
+ # also stream, otherwise write in a single chunk
66
+ elsif body.respond_to?(:each) || body.respond_to?(:to_ary)
67
+ unless body.respond_to?(:each)
68
+ body = body.to_ary
69
+ raise "Body #to_ary didn't return an array" unless body.is_a?(Array)
70
+ end
71
+ # We offset this iteration intentionally,
72
+ # to optimize for the case where there's only one chunk.
73
+ buffer = nil
74
+ body.each do |part|
75
+ response << buffer.to_s if buffer
76
+ buffer = part
77
+ end
78
+
79
+ response.send_and_close(buffer.to_s)
80
+ else
81
+ response.send_and_close(body.to_s)
82
+ end
83
+ ensure
84
+ response.close_write
85
+ body.close if body.respond_to?(:close)
86
+ end
87
+
88
+ # A streaming body is one that responds to #call and not #each.
89
+ def streaming_body?(body)
90
+ body.respond_to?(:call) && !body.respond_to?(:each)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,21 @@
1
+ module Itsi
2
+ class Server
3
+ module SchedulerInterface
4
+ # Simple wrapper to instantiate a scheduler, start it,
5
+ # and immediate have it invoke a scheduler proc
6
+ def start_scheduler_loop(scheduler_class, scheduler_task)
7
+ scheduler = scheduler_class.new
8
+ Fiber.set_scheduler(scheduler)
9
+ [scheduler, Fiber.schedule(&scheduler_task)]
10
+ end
11
+
12
+ # When running in scheduler mode,
13
+ # each request is wrapped in a Fiber.
14
+ def schedule(app, request)
15
+ Fiber.schedule do
16
+ app.call(request)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ # Running with a Fiber scheduler enabled but with an ActiveSupport isolation_level set to Thread
2
+ # can be dangerous. A thread isolation level means that all fibers sharing a thread can content
3
+ # for the same resources, which can lead to race conditions.
4
+ # This hook should *only* be disabled if you know there are no such shared resources.
5
+ if defined?(ActiveSupport::IsolatedExecutionState) && !ENV["ITSI_DISABLE_AS_AUTO_FIBER_ISOLATION_LEVEL"]
6
+ Itsi.log_info \
7
+ "ActiveSupport Isolated Execution state detected. Automatically switching to :fiber mode. "\
8
+ "Set ITSI_DISABLE_AS_AUTO_FIBER_ISOLATION_LEVEL to disable this behavior"
9
+ ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
10
+ end
@@ -0,0 +1,29 @@
1
+ module Itsi
2
+ # This trap is necessary for debuggers and similar which intercept certain signals
3
+ # then attempt to restore these to the previous signal when finished.
4
+ # If the previous signal handler was registered in native code, this restoration doesn't
5
+ # work as expected and the native signal handler is lost.
6
+ # We intercept restored signals here and reinstate the Itsi server signal handlers
7
+ # (if the server is still running).
8
+ module SignalTrap
9
+ DEFAULT_SIGNALS = ["DEFAULT", "", nil].freeze
10
+ INTERCEPTED_SIGNALS = ["INT"].freeze
11
+
12
+ def trap(signal, *args, &block)
13
+ unless INTERCEPTED_SIGNALS.include?(signal.to_s) && block.nil? && Itsi::Server.running?
14
+ return super(signal, *args, &block)
15
+ end
16
+
17
+ Itsi::Server.reset_signal_handlers
18
+ nil
19
+ end
20
+ end
21
+ end
22
+
23
+ [Kernel, Signal].each do |receiver|
24
+ receiver.singleton_class.prepend(Itsi::SignalTrap)
25
+ end
26
+
27
+ [Object].each do |receiver|
28
+ receiver.include(Itsi::SignalTrap)
29
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Server
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.13"
6
6
  end
7
7
  end
data/lib/itsi/server.rb CHANGED
@@ -2,19 +2,100 @@
2
2
 
3
3
  require_relative "server/version"
4
4
  require_relative "server/itsi_server"
5
+ require_relative "server/rack_interface"
6
+ require_relative "server/grpc_interface"
7
+ require_relative "server/scheduler_interface"
8
+ require_relative "server/signal_trap"
9
+ require_relative "server/rack/handler/itsi"
10
+ require_relative "server/config"
11
+ require_relative "standard_headers"
12
+ require_relative "http_request"
13
+ require_relative "http_response"
5
14
 
6
15
  module Itsi
7
16
  class Server
8
- # Call our Rack app with our request ENV.
9
- def self.call(app, request)
10
- app.call(request.to_env)
11
- end
17
+ extend RackInterface
18
+ extend SchedulerInterface
19
+
20
+ class << self
21
+ def running?
22
+ !!@running
23
+ end
24
+
25
+ def start_in_background_thread(cli_params={}, itsi_file = Itsi::Server::Config.config_file_path, &blk)
26
+ @background_thread = start(cli_params, itsi_file, background: true, &blk)
27
+ end
28
+
29
+ def start(cli_params, itsi_file = Itsi::Server::Config.config_file_path, background: false, &blk)
30
+ server = new(cli_params, itsi_file, blk)
31
+ previous_handler = Signal.trap(:INT, :DEFAULT)
32
+ run = lambda do
33
+ write_pid
34
+ @running = server
35
+ server.start
36
+ Signal.trap(:INT, previous_handler)
37
+ server
38
+ end
39
+ background ? Thread.new(&run) : run[]
40
+ end
41
+
42
+ def stop_background_thread
43
+ @running&.stop
44
+ @running = nil
45
+ @background_thread&.join
46
+ end
47
+
48
+ def write_pid
49
+ File.write(Itsi::Server::Config.pid_file_path, Process.pid)
50
+ end
51
+
52
+ def get_pid
53
+ pid = File.read(Itsi::Server::Config.pid_file_path).to_i
54
+ if Process.kill(0, pid)
55
+ pid
56
+ else
57
+ nil
58
+ end
59
+ rescue StandardError
60
+ nil
61
+ end
62
+
63
+ def reload
64
+ return unless pid = get_pid
65
+
66
+ Process.kill(:USR2, pid)
67
+ end
68
+
69
+ def restart
70
+ return unless pid = get_pid
71
+
72
+ Process.kill(:USR1, pid)
73
+ end
74
+
75
+ def down
76
+ puts :down
77
+ end
78
+
79
+ def up
80
+ puts :up
81
+ end
82
+
83
+ def add_worker
84
+ return unless pid = get_pid
85
+
86
+ Process.kill(:TTIN, pid)
87
+ end
88
+
89
+ def remove_worker
90
+ return unless pid = get_pid
91
+
92
+ Process.kill(:TTOU, pid)
93
+ end
94
+
95
+ def status
96
+ return unless pid = get_pid
12
97
 
13
- # If scheduler is enabled
14
- # Each request is wrapped in a Fiber.
15
- def self.schedule(app, request)
16
- Fiber.schedule do
17
- call(app, request)
98
+ Process.kill(:INFO, pid)
18
99
  end
19
100
  end
20
101
  end
@@ -0,0 +1,86 @@
1
+ module Itsi
2
+ module StandardHeaders
3
+ ALL = [
4
+ ACCEPT = "accept",
5
+ ACCEPT_CHARSET = "accept-charset",
6
+ ACCEPT_ENCODING = "accept-encoding",
7
+ ACCEPT_LANGUAGE = "accept-language",
8
+ ACCEPT_RANGES = "accept-ranges",
9
+ ACCESS_CONTROL_ALLOW_CREDENTIALS = "access-control-allow-credentials",
10
+ ACCESS_CONTROL_ALLOW_HEADERS = "access-control-allow-headers",
11
+ ACCESS_CONTROL_ALLOW_METHODS = "access-control-allow-methods",
12
+ ACCESS_CONTROL_ALLOW_ORIGIN = "access-control-allow-origin",
13
+ ACCESS_CONTROL_EXPOSE_HEADERS = "access-control-expose-headers",
14
+ ACCESS_CONTROL_MAX_AGE = "access-control-max-age",
15
+ ACCESS_CONTROL_REQUEST_HEADERS = "access-control-request-headers",
16
+ ACCESS_CONTROL_REQUEST_METHOD = "access-control-request-method",
17
+ AGE = "age",
18
+ ALLOW = "allow",
19
+ ALT_SVC = "alt-svc",
20
+ AUTHORIZATION = "authorization",
21
+ CACHE_CONTROL = "cache-control",
22
+ CACHE_STATUS = "cache-status",
23
+ CDN_CACHE_CONTROL = "cdn-cache-control",
24
+ CONNECTION = "connection",
25
+ CONTENT_DISPOSITION = "content-disposition",
26
+ CONTENT_ENCODING = "content-encoding",
27
+ CONTENT_LANGUAGE = "content-language",
28
+ CONTENT_LENGTH = "content-length",
29
+ CONTENT_LOCATION = "content-location",
30
+ CONTENT_RANGE = "content-range",
31
+ CONTENT_SECURITY_POLICY_REPORT_ONLY = "content-security-policy-report-only",
32
+ CONTENT_TYPE = "content-type",
33
+ COOKIE = "cookie",
34
+ DNT = "dnt",
35
+ DATE = "date",
36
+ ETAG = "etag",
37
+ EXPECT = "expect",
38
+ EXPIRES = "expires",
39
+ FORWARDED = "forwarded",
40
+ FROM = "from",
41
+ HOST = "host",
42
+ IF_MATCH = "if-match",
43
+ IF_MODIFIED_SINCE = "if-modified-since",
44
+ IF_NONE_MATCH = "if-none-match",
45
+ IF_RANGE = "if-range",
46
+ IF_UNMODIFIED_SINCE = "if-unmodified-since",
47
+ LAST_MODIFIED = "last-modified",
48
+ LINK = "link",
49
+ LOCATION = "location",
50
+ MAX_FORWARDS = "max-forwards",
51
+ ORIGIN = "origin",
52
+ PRAGMA = "pragma",
53
+ PROXY_AUTHENTICATE = "proxy-authenticate",
54
+ PROXY_AUTHORIZATION = "proxy-authorization",
55
+ PUBLIC_KEY_PINS = "public-key-pins",
56
+ PUBLIC_KEY_PINS_REPORT_ONLY = "public-key-pins-report-only",
57
+ RANGE = "range",
58
+ REFERER = "referer",
59
+ REFERRER_POLICY = "referrer-policy",
60
+ REFRESH = "refresh",
61
+ RETRY_AFTER = "retry-after",
62
+ SEC_WEBSOCKET_ACCEPT = "sec-websocket-accept",
63
+ SEC_WEBSOCKET_EXTENSIONS = "sec-websocket-extensions",
64
+ SEC_WEBSOCKET_KEY = "sec-websocket-key",
65
+ SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol",
66
+ SEC_WEBSOCKET_VERSION = "sec-websocket-version",
67
+ SERVER = "server",
68
+ SET_COOKIE = "set-cookie",
69
+ STRICT_TRANSPORT_SECURITY = "strict-transport-security",
70
+ TE = "te",
71
+ TRAILER = "trailer",
72
+ TRANSFER_ENCODING = "transfer-encoding",
73
+ USER_AGENT = "user-agent",
74
+ UPGRADE = "upgrade",
75
+ UPGRADE_INSECURE_REQUESTS = "upgrade-insecure-requests",
76
+ VARY = "vary",
77
+ VIA = "via",
78
+ WARNING = "warning",
79
+ WWW_AUTHENTICATE = "www-authenticate",
80
+ X_CONTENT_TYPE_OPTIONS = "x-content-type-options",
81
+ X_DNS_PREFETCH_CONTROL = "x-dns-prefetch-control",
82
+ X_FRAME_OPTIONS = "x-frame-options",
83
+ X_XSS_PROTECTION = "x-xss-protection",
84
+ ]
85
+ end
86
+ end