El servidor Ganchos, registros y límites
Ganchos, registros y límites
Lo que el servidor dice de sí mismo, y lo que deja hacer a cada uno con qué frecuencia. Cada cosa es una opción de
createDriveHandler, y cada fallo tuyo se registra, no es el de la petición.En esta página
Qué se hace, qué falló, cuánto tardó
import pino from 'pino';
import { metrics } from '@opentelemetry/api';
import { createDriveHandler, openTelemetry } from '@rebofiles/server';
const log = pino();
createDriveHandler({
// …
// Each thing done: a webhook, an audit of your own, another index, space counted.
onChange: async ({ activity, entry }) => {
await fetch(process.env.WEBHOOK!, { method: 'POST', body: JSON.stringify({ ...activity, size: entry?.kind === 'file' ? entry.versions[0].size : 0 }) });
},
logger: { error: (message, detail) => log.error({ detail }, message), warn: (message, detail) => log.warn({ detail }, message) },
onRequest: openTelemetry(metrics.getMeter('rebofiles')),
});- A
onChangese le dice cada cosa hecha, como la dice la actividad (created,uploaded,version,renamed,moved,trashed,restored,deleted,shared,commented,held…), con la entrada como queda, null si se borró. loggeres donde el servidor escribe lo que no respondió: una actividad no guardada, un gancho que falló, un archivo que no se pudo revisar.consolesi no se da.- A
onRequestse le dice cada ruta como la dice la lista (GET /entries/:id), su estado, su tiempo y quién preguntó.openTelemetry(meter)lo hace el histogramahttp.server.request.durationy un contador de los 5xx.
Cuánto puede pedir cada uno
| Tipo | Por | Si no se dice |
|---|---|---|
| uploads | persona | 1000 por minuto |
| zip | persona, y enlace | 30 por minuto |
| searchText | persona | 120 por minuto |
| linkPassword | enlace y dirección | 10 erróneas en 15 minutos, después ni la correcta |
| links | enlace | 600 por minuto |
| requests | persona | ninguno |
import { createDriveHandler } from '@rebofiles/server';
createDriveHandler({
// …
limits: {
uploads: { max: 200, every: 60_000 }, // starting an upload, by person
requests: { max: 1200, every: 60_000 }, // every request, by person
},
// Wrong passwords of a link counted by the address too, behind a proxy you trust.
clientIp: (request) => request.headers.get('x-forwarded-for')?.split(',')[0].trim(),
});Pasado uno, la respuesta es un 429 rate-limited con Retry-After, y el drive lo dice en el idioma de cada persona. limits: false para ninguno.
Webhooks
Cada cambio avisado a otro servicio por HTTP, como onChange se lo dice a tu código: sin retrasar nunca la petición que lo hizo.
import { everyChange, verifyWebhook, webhook } from '@rebofiles/server';
createDriveHandler({
...options,
// Each change POSTed as JSON, signed, tried again three times: only these actions, or all.
onChange: everyChange(
webhook({ url: 'https://crm.example/hooks/files', secret: process.env.HOOK_SECRET!, actions: ['uploaded', 'version', 'deleted'] }),
({ activity }) => audit(activity),
),
});
// Where it arrives: the body as it came, checked before it is trusted.
const ok = verifyWebhook(process.env.HOOK_SECRET!, await request.text(), request.headers);- El cuerpo:
id(el mismo en cada intento),action,at,by, laentryresumida y laactivity. CabecerasX-Rebofiles-Event,X-Rebofiles-Delivery,X-Rebofiles-TimestampyX-Rebofiles-Signature, un HMAC-SHA256 de la marca de tiempo y el cuerpo. - Se reintenta al segundo y a los cuatro, pero no cuando se rechaza por lo que es (4xx); a
onFailurese le avisa del que no llegó.
Más de un proceso
Las cuentas se guardan en el proceso, salvo que un `limiter` tuyo las guarde para todos: unas líneas sobre Redis.
limiter.ts
import { Redis } from 'ioredis';
import type { Limiter } from '@rebofiles/server';
const redis = new Redis(process.env.REDIS_URL!);
/** The same windows for every process: a count per key, gone when its window is. */
export const redisLimiter: Limiter = {
async hit(key, limit, how) {
if (how?.peek) {
const [count, ttl] = await Promise.all([redis.get(key), redis.pttl(key)]);
return Number(count) >= limit.max ? { ok: false, retryAfter: ttl } : { ok: true, retryAfter: 0 };
}
const count = await redis.incr(key);
if (count === 1) await redis.pexpire(key, limit.every);
return count <= limit.max ? { ok: true, retryAfter: 0 } : { ok: false, retryAfter: await redis.pttl(key) };
},
};