@zintrust/redis-rpc 2.4.3 → 2.4.4
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.
- package/dist/server.js +158 -22
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -18,33 +18,169 @@ const json = (response, status, payload) => {
|
|
|
18
18
|
});
|
|
19
19
|
response.end(JSON.stringify(payload));
|
|
20
20
|
};
|
|
21
|
+
const getHeaderSecret = (request) => {
|
|
22
|
+
const raw = request.headers['x-redis-rpc-secret'];
|
|
23
|
+
if (Array.isArray(raw)) {
|
|
24
|
+
return raw[0] ?? '';
|
|
25
|
+
}
|
|
26
|
+
return typeof raw === 'string' ? raw : '';
|
|
27
|
+
};
|
|
28
|
+
const previewSecret = (value) => value.slice(0, 5);
|
|
29
|
+
const logStep = (step, details) => {
|
|
30
|
+
Logger.debug(`[redis-rpc][server] ${step}`, details);
|
|
31
|
+
};
|
|
32
|
+
const getRequestContext = (request, settings) => {
|
|
33
|
+
const method = request.method ?? '';
|
|
34
|
+
const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
|
|
35
|
+
const headerSecret = getHeaderSecret(request);
|
|
36
|
+
const settingsSecret = settings.secret || '';
|
|
37
|
+
return {
|
|
38
|
+
method,
|
|
39
|
+
url,
|
|
40
|
+
headerSecret,
|
|
41
|
+
settingsSecret,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const logRequestReceived = (context) => {
|
|
45
|
+
logStep('request.received', {
|
|
46
|
+
method: context.method,
|
|
47
|
+
path: context.url.pathname,
|
|
48
|
+
host: context.url.host,
|
|
49
|
+
headerSecretPreview: previewSecret(context.headerSecret),
|
|
50
|
+
settingsSecretPreview: previewSecret(context.settingsSecret),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const isHealthRoute = (context) => context.method === 'GET' && context.url.pathname === '/health';
|
|
54
|
+
const isRpcRoute = (context) => context.method === 'POST' && context.url.pathname === '/rpc';
|
|
55
|
+
const logHealthRoute = (prefix, path) => {
|
|
56
|
+
logStep('route.health', {
|
|
57
|
+
path,
|
|
58
|
+
prefix,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const logRouteCheck = (context) => {
|
|
62
|
+
logStep('route.check', {
|
|
63
|
+
method: context.method,
|
|
64
|
+
path: context.url.pathname,
|
|
65
|
+
isRpcRoute: isRpcRoute(context),
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const logRouteNotFound = (context) => {
|
|
69
|
+
logStep('route.notFound', {
|
|
70
|
+
method: context.method,
|
|
71
|
+
path: context.url.pathname,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
const validateRequestSecret = (context) => {
|
|
75
|
+
logStep('secret.validate.start', {
|
|
76
|
+
headerSecretPreview: previewSecret(context.headerSecret),
|
|
77
|
+
settingsSecretPreview: previewSecret(context.settingsSecret),
|
|
78
|
+
});
|
|
79
|
+
const secretMatches = context.settingsSecret !== '' && context.headerSecret === context.settingsSecret;
|
|
80
|
+
logStep('secret.validate.result', {
|
|
81
|
+
headerSecretPreview: previewSecret(context.headerSecret),
|
|
82
|
+
settingsSecretPreview: previewSecret(context.settingsSecret),
|
|
83
|
+
secretMatches,
|
|
84
|
+
});
|
|
85
|
+
return secretMatches;
|
|
86
|
+
};
|
|
87
|
+
const logSecretValidationFailure = (context) => {
|
|
88
|
+
logStep('secret.validate.failed', {
|
|
89
|
+
headerSecretPreview: previewSecret(context.headerSecret),
|
|
90
|
+
settingsSecretPreview: previewSecret(context.settingsSecret),
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const readRpcBody = async (request) => {
|
|
94
|
+
logStep('body.read.start', {});
|
|
95
|
+
const bodyText = await readBody(request);
|
|
96
|
+
logStep('body.read.complete', {
|
|
97
|
+
length: bodyText.length,
|
|
98
|
+
isEmpty: bodyText.trim().length === 0,
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
const body = (bodyText.trim().length === 0 ? {} : JSON.parse(bodyText));
|
|
102
|
+
logStep('body.parse.complete', {
|
|
103
|
+
requestId: body.requestId ?? null,
|
|
104
|
+
service: String(body.service || ''),
|
|
105
|
+
method: String(body.method || ''),
|
|
106
|
+
hasPayload: body.payload !== undefined,
|
|
107
|
+
});
|
|
108
|
+
return body;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logStep('body.parse.failed', {
|
|
112
|
+
error: error instanceof Error ? error.message : String(error),
|
|
113
|
+
});
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const dispatchRpcRequest = async (backend, body) => {
|
|
118
|
+
logStep('dispatch.start', {
|
|
119
|
+
requestId: body.requestId ?? null,
|
|
120
|
+
service: String(body.service || ''),
|
|
121
|
+
method: String(body.method || ''),
|
|
122
|
+
});
|
|
123
|
+
const result = await backend.dispatch(String(body.service || ''), String(body.method || ''), body.payload ?? {});
|
|
124
|
+
logStep('dispatch.complete', {
|
|
125
|
+
requestId: body.requestId ?? null,
|
|
126
|
+
service: String(body.service || ''),
|
|
127
|
+
method: String(body.method || ''),
|
|
128
|
+
});
|
|
129
|
+
return result;
|
|
130
|
+
};
|
|
131
|
+
const sendRpcSuccess = (response, requestId, result) => {
|
|
132
|
+
json(response, 200, { ok: true, requestId, result, error: null });
|
|
133
|
+
logStep('response.sent', {
|
|
134
|
+
status: 200,
|
|
135
|
+
requestId,
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const handleRequestError = (response, error) => {
|
|
139
|
+
const payload = toErrorPayload(error);
|
|
140
|
+
logStep('request.error', {
|
|
141
|
+
status: payload.status,
|
|
142
|
+
message: payload.body && typeof payload.body === 'object' && 'message' in payload.body
|
|
143
|
+
? String(payload.body.message ?? '')
|
|
144
|
+
: '',
|
|
145
|
+
});
|
|
146
|
+
json(response, payload.status, payload.body);
|
|
147
|
+
};
|
|
148
|
+
const handleRpcRequest = async (request, response, context, backend) => {
|
|
149
|
+
logRouteCheck(context);
|
|
150
|
+
if (!isRpcRoute(context)) {
|
|
151
|
+
logRouteNotFound(context);
|
|
152
|
+
throw createRpcNotFoundError('Unknown Redis RPC route');
|
|
153
|
+
}
|
|
154
|
+
if (!validateRequestSecret(context)) {
|
|
155
|
+
logSecretValidationFailure(context);
|
|
156
|
+
throw createRpcUnauthorizedError('Invalid Redis RPC secret');
|
|
157
|
+
}
|
|
158
|
+
const body = await readRpcBody(request);
|
|
159
|
+
const result = await dispatchRpcRequest(backend, body);
|
|
160
|
+
sendRpcSuccess(response, body.requestId ?? null, result);
|
|
161
|
+
};
|
|
162
|
+
const handleHealthRequest = (response, backend) => {
|
|
163
|
+
logHealthRoute(backend.prefix, '/health');
|
|
164
|
+
json(response, 200, { ok: true, service: 'redis-rpc', prefix: backend.prefix });
|
|
165
|
+
};
|
|
166
|
+
const handleIncomingRequest = async (request, response, settings, backend) => {
|
|
167
|
+
const context = getRequestContext(request, settings);
|
|
168
|
+
logRequestReceived(context);
|
|
169
|
+
if (isHealthRoute(context)) {
|
|
170
|
+
handleHealthRequest(response, backend);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
await handleRpcRequest(request, response, context, backend);
|
|
174
|
+
};
|
|
21
175
|
export const createRedisRpcServer = (options = {}) => {
|
|
22
176
|
const settings = { ...rpcServerOptions(), ...options };
|
|
23
177
|
const backend = isObject(options.backend)
|
|
24
178
|
? options.backend
|
|
25
179
|
: createRedisRpcBackend(settings);
|
|
26
|
-
const server = http.createServer(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
json(response, 200, { ok: true, service: 'redis-rpc', prefix: backend.prefix });
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (request.method !== 'POST' || url.pathname !== '/rpc') {
|
|
34
|
-
throw createRpcNotFoundError('Unknown Redis RPC route');
|
|
35
|
-
}
|
|
36
|
-
if (!settings.secret || request.headers['x-redis-rpc-secret'] !== settings.secret) {
|
|
37
|
-
throw createRpcUnauthorizedError('Invalid Redis RPC secret');
|
|
38
|
-
}
|
|
39
|
-
const bodyText = await readBody(request);
|
|
40
|
-
const body = (bodyText.trim().length === 0 ? {} : JSON.parse(bodyText));
|
|
41
|
-
const result = await backend.dispatch(String(body.service || ''), String(body.method || ''), body.payload ?? {});
|
|
42
|
-
json(response, 200, { ok: true, requestId: body.requestId ?? null, result, error: null });
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
const payload = toErrorPayload(error);
|
|
46
|
-
json(response, payload.status, payload.body);
|
|
47
|
-
}
|
|
180
|
+
const server = http.createServer((request, response) => {
|
|
181
|
+
void handleIncomingRequest(request, response, settings, backend).catch((error) => {
|
|
182
|
+
handleRequestError(response, error);
|
|
183
|
+
});
|
|
48
184
|
});
|
|
49
185
|
return { server, backend, settings };
|
|
50
186
|
};
|