Zum Inhalt springen
Dokumentation auf Deutsch

Übersetzung der englischen Originalseite. Bei Abweichungen gilt die englische Fassung.

Fehler und problem+json

Fehler sind in DaloyJS ein vollwertiger Bestandteil. Jeder geworfene HttpError wird zu problem+json nach RFC 9457 serialisiert, mit einer stabilen type-URI, einer Request-ID und dem passenden Content-Type.

Vom geworfenen Fehler zu problem+json
  1. 01Handlerthrow new NotFoundError(...)beliebige Unterklasse von HttpError
  2. 02FrameworkAuf Felder nach RFC 9457 abbildentype · title · status · detail
  3. 03Produktion5xx-Details schwärzennur sichtbar, wenn env development oder test ist
  4. 04ÜbertragungSerialisierte Antwortapplication/problem+json + x-request-id
Ein geworfener HttpError wird automatisch zu einem problem+json-Dokument. Sofern die Umgebung nicht ausdrücklich development oder test ist, wird das Feld detail bei 5xx-Antworten geschwärzt, damit Stacktraces und SQL-Fragmente nie beim Client ankommen, während der vollständige Fehler weiterhin an Ihren onError-Hook geht.

Eingebaute Fehlerklassen

ts
import {
  HttpError,                         // Basisklasse: beliebiger Status, eigene type/title/headers
  BadRequestError,                   // 400
  UnauthorizedError,                 // 401 + cache-control: no-store
  ForbiddenError,                    // 403 + cache-control: no-store
  NotFoundError,                     // 404
  MethodNotAllowedError,             // 405 + Allow-Header
  RequestTimeoutError,               // 408 / 503 / 504 + cache-control: no-store
  ConflictError,                     // 409 + cache-control: no-store
  PayloadTooLargeError,              // 413
  UnsupportedMediaTypeError,         // 415
  ValidationError,                   // 422
  TooManyRequestsError,              // 429 + Retry-After + cache-control: no-store
  RequestHeaderFieldsTooLargeError,  // 431
  InternalError,                     // 500 (detail außerhalb von dev/test geschwärzt)
  ServiceUnavailableError,           // 503 + Retry-After + cache-control: no-store
} from "@daloyjs/core";

TooManyRequestsError und ServiceUnavailableError validieren retryAfterSeconds auf dieselbe Weise: Alles außer einer nicht negativen sicheren Ganzzahl löst bei der Konstruktion einen RangeError aus, damit ein NaN aus einer fehlenden Umgebungsvariable nie zu einem ungültigen Retry-After-Header wird. RequestTimeoutError (die Frist aus requestTimeoutMs, siehe requestTimeoutStatus, auf Englisch) ist seit 1.16.0 für jede Statusvariante no-store, damit ein gemeinsamer Cache nicht weiter ein 503/504 ausliefert, nachdem sich das Backend erholt hat. Ein bloßes new HttpError(status, …) erhält als Problem-type die URI https://httpstatuses.io/<status>; die eingebauten Klassen tragen stabile URIs der Form https://daloyjs.dev/errors/<slug>.

Im Handler werfen

ts
import { NotFoundError } from "@daloyjs/core";

app.get(
  "/users/:id",
  {
    operationId: "getUser",
    responses: { 200: { description: "ok" }, 404: { description: "missing" } },
  },
  async ({ params }) => {
    const user = await db.find(params.id);
    if (!user) throw new NotFoundError(`user ${params.id} not found`);
    return { status: 200, body: user };
  },
);

Übertragungsformat

Die Request-ID wird dem Client an zwei Stellen zurückgegeben: im Response-Header x-request-id und (gemäß RFC 9457 §3.1) im Feld instance des Problem-Dokuments als URN der Form urn:request:<uuid>. Es gibt keine Eigenschaft requestId auf oberster Ebene; Clients sollten den Header lesen oder die URN aus instance parsen.

json
HTTP/1.1 404 Not Found
content-type: application/problem+json
x-request-id: c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431

{
  "type": "https://daloyjs.dev/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "user 42 not found",
  "instance": "urn:request:c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431"
}

