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
26 changes: 16 additions & 10 deletions frameworks/fulmine/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ if (cluster.isPrimary) {
const express = require('fulmine.js');
const fs = require('fs');
const zlib = require('zlib');
// level 1: the arena measures throughput of compressed JSON, and the payloads are small
// enough that a higher level buys bytes nobody counts
// json-comp counts the bytes twice over, rps * (minBpr/myBpr)^2, so brotli is preferred where
// the client offers it: q3 is 12% smaller than gzip level 1 here for 24us more per request.
// Gzip stays for a client that asks only for gzip, where the level is not worth the CPU.
const GZIP_OPTS = { level: 1 };
const BROTLI_OPTS = { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 3 } };

const app = express();
app.disable('x-powered-by');
Expand Down Expand Up @@ -164,14 +166,14 @@ if (cluster.isPrimary) {
const body = JSON.stringify({ items, count });
// json-comp profile: negotiated per request, nothing without Accept-Encoding
const ae = req.headers['accept-encoding'] || '';
if (ae.includes('gzip')) {
if (ae.includes('br')) {
res.set({ ...SERVER_HDR, 'content-encoding': 'br' })
.type('application/json')
.send(zlib.brotliCompressSync(body, BROTLI_OPTS));
} else if (ae.includes('gzip')) {
res.set({ ...SERVER_HDR, 'content-encoding': 'gzip' })
.type('application/json')
.send(zlib.gzipSync(body, GZIP_OPTS));
} else if (ae.includes('br')) {
res.set({ ...SERVER_HDR, 'content-encoding': 'br' })
.type('application/json')
.send(zlib.brotliCompressSync(body, { params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 3 } }));
} else {
res.set(SERVER_HDR).type('application/json').send(body);
}
Expand Down Expand Up @@ -336,7 +338,9 @@ if (cluster.isPrimary) {
}
});

app.get('/static/:filename', (req, res) => {
// shared by the plaintext listener and the TLS one on 8081, as the JSON route is: static-tls
// asks for the same files over TLS
const registerStaticRoute = (target) => target.get('/static/:filename', (req, res) => {
const sf = staticFiles[req.params.filename];
if (!sf) return res.status(404).send('Not found');
const ae = req.headers['accept-encoding'] || '';
Expand All @@ -356,6 +360,7 @@ if (cluster.isPrimary) {
res.set(headers).send(buf);
});
});
registerStaticRoute(app);

// WebSocket echo profiles, on µWS's own WebSocket server through the app's uwsApp handle.
// Every connection performs µWS's real upgrade handshake; the echo hands the incoming
Expand All @@ -368,8 +373,8 @@ if (cluster.isPrimary) {
}
});

