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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +3937 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +141 -46
- data/ext/itsi_error/Cargo.toml +3 -0
- data/ext/itsi_error/src/lib.rs +98 -24
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- 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
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +72 -14
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/default_responses/html/401.html +68 -0
- data/ext/itsi_server/src/default_responses/html/403.html +68 -0
- data/ext/itsi_server/src/default_responses/html/404.html +68 -0
- data/ext/itsi_server/src/default_responses/html/413.html +71 -0
- data/ext/itsi_server/src/default_responses/html/429.html +68 -0
- data/ext/itsi_server/src/default_responses/html/500.html +71 -0
- data/ext/itsi_server/src/default_responses/html/502.html +71 -0
- data/ext/itsi_server/src/default_responses/html/503.html +68 -0
- data/ext/itsi_server/src/default_responses/html/504.html +69 -0
- data/ext/itsi_server/src/default_responses/html/index.html +238 -0
- data/ext/itsi_server/src/default_responses/json/401.json +6 -0
- data/ext/itsi_server/src/default_responses/json/403.json +6 -0
- data/ext/itsi_server/src/default_responses/json/404.json +6 -0
- data/ext/itsi_server/src/default_responses/json/413.json +6 -0
- data/ext/itsi_server/src/default_responses/json/429.json +6 -0
- data/ext/itsi_server/src/default_responses/json/500.json +6 -0
- data/ext/itsi_server/src/default_responses/json/502.json +6 -0
- data/ext/itsi_server/src/default_responses/json/503.json +6 -0
- data/ext/itsi_server/src/default_responses/json/504.json +6 -0
- data/ext/itsi_server/src/default_responses/mod.rs +11 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +132 -40
- data/ext/itsi_server/src/prelude.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +345 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +375 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +83 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
- data/ext/itsi_server/src/server/binds/bind.rs +201 -0
- data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/binds/listener.rs +432 -0
- data/ext/itsi_server/src/server/binds/mod.rs +4 -0
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/binds/tls.rs +270 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/http_message_types.rs +97 -0
- data/ext/itsi_server/src/server/io_stream.rs +105 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +165 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +56 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +86 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +285 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +142 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +289 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +292 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +157 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +201 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +87 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +414 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +131 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +44 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +36 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +180 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +163 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +347 -0
- data/ext/itsi_server/src/server/mod.rs +12 -5
- data/ext/itsi_server/src/server/process_worker.rs +247 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +342 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +76 -0
- data/ext/itsi_server/src/server/size_limited_incoming.rs +101 -0
- data/ext/itsi_server/src/server/thread_worker.rs +475 -0
- data/ext/itsi_server/src/services/cache_store.rs +74 -0
- data/ext/itsi_server/src/services/itsi_http_service.rs +239 -0
- data/ext/itsi_server/src/services/mime_types.rs +1416 -0
- data/ext/itsi_server/src/services/mod.rs +6 -0
- data/ext/itsi_server/src/services/password_hasher.rs +83 -0
- data/ext/itsi_server/src/services/rate_limiter.rs +569 -0
- data/ext/itsi_server/src/services/static_file_server.rs +1324 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +315 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
- data/lib/itsi/http_request.rb +186 -0
- data/lib/itsi/http_response.rb +41 -0
- data/lib/itsi/passfile.rb +109 -0
- data/lib/itsi/server/config/dsl.rb +565 -0
- data/lib/itsi/server/config.rb +166 -0
- data/lib/itsi/server/default_app/default_app.rb +34 -0
- data/lib/itsi/server/default_app/index.html +115 -0
- data/lib/itsi/server/default_config/Itsi-rackup.rb +119 -0
- data/lib/itsi/server/default_config/Itsi.rb +107 -0
- data/lib/itsi/server/grpc/grpc_call.rb +246 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +100 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
- data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/route_tester.rb +107 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
- data/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
- data/lib/itsi/server/typed_handlers.rb +17 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +160 -9
- data/lib/itsi/standard_headers.rb +86 -0
- data/lib/ruby_lsp/itsi/addon.rb +111 -0
- data/lib/shell_completions/completions.rb +26 -0
- metadata +182 -25
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/bind.rs +0 -138
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/listener.rs +0 -218
- data/ext/itsi_server/src/server/tls.rs +0 -138
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
@@ -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
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
3
|
+
# source: reflection.proto
|
4
|
+
|
5
|
+
require 'google/protobuf'
|
6
|
+
require "google/protobuf/descriptor_pb"
|
7
|
+
|
8
|
+
descriptor_data = "\n\x10reflection.proto\x12\x12grpc.reflection.v1\"\x85\x02\n\x17ServerReflectionRequest\x12\x0c\n\x04host\x18\x01 \x01(\t\x12\x1a\n\x10\x66ile_by_filename\x18\x03 \x01(\tH\x00\x12 \n\x16\x66ile_containing_symbol\x18\x04 \x01(\tH\x00\x12I\n\x19\x66ile_containing_extension\x18\x05 \x01(\x0b\x32$.grpc.reflection.v1.ExtensionRequestH\x00\x12\'\n\x1d\x61ll_extension_numbers_of_type\x18\x06 \x01(\tH\x00\x12\x17\n\rlist_services\x18\x07 \x01(\tH\x00\x42\x11\n\x0fmessage_request\"E\n\x10\x45xtensionRequest\x12\x17\n\x0f\x63ontaining_type\x18\x01 \x01(\t\x12\x18\n\x10\x65xtension_number\x18\x02 \x01(\x05\"\xb8\x03\n\x18ServerReflectionResponse\x12\x12\n\nvalid_host\x18\x01 \x01(\t\x12\x45\n\x10original_request\x18\x02 \x01(\x0b\x32+.grpc.reflection.v1.ServerReflectionRequest\x12N\n\x18\x66ile_descriptor_response\x18\x04 \x01(\x0b\x32*.grpc.reflection.v1.FileDescriptorResponseH\x00\x12U\n\x1e\x61ll_extension_numbers_response\x18\x05 \x01(\x0b\x32+.grpc.reflection.v1.ExtensionNumberResponseH\x00\x12I\n\x16list_services_response\x18\x06 \x01(\x0b\x32\'.grpc.reflection.v1.ListServiceResponseH\x00\x12;\n\x0e\x65rror_response\x18\x07 \x01(\x0b\x32!.grpc.reflection.v1.ErrorResponseH\x00\x42\x12\n\x10message_response\"7\n\x16\x46ileDescriptorResponse\x12\x1d\n\x15\x66ile_descriptor_proto\x18\x01 \x03(\x0c\"K\n\x17\x45xtensionNumberResponse\x12\x16\n\x0e\x62\x61se_type_name\x18\x01 \x01(\t\x12\x18\n\x10\x65xtension_number\x18\x02 \x03(\x05\"K\n\x13ListServiceResponse\x12\x34\n\x07service\x18\x01 \x03(\x0b\x32#.grpc.reflection.v1.ServiceResponse\"\x1f\n\x0fServiceResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\":\n\rErrorResponse\x12\x12\n\nerror_code\x18\x01 \x01(\x05\x12\x15\n\rerror_message\x18\x02 \x01(\t2\x89\x01\n\x10ServerReflection\x12u\n\x14ServerReflectionInfo\x12+.grpc.reflection.v1.ServerReflectionRequest\x1a,.grpc.reflection.v1.ServerReflectionResponse(\x01\x30\x01\x42\x66\n\x15io.grpc.reflection.v1B\x15ServerReflectionProtoP\x01Z4google.golang.org/grpc/reflection/grpc_reflection_v1b\x06proto3"
|
9
|
+
|
10
|
+
pool = Google::Protobuf::DescriptorPool.generated_pool
|
11
|
+
pool.add_serialized_file(descriptor_data)
|
12
|
+
|
13
|
+
module Grpc
|
14
|
+
module Reflection
|
15
|
+
module V1
|
16
|
+
ServerReflectionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ServerReflectionRequest").msgclass
|
17
|
+
ExtensionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ExtensionRequest").msgclass
|
18
|
+
ServerReflectionResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ServerReflectionResponse").msgclass
|
19
|
+
FileDescriptorResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.FileDescriptorResponse").msgclass
|
20
|
+
ExtensionNumberResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ExtensionNumberResponse").msgclass
|
21
|
+
ListServiceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ListServiceResponse").msgclass
|
22
|
+
ServiceResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ServiceResponse").msgclass
|
23
|
+
ErrorResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.reflection.v1.ErrorResponse").msgclass
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# Source: reflection.proto for package 'grpc.reflection.v1'
|
3
|
+
# Original file comments:
|
4
|
+
# Copyright 2016 The gRPC Authors
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
# Service exported by server reflection. A more complete description of how
|
19
|
+
# server reflection works can be found at
|
20
|
+
# https://github.com/grpc/grpc/blob/master/doc/server-reflection.md
|
21
|
+
#
|
22
|
+
# The canonical version of this proto can be found at
|
23
|
+
# https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto
|
24
|
+
#
|
25
|
+
|
26
|
+
require "grpc"
|
27
|
+
require_relative "reflection_pb"
|
28
|
+
|
29
|
+
module Grpc
|
30
|
+
module Reflection
|
31
|
+
module V1
|
32
|
+
module ServerReflection
|
33
|
+
class Service
|
34
|
+
include ::GRPC::GenericService
|
35
|
+
|
36
|
+
self.marshal_class_method = :encode
|
37
|
+
self.unmarshal_class_method = :decode
|
38
|
+
self.service_name = "grpc.reflection.v1.ServerReflection"
|
39
|
+
|
40
|
+
# The reflection service is structured as a bidirectional stream, ensuring
|
41
|
+
# all related requests go to a single server.
|
42
|
+
rpc :ServerReflectionInfo, stream(::Grpc::Reflection::V1::ServerReflectionRequest),
|
43
|
+
stream(::Grpc::Reflection::V1::ServerReflectionResponse)
|
44
|
+
|
45
|
+
def initialize(handlers)
|
46
|
+
@handlers = handlers
|
47
|
+
super()
|
48
|
+
end
|
49
|
+
|
50
|
+
def server_reflection_info(req, _unused_call)
|
51
|
+
req.each do |request|
|
52
|
+
res = Grpc::Reflection::V1::ServerReflectionResponse.new
|
53
|
+
|
54
|
+
if !request.list_services.empty?
|
55
|
+
res.list_services_response = Grpc::Reflection::V1::ListServiceResponse.new(service: list_services_response)
|
56
|
+
elsif !request.file_containing_symbol.empty?
|
57
|
+
res.file_descriptor_response = Grpc::Reflection::V1::FileDescriptorResponse.new(
|
58
|
+
file_descriptor_proto: [
|
59
|
+
Google::Protobuf::FileDescriptorProto.encode(Google::Protobuf::DescriptorPool.generated_pool.lookup(request.file_containing_symbol).file_descriptor.to_proto)
|
60
|
+
]
|
61
|
+
)
|
62
|
+
elsif !request.file_by_filename.empty?
|
63
|
+
# Handle file_by_filename requests
|
64
|
+
file_descriptor = find_file_descriptor_by_filename(request.file_by_filename)
|
65
|
+
if file_descriptor
|
66
|
+
res.file_descriptor_response = Grpc::Reflection::V1::FileDescriptorResponse.new(
|
67
|
+
file_descriptor_proto: [Google::Protobuf::FileDescriptorProto.encode(file_descriptor)]
|
68
|
+
)
|
69
|
+
else
|
70
|
+
res.error_response = Grpc::Reflection::V1::ErrorResponse.new(
|
71
|
+
error_code: 5, # NOT_FOUND
|
72
|
+
error_message: "File not found: #{request.file_by_filename}"
|
73
|
+
)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
yield res
|
77
|
+
# We can loop here if running in Fiber mode, but for best compatibility with blocking IO modes
|
78
|
+
# we'll close the connection and force the client to reconnect force
|
79
|
+
# subsequent reflection requests
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_file_descriptor_by_filename(filename)
|
85
|
+
# First try direct lookup in the pool
|
86
|
+
|
87
|
+
descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(filename)&.file_descriptor
|
88
|
+
return descriptor.to_proto if descriptor
|
89
|
+
|
90
|
+
proto_name = convert_file_path_to_proto_name(filename)
|
91
|
+
descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(proto_name)&.file_descriptor
|
92
|
+
return descriptor.to_proto if descriptor
|
93
|
+
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def convert_file_path_to_proto_name(file_path)
|
98
|
+
# Remove .proto extension
|
99
|
+
file_path = file_path.sub(/\.proto$/, "")
|
100
|
+
|
101
|
+
# Split path into parts
|
102
|
+
parts = file_path.split("/")
|
103
|
+
|
104
|
+
# Convert last part to PascalCase (e.g., money -> Money)
|
105
|
+
parts[-1] = parts[-1].split("_").map(&:capitalize).join
|
106
|
+
|
107
|
+
# Join with dots
|
108
|
+
parts.join(".")
|
109
|
+
end
|
110
|
+
|
111
|
+
def list_services_response
|
112
|
+
@list_services_response ||= @handlers.map(&:class).map(&:service_name).map do |name|
|
113
|
+
Grpc::Reflection::V1::ServiceResponse.new(name: name)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
Stub = Service.rpc_stub_class
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
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
|