Schwärzung in Produktion

DaloyJS entfernt das Feld detail bei jeder 5xx-Antwort, sofern die Umgebung nicht eindeutig development oder test ist, damit interne Fehlermeldungen und SQL-Fragmente nicht an Clients gelangen. Der vollständige Fehler geht weiterhin an Ihren Logger und an den onError-Hook.

Konfiguration5xx-detail
env: "development" oder env: "test"sichtbar
kein env, NODE_ENV=development oder testsichtbar
kein env, NODE_ENV nicht gesetzt, leer oder etwas anderesgeschwärzt
env: "production", production: true oder NODE_ENV=productiongeschwärzt
production: false bei nicht gesetztem NODE_ENVgeschwärzt

Eine nicht gesetzte Umgebung schlägt absichtlich sicher fehl. Edge-Laufzeiten wie Cloudflare Workers und Deno Deploy haben kein NODE_ENV, und würde man das als development behandeln, gingen Exception-Meldungen an Clients. daloy dev setzt NODE_ENV=development für seinen Kindprozess, wenn Sie es nicht gesetzt haben, sodass die lokale Entwicklung die Details weiterhin zeigt. Fehler, die Sie selbst serialisieren (zum Beispiel ein Response-Body, der in einem catch-Block aus err.message gebaut wird), werden nicht geschwärzt. Werfen Sie sie stattdessen, oder werfen Sie einen HttpError mit einem unbedenklichen detail.

Eigene Fehlerklassen

ts
import { HttpError } from "@daloyjs/core";

export class QuotaExceededError extends HttpError {
  constructor(resource: string) {
    super(429, {
      title: "Quota exceeded",
      type: "https://api.example.com/errors/quota-exceeded",
      detail: `Quota exceeded for ${resource}`,
    });
  }
}

Die eingebauten Klassen oben setzen alle einen type der Form https://daloyjs.dev/errors/<slug>. Ein einfacher HttpError, der ohne type geworfen wird, fällt auf https://httpstatuses.io/<status> zurück. Eigene Middleware des Frameworks, die mit 503 antwortet (loadShedding(), concurrencyLimit(), requestTimeoutStatus: 503), setzt ihren eigenen daloyjs.dev-Typ, daher gilt der Fallback nur für Fehler, die Sie selbst erzeugen. Setzen Sie type, wenn Clients darauf reagieren, oder verwenden Sie eine eingebaute Klasse:

ts
throw new HttpError(503, { title: "Service Unavailable" });
// "type": "https://httpstatuses.io/503"

throw new HttpError(503, {
  title: "Service Unavailable",
  type: "https://api.example.com/errors/service-unavailable",
});

Dienst nicht verfügbar

Für einen Wartungsmodus, den Ausfall einer Abhängigkeit oder eine Instanz, die gerade geleert wird, werfen Sie ServiceUnavailableError. Er antwortet mit 503, einem stabilen Typ https://daloyjs.dev/errors/service-unavailable, einem optionalen Retry-After und cache-control: no-store, damit ein CDN den Ausfall nicht weiter ausliefert, nachdem Sie sich erholt haben.

ts
import { ServiceUnavailableError } from "@daloyjs/core";

if (await maintenance.isOn()) {
  // Retry-After: 120. Das detail wird außerhalb von development/test geschwärzt,
  // daher ist der Header das, worauf der Client tatsächlich reagieren kann.
  throw new ServiceUnavailableError(120, "scheduled database maintenance");
}

retryAfterSeconds muss eine nicht negative Ganzzahl sein. Alles andere (NaN aus einer fehlenden Umgebungsvariable, -1, 1.5) löst einen RangeError aus, statt einen fehlerhaften Retry-After-Header zu senden.

Eigenes onError

ts
app.use({
  onError: async (error, ctx) => {
    logger.error({ err: error, requestId: ctx?.state.requestId }, "request failed");
    // eine Response zurückgeben, um zu überschreiben; sonst serialisiert DaloyJS problem+json
  },
});

Zuletzt aktualisiert: