Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 76 additions & 3 deletions frameworks/fulmine/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ if (cluster.isPrimary) {
});

// shared by the plaintext listener and the TLS one on 8081: same handler, same shapes
const registerJsonRoute = (target) => target.get('/json/:count', (req, res) => {
const registerJsonRoute = (target, path = '/json/:count') => target.get(path, (req, res) => {
if (datasetItems) {
let count = parseInt(req.params.count, 10) || 0;
if (count < 0) count = 0;
Expand Down Expand Up @@ -242,7 +242,9 @@ if (cluster.isPrimary) {
}
});

app.get('/crud/items/:id', async (req, res) => {
// the cache-aside read, registered under /crud for the crud profile and under /api for
// production-stack, which asks for the same thing behind the edge's JWT check
const itemRead = async (req, res) => {
if (!pgPool) return res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"DB not available"}');
const id = parseInt(req.params.id, 10);
if (!Number.isFinite(id)) return res.status(404).set(SERVER_HDR).end();
Expand All @@ -263,7 +265,8 @@ if (cluster.isPrimary) {
} catch (e) {
res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"query failed"}');
}
});
};
app.get('/crud/items/:id', itemRead);

app.post('/crud/items', (req, res) => {
if (!pgPool) return res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"DB not available"}');
Expand Down Expand Up @@ -310,6 +313,76 @@ if (cluster.isPrimary) {
});
});

// ── production-stack ──────────────────────────────────────────────────
// Four services, and this is the server behind them. The edge terminates TLS, serves
// /static/* itself and sends /api/* past the shared JWT verifier first, so nothing here
// checks a token: what arrives is already authorised and carries X-User-Id.
const USER_TTL_MS = 30000;
const userGet = (id) => {
if (redis) return redis.get('user:' + id);
const hit = crudCache.get('user:' + id);
if (!hit) return null;
if (hit.until <= Date.now()) { crudCache.delete('user:' + id); return null; }
return hit.json;
};
const userSet = (id, json) => {
if (redis) return redis.set('user:' + id, json, 'PX', USER_TTL_MS);
crudCache.set('user:' + id, { json, until: Date.now() + USER_TTL_MS });
};

app.get('/public/baseline', (req, res) => {
res.set(SERVER_HDR).type('text/plain').send(String(sumQuery(req.query)));
});
registerJsonRoute(app, '/public/json/:count');
app.get('/api/items/:id', itemRead);

// 204 and no body, unlike the crud PUT this otherwise mirrors. The cache entry goes after
// the row is written, so the next read misses and repopulates from Postgres.
app.post('/api/items/:id', (req, res) => {
if (!pgPool) return res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"DB not available"}');
const id = parseInt(req.params.id, 10);
if (!Number.isFinite(id)) return res.status(404).set(SERVER_HDR).end();
readJsonBody(req, async (err, body) => {
if (err) return res.status(400).set(SERVER_HDR).end();
try {
const result = await pgPool.query({
name: 'crud-update',
text: 'UPDATE items SET name = $1, price = $2, quantity = $3 WHERE id = $4',
values: [body.name ?? 'Updated', body.price ?? 0, body.quantity ?? 0, id]
});
if (result.rowCount === 0) return res.status(404).set(SERVER_HDR).end();
await crudDel(id);
res.status(204).set(SERVER_HDR).end();
} catch (e) {
res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"update failed"}');
}
});
});

app.get('/api/me', async (req, res) => {
if (!pgPool) return res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"DB not available"}');
const id = parseInt(req.headers['x-user-id'], 10);
if (!Number.isFinite(id)) return res.status(401).set(SERVER_HDR).end();
try {
const cached = await userGet(id);
if (cached) {
return res.set(CACHE_HIT_HDR).type('application/json').send(cached);
}
const result = await pgPool.query({
name: 'user-read',
text: 'SELECT id, name, email, plan FROM users WHERE id = $1 LIMIT 1',
values: [id]
});
if (result.rows.length === 0) return res.status(404).set(SERVER_HDR).end();
const u = result.rows[0];
const json = JSON.stringify({ id: u.id, name: u.name, email: u.email, plan: u.plan });
userSet(id, json);
res.set(CACHE_MISS_HDR).type('application/json').send(json);
} catch (e) {
res.status(500).set(SERVER_HDR).type('application/json').send('{"error":"query failed"}');
}
});

