Node.js: Gateway — ACL + Dedup
Bu sayfada ACL (I₁) ve Dedup / İdempotency (I₂) Node.js ile nasıl uygulanır gösterilir.
Gereksinimler
bash
npm install ws nats redis jsonwebtoken- Redis: Dedup set (ve isteğe bağlı ACL için key'ler).
- JWT: CONNECT/setup'ta token doğrulama.
ACL Store (Bellek + Redis Örneği)
javascript
const redis = require('redis');
const redisClient = redis.createClient({ url: process.env.REDIS_URL || 'redis://localhost:6379' });
async function aclAllow(userId, topic, action) {
const key = `acl:${userId}:${action}`;
const allowed = await redisClient.sMembers(key);
const exact = allowed.includes(topic);
const wildcard = allowed.some((t) => t.endsWith('*') && topic.startsWith(t.slice(0, -1)));
return exact || wildcard;
}
// Örnek veri (uygulama başlangıcında veya admin API ile):
// await redisClient.sAdd('acl:user1:read', 'risk.alerts', 'prices.*');
// await redisClient.sAdd('acl:user1:write', 'orders');Dedup: Redis Set ile
javascript
const DEDUP_TTL = 3600; // 1 saat
async function isDuplicate(userId, messageId) {
const key = `dedup:${userId}`;
const added = await redisClient.sAdd(key, messageId);
if (added === 0) return true;
await redisClient.expire(key, DEDUP_TTL);
return false;
}Gateway Handler: SUBSCRIBE + ACL
javascript
async function handleSubscribe(ws, msg) {
const { topic } = msg;
if (!ws.userId) {
ws.send(JSON.stringify({ type: 'error', code: 'AUTH_REQUIRED' }));
return;
}
const allowed = await aclAllow(ws.userId, topic, 'read');
if (!allowed) {
ws.send(JSON.stringify({ type: 'error', code: 'ACL_DENIED', topic }));
return;
}
addSubscriber(ws, topic);
if (!ws.topics) ws.topics = new Set();
ws.topics.add(topic);
ws.send(JSON.stringify({ type: 'subscribed', topic }));
}Gateway Handler: PUBLISH + ACL + Dedup
javascript
async function handlePublish(ws, msg) {
const { topic, messageId, payload } = msg;
if (!ws.userId) {
ws.send(JSON.stringify({ type: 'error', code: 'AUTH_REQUIRED' }));
return;
}
const allowed = await aclAllow(ws.userId, topic, 'write');
if (!allowed) {
ws.send(JSON.stringify({ type: 'error', code: 'ACL_DENIED', topic }));
return;
}
const dup = await isDuplicate(ws.userId, messageId);
if (dup) {
ws.send(JSON.stringify({ type: 'ack', messageId, status: 'duplicate' }));
return;
}
const seqNo = await nextSeqNo(topic);
const payloadWithSeq = { ...payload, seqNo, messageId };
nc.publish(topic, Buffer.from(JSON.stringify(payloadWithSeq)));
ws.send(JSON.stringify({ type: 'ack', messageId, status: 'ok' }));
}SeqNo: Redis INCR
javascript
async function nextSeqNo(topic) {
const key = `seq:${topic}`;
return await redisClient.incr(key);
}Ana Mesaj Yönlendirme
javascript
ws.on('message', async (data) => {
try {
const msg = JSON.parse(data.toString());
if (msg.type === 'setup' || msg.type === 'connect') {
ws.userId = getUserIdFromToken(msg.token);
if (!ws.userId) {
ws.send(JSON.stringify({ type: 'error', code: 'AUTH_FAILED' }));
return;
}
ws.send(JSON.stringify({ type: 'ready' }));
return;
}
if (msg.type === 'subscribe') {
await handleSubscribe(ws, msg);
return;
}
if (msg.type === 'publish') {
await handlePublish(ws, msg);
return;
}
} catch (err) {
ws.send(JSON.stringify({ type: 'error', message: err.message }));
}
});Akış Özeti (Mermaid)
Tam çalışan örnek: Tam E2E Örnek.