model-context-protocol-rb 0.5.0 → 0.6.0
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 +18 -1
- data/README.md +262 -191
- data/lib/model_context_protocol/server/cancellable.rb +5 -5
- data/lib/model_context_protocol/server/{mcp_logger.rb → client_logger.rb} +7 -10
- data/lib/model_context_protocol/server/configuration.rb +17 -34
- data/lib/model_context_protocol/server/global_config/server_logging.rb +78 -0
- data/lib/model_context_protocol/server/progressable.rb +43 -21
- data/lib/model_context_protocol/server/prompt.rb +12 -7
- data/lib/model_context_protocol/server/redis_pool_manager.rb +1 -1
- data/lib/model_context_protocol/server/resource.rb +7 -4
- data/lib/model_context_protocol/server/router.rb +8 -7
- data/lib/model_context_protocol/server/server_logger.rb +28 -0
- data/lib/model_context_protocol/server/stdio_transport/request_store.rb +17 -17
- data/lib/model_context_protocol/server/stdio_transport.rb +18 -12
- data/lib/model_context_protocol/server/streamable_http_transport/message_poller.rb +9 -9
- data/lib/model_context_protocol/server/streamable_http_transport/request_store.rb +36 -36
- data/lib/model_context_protocol/server/streamable_http_transport/server_request_store.rb +231 -0
- data/lib/model_context_protocol/server/streamable_http_transport.rb +423 -167
- data/lib/model_context_protocol/server/tool.rb +6 -5
- data/lib/model_context_protocol/server.rb +15 -13
- data/lib/model_context_protocol/version.rb +1 -1
- metadata +9 -6
|
@@ -6,10 +6,10 @@ module ModelContextProtocol
|
|
|
6
6
|
POLL_INTERVAL = 0.1 # 100ms
|
|
7
7
|
BATCH_SIZE = 100
|
|
8
8
|
|
|
9
|
-
def initialize(redis_client, stream_registry,
|
|
9
|
+
def initialize(redis_client, stream_registry, client_logger, &message_delivery_block)
|
|
10
10
|
@redis = redis_client
|
|
11
11
|
@stream_registry = stream_registry
|
|
12
|
-
@
|
|
12
|
+
@client_logger = client_logger
|
|
13
13
|
@message_delivery_block = message_delivery_block
|
|
14
14
|
@running = false
|
|
15
15
|
@poll_thread = nil
|
|
@@ -22,14 +22,14 @@ module ModelContextProtocol
|
|
|
22
22
|
@poll_thread = Thread.new do
|
|
23
23
|
poll_loop
|
|
24
24
|
rescue => e
|
|
25
|
-
@
|
|
25
|
+
@client_logger.error("Message poller thread error", error: e.message, backtrace: e.backtrace.first(5))
|
|
26
26
|
sleep 1
|
|
27
27
|
retry if @running
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
@poll_thread.name = "MCP-MessagePoller" if @poll_thread.respond_to?(:name=)
|
|
31
31
|
|
|
32
|
-
@
|
|
32
|
+
@client_logger.debug("Message poller started")
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def stop
|
|
@@ -37,11 +37,11 @@ module ModelContextProtocol
|
|
|
37
37
|
|
|
38
38
|
if @poll_thread&.alive?
|
|
39
39
|
@poll_thread.kill
|
|
40
|
-
@poll_thread.join(
|
|
40
|
+
@poll_thread.join(5)
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
@poll_thread = nil
|
|
44
|
-
@
|
|
44
|
+
@client_logger.debug("Message poller stopped")
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def running?
|
|
@@ -55,7 +55,7 @@ module ModelContextProtocol
|
|
|
55
55
|
begin
|
|
56
56
|
poll_and_deliver_messages
|
|
57
57
|
rescue => e
|
|
58
|
-
@
|
|
58
|
+
@client_logger.error("Error in message polling", error: e.message)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
sleep POLL_INTERVAL
|
|
@@ -91,9 +91,9 @@ module ModelContextProtocol
|
|
|
91
91
|
@message_delivery_block&.call(stream, message)
|
|
92
92
|
rescue IOError, Errno::EPIPE, Errno::ECONNRESET
|
|
93
93
|
@stream_registry.unregister_stream(session_id)
|
|
94
|
-
@
|
|
94
|
+
@client_logger.debug("Unregistered disconnected stream", session_id: session_id)
|
|
95
95
|
rescue => e
|
|
96
|
-
@
|
|
96
|
+
@client_logger.error("Error delivering message to stream",
|
|
97
97
|
session_id: session_id, error: e.message)
|
|
98
98
|
end
|
|
99
99
|
end
|
|
@@ -19,10 +19,10 @@ module ModelContextProtocol
|
|
|
19
19
|
|
|
20
20
|
# Register a new request with its associated session
|
|
21
21
|
#
|
|
22
|
-
# @param
|
|
22
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
23
23
|
# @param session_id [String] the session identifier (can be nil for sessionless requests)
|
|
24
24
|
# @return [void]
|
|
25
|
-
def register_request(
|
|
25
|
+
def register_request(jsonrpc_request_id, session_id = nil)
|
|
26
26
|
request_data = {
|
|
27
27
|
session_id: session_id,
|
|
28
28
|
server_instance: @server_instance,
|
|
@@ -30,11 +30,11 @@ module ModelContextProtocol
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
@redis.multi do |multi|
|
|
33
|
-
multi.set("#{REQUEST_KEY_PREFIX}#{
|
|
33
|
+
multi.set("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
34
34
|
request_data.to_json, ex: @ttl)
|
|
35
35
|
|
|
36
36
|
if session_id
|
|
37
|
-
multi.set("#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
37
|
+
multi.set("#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}",
|
|
38
38
|
true, ex: @ttl)
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -42,34 +42,34 @@ module ModelContextProtocol
|
|
|
42
42
|
|
|
43
43
|
# Mark a request as cancelled
|
|
44
44
|
#
|
|
45
|
-
# @param
|
|
45
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
46
46
|
# @param reason [String] optional reason for cancellation
|
|
47
47
|
# @return [Boolean] true if cancellation was recorded
|
|
48
|
-
def mark_cancelled(
|
|
48
|
+
def mark_cancelled(jsonrpc_request_id, reason = nil)
|
|
49
49
|
cancellation_data = {
|
|
50
50
|
cancelled_at: Time.now.to_f,
|
|
51
51
|
reason: reason
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
result = @redis.set("#{CANCELLED_KEY_PREFIX}#{
|
|
54
|
+
result = @redis.set("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
55
55
|
cancellation_data.to_json, ex: @ttl)
|
|
56
56
|
result == "OK"
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Check if a request has been cancelled
|
|
60
60
|
#
|
|
61
|
-
# @param
|
|
61
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
62
62
|
# @return [Boolean] true if the request is cancelled, false otherwise
|
|
63
|
-
def cancelled?(
|
|
64
|
-
@redis.exists("#{CANCELLED_KEY_PREFIX}#{
|
|
63
|
+
def cancelled?(jsonrpc_request_id)
|
|
64
|
+
@redis.exists("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}") == 1
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# Get cancellation information for a request
|
|
68
68
|
#
|
|
69
|
-
# @param
|
|
69
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
70
70
|
# @return [Hash, nil] cancellation data or nil if not cancelled
|
|
71
|
-
def get_cancellation_info(
|
|
72
|
-
data = @redis.get("#{CANCELLED_KEY_PREFIX}#{
|
|
71
|
+
def get_cancellation_info(jsonrpc_request_id)
|
|
72
|
+
data = @redis.get("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
73
73
|
data ? JSON.parse(data) : nil
|
|
74
74
|
rescue JSON::ParserError
|
|
75
75
|
nil
|
|
@@ -77,13 +77,13 @@ module ModelContextProtocol
|
|
|
77
77
|
|
|
78
78
|
# Unregister a request (typically called when request completes)
|
|
79
79
|
#
|
|
80
|
-
# @param
|
|
80
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
81
81
|
# @return [void]
|
|
82
|
-
def unregister_request(
|
|
83
|
-
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
82
|
+
def unregister_request(jsonrpc_request_id)
|
|
83
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
84
84
|
|
|
85
|
-
keys_to_delete = ["#{REQUEST_KEY_PREFIX}#{
|
|
86
|
-
"#{CANCELLED_KEY_PREFIX}#{
|
|
85
|
+
keys_to_delete = ["#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}",
|
|
86
|
+
"#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}"]
|
|
87
87
|
|
|
88
88
|
if request_data
|
|
89
89
|
begin
|
|
@@ -91,7 +91,7 @@ module ModelContextProtocol
|
|
|
91
91
|
session_id = data["session_id"]
|
|
92
92
|
|
|
93
93
|
if session_id
|
|
94
|
-
keys_to_delete << "#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
94
|
+
keys_to_delete << "#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}"
|
|
95
95
|
end
|
|
96
96
|
rescue JSON::ParserError
|
|
97
97
|
nil
|
|
@@ -103,10 +103,10 @@ module ModelContextProtocol
|
|
|
103
103
|
|
|
104
104
|
# Get information about a specific request
|
|
105
105
|
#
|
|
106
|
-
# @param
|
|
106
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
107
107
|
# @return [Hash, nil] request information or nil if not found
|
|
108
|
-
def get_request(
|
|
109
|
-
data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
108
|
+
def get_request(jsonrpc_request_id)
|
|
109
|
+
data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
110
110
|
data ? JSON.parse(data) : nil
|
|
111
111
|
rescue JSON::ParserError
|
|
112
112
|
nil
|
|
@@ -114,10 +114,10 @@ module ModelContextProtocol
|
|
|
114
114
|
|
|
115
115
|
# Check if a request is currently active
|
|
116
116
|
#
|
|
117
|
-
# @param
|
|
117
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
118
118
|
# @return [Boolean] true if the request is active, false otherwise
|
|
119
|
-
def active?(
|
|
120
|
-
@redis.exists("#{REQUEST_KEY_PREFIX}#{
|
|
119
|
+
def active?(jsonrpc_request_id)
|
|
120
|
+
@redis.exists("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}") == 1
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
# Clean up all requests associated with a session
|
|
@@ -131,20 +131,20 @@ module ModelContextProtocol
|
|
|
131
131
|
return [] if request_keys.empty?
|
|
132
132
|
|
|
133
133
|
# Extract request IDs from the keys
|
|
134
|
-
|
|
134
|
+
jsonrpc_request_ids = request_keys.map do |key|
|
|
135
135
|
key.sub("#{SESSION_KEY_PREFIX}#{session_id}:", "")
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
# Delete all related keys
|
|
139
139
|
all_keys = []
|
|
140
|
-
|
|
141
|
-
all_keys << "#{REQUEST_KEY_PREFIX}#{
|
|
142
|
-
all_keys << "#{CANCELLED_KEY_PREFIX}#{
|
|
140
|
+
jsonrpc_request_ids.each do |jsonrpc_request_id|
|
|
141
|
+
all_keys << "#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}"
|
|
142
|
+
all_keys << "#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}"
|
|
143
143
|
end
|
|
144
144
|
all_keys.concat(request_keys)
|
|
145
145
|
|
|
146
146
|
@redis.del(*all_keys) unless all_keys.empty?
|
|
147
|
-
|
|
147
|
+
jsonrpc_request_ids
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
# Get all active request IDs for a specific session
|
|
@@ -196,21 +196,21 @@ module ModelContextProtocol
|
|
|
196
196
|
|
|
197
197
|
# Refresh the TTL for an active request
|
|
198
198
|
#
|
|
199
|
-
# @param
|
|
199
|
+
# @param jsonrpc_request_id [String] the unique JSON-RPC request identifier
|
|
200
200
|
# @return [Boolean] true if TTL was refreshed, false if request doesn't exist
|
|
201
|
-
def refresh_request_ttl(
|
|
202
|
-
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{
|
|
201
|
+
def refresh_request_ttl(jsonrpc_request_id)
|
|
202
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}")
|
|
203
203
|
return false unless request_data
|
|
204
204
|
|
|
205
205
|
@redis.multi do |multi|
|
|
206
|
-
multi.expire("#{REQUEST_KEY_PREFIX}#{
|
|
207
|
-
multi.expire("#{CANCELLED_KEY_PREFIX}#{
|
|
206
|
+
multi.expire("#{REQUEST_KEY_PREFIX}#{jsonrpc_request_id}", @ttl)
|
|
207
|
+
multi.expire("#{CANCELLED_KEY_PREFIX}#{jsonrpc_request_id}", @ttl)
|
|
208
208
|
|
|
209
209
|
begin
|
|
210
210
|
data = JSON.parse(request_data)
|
|
211
211
|
session_id = data["session_id"]
|
|
212
212
|
if session_id
|
|
213
|
-
multi.expire("#{SESSION_KEY_PREFIX}#{session_id}:#{
|
|
213
|
+
multi.expire("#{SESSION_KEY_PREFIX}#{session_id}:#{jsonrpc_request_id}", @ttl)
|
|
214
214
|
end
|
|
215
215
|
rescue JSON::ParserError
|
|
216
216
|
nil
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module ModelContextProtocol
|
|
4
|
+
class Server::StreamableHttpTransport
|
|
5
|
+
# Redis-based distributed storage for tracking server-initiated requests and their response status.
|
|
6
|
+
# This store is used by StreamableHttpTransport to manage outgoing request lifecycle (like pings)
|
|
7
|
+
# across multiple server instances and handle timeouts in a distributed environment.
|
|
8
|
+
class ServerRequestStore
|
|
9
|
+
REQUEST_KEY_PREFIX = "server_request:pending:"
|
|
10
|
+
SESSION_KEY_PREFIX = "server_request:session:"
|
|
11
|
+
DEFAULT_TTL = 60 # 1 minute TTL for request entries
|
|
12
|
+
|
|
13
|
+
def initialize(redis_client, server_instance, ttl: DEFAULT_TTL)
|
|
14
|
+
@redis = redis_client
|
|
15
|
+
@server_instance = server_instance
|
|
16
|
+
@ttl = ttl
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Register a new server-initiated request with its associated session
|
|
20
|
+
#
|
|
21
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
22
|
+
# @param session_id [String] the session identifier (can be nil for sessionless requests)
|
|
23
|
+
# @param type [Symbol] the type of request (e.g., :ping)
|
|
24
|
+
# @return [void]
|
|
25
|
+
def register_request(request_id, session_id = nil, type: :ping)
|
|
26
|
+
request_data = {
|
|
27
|
+
session_id: session_id,
|
|
28
|
+
server_instance: @server_instance,
|
|
29
|
+
type: type.to_s,
|
|
30
|
+
created_at: Time.now.to_f
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@redis.multi do |multi|
|
|
34
|
+
multi.set("#{REQUEST_KEY_PREFIX}#{request_id}",
|
|
35
|
+
request_data.to_json, ex: @ttl)
|
|
36
|
+
|
|
37
|
+
if session_id
|
|
38
|
+
multi.set("#{SESSION_KEY_PREFIX}#{session_id}:#{request_id}",
|
|
39
|
+
true, ex: @ttl)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Mark a server-initiated request as completed (response received)
|
|
45
|
+
#
|
|
46
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
47
|
+
# @return [Boolean] true if request was pending, false if not found
|
|
48
|
+
def mark_completed(request_id)
|
|
49
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{request_id}")
|
|
50
|
+
return false unless request_data
|
|
51
|
+
|
|
52
|
+
unregister_request(request_id)
|
|
53
|
+
true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if a server-initiated request is still pending
|
|
57
|
+
#
|
|
58
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
59
|
+
# @return [Boolean] true if the request is pending, false otherwise
|
|
60
|
+
def pending?(request_id)
|
|
61
|
+
@redis.exists("#{REQUEST_KEY_PREFIX}#{request_id}") == 1
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get information about a specific pending request
|
|
65
|
+
#
|
|
66
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
67
|
+
# @return [Hash, nil] request information or nil if not found
|
|
68
|
+
def get_request(request_id)
|
|
69
|
+
data = @redis.get("#{REQUEST_KEY_PREFIX}#{request_id}")
|
|
70
|
+
data ? JSON.parse(data) : nil
|
|
71
|
+
rescue JSON::ParserError
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Find requests that have exceeded the specified timeout
|
|
76
|
+
#
|
|
77
|
+
# @param timeout_seconds [Integer] timeout in seconds
|
|
78
|
+
# @return [Array<Hash>] array of expired request info with request_id and session_id
|
|
79
|
+
def get_expired_requests(timeout_seconds)
|
|
80
|
+
current_time = Time.now.to_f
|
|
81
|
+
expired_requests = []
|
|
82
|
+
|
|
83
|
+
# Get all pending request keys
|
|
84
|
+
request_keys = @redis.keys("#{REQUEST_KEY_PREFIX}*")
|
|
85
|
+
return expired_requests if request_keys.empty?
|
|
86
|
+
|
|
87
|
+
# Get all request data in batch
|
|
88
|
+
request_values = @redis.mget(request_keys)
|
|
89
|
+
|
|
90
|
+
request_keys.each_with_index do |key, index|
|
|
91
|
+
next unless request_values[index]
|
|
92
|
+
|
|
93
|
+
begin
|
|
94
|
+
request_data = JSON.parse(request_values[index])
|
|
95
|
+
created_at = request_data["created_at"]
|
|
96
|
+
|
|
97
|
+
if created_at && (current_time - created_at) > timeout_seconds
|
|
98
|
+
request_id = key.sub(REQUEST_KEY_PREFIX, "")
|
|
99
|
+
expired_requests << {
|
|
100
|
+
request_id: request_id,
|
|
101
|
+
session_id: request_data["session_id"],
|
|
102
|
+
type: request_data["type"],
|
|
103
|
+
age: current_time - created_at
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
rescue JSON::ParserError
|
|
107
|
+
# Skip malformed entries
|
|
108
|
+
next
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
expired_requests
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Clean up expired requests based on timeout
|
|
116
|
+
#
|
|
117
|
+
# @param timeout_seconds [Integer] timeout in seconds
|
|
118
|
+
# @return [Array<String>] list of cleaned up request IDs
|
|
119
|
+
def cleanup_expired_requests(timeout_seconds)
|
|
120
|
+
expired_requests = get_expired_requests(timeout_seconds)
|
|
121
|
+
|
|
122
|
+
expired_requests.each do |request_info|
|
|
123
|
+
unregister_request(request_info[:request_id])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
expired_requests.map { |r| r[:request_id] }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Unregister a request (typically called when request completes or times out)
|
|
130
|
+
#
|
|
131
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
132
|
+
# @return [void]
|
|
133
|
+
def unregister_request(request_id)
|
|
134
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{request_id}")
|
|
135
|
+
|
|
136
|
+
keys_to_delete = ["#{REQUEST_KEY_PREFIX}#{request_id}"]
|
|
137
|
+
|
|
138
|
+
if request_data
|
|
139
|
+
begin
|
|
140
|
+
data = JSON.parse(request_data)
|
|
141
|
+
session_id = data["session_id"]
|
|
142
|
+
|
|
143
|
+
if session_id
|
|
144
|
+
keys_to_delete << "#{SESSION_KEY_PREFIX}#{session_id}:#{request_id}"
|
|
145
|
+
end
|
|
146
|
+
rescue JSON::ParserError
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@redis.del(*keys_to_delete) unless keys_to_delete.empty?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Clean up all server requests associated with a session
|
|
155
|
+
# This is typically called when a session is terminated
|
|
156
|
+
#
|
|
157
|
+
# @param session_id [String] the session identifier
|
|
158
|
+
# @return [Array<String>] list of cleaned up request IDs
|
|
159
|
+
def cleanup_session_requests(session_id)
|
|
160
|
+
pattern = "#{SESSION_KEY_PREFIX}#{session_id}:*"
|
|
161
|
+
request_keys = @redis.keys(pattern)
|
|
162
|
+
return [] if request_keys.empty?
|
|
163
|
+
|
|
164
|
+
# Extract request IDs from the keys
|
|
165
|
+
request_ids = request_keys.map do |key|
|
|
166
|
+
key.sub("#{SESSION_KEY_PREFIX}#{session_id}:", "")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Delete all related keys
|
|
170
|
+
all_keys = []
|
|
171
|
+
request_ids.each do |request_id|
|
|
172
|
+
all_keys << "#{REQUEST_KEY_PREFIX}#{request_id}"
|
|
173
|
+
end
|
|
174
|
+
all_keys.concat(request_keys)
|
|
175
|
+
|
|
176
|
+
@redis.del(*all_keys) unless all_keys.empty?
|
|
177
|
+
request_ids
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Get all pending request IDs for a specific session
|
|
181
|
+
#
|
|
182
|
+
# @param session_id [String] the session identifier
|
|
183
|
+
# @return [Array<String>] list of pending request IDs for the session
|
|
184
|
+
def get_session_requests(session_id)
|
|
185
|
+
pattern = "#{SESSION_KEY_PREFIX}#{session_id}:*"
|
|
186
|
+
request_keys = @redis.keys(pattern)
|
|
187
|
+
|
|
188
|
+
request_keys.map do |key|
|
|
189
|
+
key.sub("#{SESSION_KEY_PREFIX}#{session_id}:", "")
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Get all pending request IDs across all sessions
|
|
194
|
+
#
|
|
195
|
+
# @return [Array<String>] list of all pending request IDs
|
|
196
|
+
def get_all_pending_requests
|
|
197
|
+
pattern = "#{REQUEST_KEY_PREFIX}*"
|
|
198
|
+
request_keys = @redis.keys(pattern)
|
|
199
|
+
|
|
200
|
+
request_keys.map do |key|
|
|
201
|
+
key.sub(REQUEST_KEY_PREFIX, "")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Refresh the TTL for a pending request
|
|
206
|
+
#
|
|
207
|
+
# @param request_id [String] the unique JSON-RPC request identifier
|
|
208
|
+
# @return [Boolean] true if TTL was refreshed, false if request doesn't exist
|
|
209
|
+
def refresh_request_ttl(request_id)
|
|
210
|
+
request_data = @redis.get("#{REQUEST_KEY_PREFIX}#{request_id}")
|
|
211
|
+
return false unless request_data
|
|
212
|
+
|
|
213
|
+
@redis.multi do |multi|
|
|
214
|
+
multi.expire("#{REQUEST_KEY_PREFIX}#{request_id}", @ttl)
|
|
215
|
+
|
|
216
|
+
begin
|
|
217
|
+
data = JSON.parse(request_data)
|
|
218
|
+
session_id = data["session_id"]
|
|
219
|
+
if session_id
|
|
220
|
+
multi.expire("#{SESSION_KEY_PREFIX}#{session_id}:#{request_id}", @ttl)
|
|
221
|
+
end
|
|
222
|
+
rescue JSON::ParserError
|
|
223
|
+
nil
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
true
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|