app.post('/upload', (req, res) => {
let size = 0;
req.on('data', chunk => size += chunk.length);
Expand Down
86 changes: 86 additions & 0 deletions frameworks/fulmine/compose.production-stack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# production-stack: edge + cache + authsvc + server, on a 64 logical CPU budget.
#
# edge (caddy) - 32, terminates TLS and h2 and serves /static off disk
# cache (redis) - 2, single threaded on the data path, so more would sit idle
# authsvc (rust) - 8, one HMAC per /api request and nothing else
# server (node) - 22, the rest: JSON, Postgres, Redis client, one worker per core
#
# The edge gets the larger half because it is what saturates: the first run gave the whole stack
# 1537% of CPU against the 1600% its sixteen logical CPUs allow, so the edge was pegged and the
# other three sat idle. Terminating TLS under multiplexed h2 is the expensive half here, and it is
# the knob to sweep first through EDGE_CPUSET and SERVER_CPUSET.
#
# All four on host networking, so what goes between them is loopback rather than a bridge.
# Redis binds 6379, authsvc 9090, server 8080, the edge 8443.
services:
edge:
build: ./proxy-production
network_mode: host
cpuset: "${EDGE_CPUSET:-0-15,64-79}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
volumes:
- ${CERTS_DIR}:/certs:ro
- ${DATA_DIR}/static:/data/static:ro
depends_on:
- authsvc
- server

cache:
image: redis:7-alpine
network_mode: host
cpuset: "${CACHE_CPUSET:-16,80}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
volumes:
- ${DATA_DIR}/redis-seed.txt:/seed.txt:ro
- ${DATA_DIR}/redis-entrypoint.sh:/entrypoint.sh:ro
entrypoint: ["sh", "/entrypoint.sh"]

authsvc:
build: ../_shared/authsvc
network_mode: host
cpuset: "${AUTH_CPUSET:-17-20,81-84}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
environment:
- JWT_SECRET=httparena-bench-secret-do-not-use-in-production
- AUTHSVC_LISTEN=0.0.0.0:9090

server:
build:
context: .
dockerfile: Dockerfile
network_mode: host
cpuset: "${SERVER_CPUSET:-21-31,85-95}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
environment:
- DATABASE_URL=${DATABASE_URL}
- DATABASE_MAX_CONN=256
- REDIS_URL=redis://127.0.0.1:6379
- DATASET_PATH=/data/dataset.json
volumes:
- ${DATA_DIR}/dataset.json:/data/dataset.json:ro
depends_on:
- cache
3 changes: 2 additions & 1 deletion frameworks/fulmine/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"json-tls",
"crud",
"gateway-64",
"gateway-h3"
"gateway-h3",
"production-stack"
],
"maintainers": [
"nigrosimone"
Expand Down
52 changes: 52 additions & 0 deletions frameworks/fulmine/proxy-production/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# fulmine production-stack edge.
#
# Caddy terminates TLS and h2 on 8443 and splits the three paths the profile defines:
# /static/* off disk here, /public/* straight to the server, /api/* past the shared JWT
# verifier first. The server itself never sees a token, only the X-User-Id that survives.

{
admin off
auto_https off
servers {
protocols h1 h2
}
}

https://localhost:8443 {
tls /certs/server.crt /certs/server.key

handle /static/* {
root * /data
file_server {
precompressed br gzip
}
}

handle /public/* {
reverse_proxy 127.0.0.1:8080 {
transport http {
versions 1.1
keepalive 5m
keepalive_idle_conns 2048
keepalive_idle_conns_per_host 2048
}
}
}

# authsvc answers 200 with X-User-Id or 401, and forward_auth passes its refusal straight
# back, so an unauthenticated /api/* never reaches the server at all.
handle /api/* {
forward_auth 127.0.0.1:9090 {
uri /_auth
copy_headers X-User-Id
}
reverse_proxy 127.0.0.1:8080 {
transport http {
versions 1.1
keepalive 5m
keepalive_idle_conns 2048
keepalive_idle_conns_per_host 2048
}
}
}
}
8 changes: 8 additions & 0 deletions frameworks/fulmine/proxy-production/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Stock Caddy again, same as the gateway edge, with the production-stack routing.
FROM caddy:2-alpine

COPY Caddyfile /etc/caddy/Caddyfile

EXPOSE 8443/tcp

CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
Loading