// json-tls profile: the same JSON route over uWS's native TLS on 8081. The certs are
// mounted by the harness for the TLS profiles; without them there is simply no listener.
// json-tls and static-tls profiles: the same two routes over uWS's native TLS on 8081. The
// certs are mounted by the harness for the TLS profiles; without them there is no listener.
if (fs.existsSync('/certs/server.key') && fs.existsSync('/certs/server.crt')) {
const tlsApp = express({
uwsOptions: {
Expand All @@ -380,6 +385,7 @@ if (cluster.isPrimary) {
tlsApp.disable('x-powered-by');
tlsApp.set('etag', false);
registerJsonRoute(tlsApp);
registerStaticRoute(tlsApp);
tlsApp.listen(8081);
}

Expand Down
43 changes: 43 additions & 0 deletions frameworks/fulmine/compose.gateway-h3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# gateway-h3 - the same two services as compose.gateway.yml, reached over QUIC
# instead. Stock Caddy already binds 8443 on tcp and udp with `protocols h1 h2
# h3`, so the proxy config is identical and only the load generator differs.
# The proxy to server hop stays loopback h1 either way.
services:
proxy:
build:
context: ./proxy
network_mode: host
cpuset: "${PROXY_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:
- server

server:
build:
context: .
dockerfile: Dockerfile
network_mode: host
cpuset: "${SERVER_CPUSET:-16-31,80-95}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
environment:
- DATABASE_URL=${DATABASE_URL}
- DATABASE_MAX_CONN=256
- DATASET_PATH=/data/dataset.json
volumes:
- ${DATA_DIR}/dataset.json:/data/dataset.json:ro
- ${DATA_DIR}/static:/data/static:ro
46 changes: 46 additions & 0 deletions frameworks/fulmine/compose.gateway.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# gateway-64 - stock Caddy (TLS + h2 on 8443) in front of the fulmine server,
# forwarding the dynamic endpoints over loopback h1 with keepalive. The server
# gets no certs here, so it comes up cleartext on 8080 only.
#
# The CPU split is the knob worth sweeping, via PROXY_CPUSET / SERVER_CPUSET.
# Terminating TLS under multiplexed h2 is usually the heavier half, so the
# proxy starts with the same share as the server rather than less.
services:
proxy:
build:
context: ./proxy
network_mode: host
cpuset: "${PROXY_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:
- server

server:
build:
context: .
dockerfile: Dockerfile
network_mode: host
cpuset: "${SERVER_CPUSET:-16-31,80-95}"
ulimits:
memlock: -1
nofile:
soft: 1048576
hard: 1048576
security_opt:
- seccomp:unconfined
environment:
- DATABASE_URL=${DATABASE_URL}
- DATABASE_MAX_CONN=256
- DATASET_PATH=/data/dataset.json
volumes:
- ${DATA_DIR}/dataset.json:/data/dataset.json:ro
- ${DATA_DIR}/static:/data/static:ro
5 changes: 4 additions & 1 deletion frameworks/fulmine/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@
"api-16",
"async-db",
"static",
"static-tls",
"echo-ws",
"echo-ws-pipeline",
"echo-ws-limited",
"json-tls",
"crud"
"crud",
"gateway-64",
"gateway-h3"
],
"maintainers": [
"nigrosimone"
Expand Down
2 changes: 1 addition & 1 deletion frameworks/fulmine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "httparena-fulmine",
"private": true,
"dependencies": {
"fulmine.js": "^5.5.0",
"fulmine.js": "^5.5.2",
"pg": "^8.13.0",
"pg-native": "^3.8.0",
"ioredis": "^5.4.1"
Expand Down
43 changes: 43 additions & 0 deletions frameworks/fulmine/proxy/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# fulmine gateway edge.
#
# Stock Caddy terminates TLS and h1/h2/h3 on 8443, serves /static/* from disk,
# and forwards the dynamic endpoints to the fulmine server. Both services run
# with network_mode: host, so the server is reachable on loopback.
#
# The same file covers gateway-64 and gateway-h3: Caddy binds 8443 on tcp and
# udp both, and only the load generator differs between the two profiles.
{
admin off
auto_https off
servers {
protocols h1 h2 h3
}
}

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

# the profile asks the proxy to serve these from disk rather than forward
# them. precompressed prefers the .br / .gz sidecars per Accept-Encoding.
# (root /data + request /static/foo -> /data/static/foo.)
handle /static/* {
root * /data
file_server {
precompressed br gzip
}
}

# everything else is the fulmine server. Plain h1 with a large keepalive
# pool, so the streams arriving multiplexed over one h2 connection fan out
# across the backend workers instead of queueing behind one upstream.
handle {
reverse_proxy 127.0.0.1:8080 {
transport http {
versions 1.1
keepalive 5m
keepalive_idle_conns 2048
keepalive_idle_conns_per_host 2048
}
}
}
}
10 changes: 10 additions & 0 deletions frameworks/fulmine/proxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Stock Caddy, which ships h1/h2/h3 in the base image, so no custom build. It
# covers gateway-64 (h2 over TCP) and gateway-h3 (QUIC over UDP) from the one
# Caddyfile, which binds 8443 on both.
FROM caddy:2-alpine

COPY Caddyfile /etc/caddy/Caddyfile

EXPOSE 8443/tcp 8443/udp

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