Bootcamp i backendudvikling med Node.js · Lektion

Interceptors, deadlines og metadata

Tilføj tværgående auth og logging via interceptors, samtidig med at deadlines håndhæves og metadata videregives.

Lektion 3 af 413 trin

Interceptors, deadlines og metadata er en gratis Bootcamp i backendudvikling med Node.js-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Bootcamp i backendudvikling med Node.js, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Bootcamp i backendudvikling med Node.js-kurset indeholder 4 lektioner i alt.

Tværgående hensyn i gRPC

Når din gRPC-tjeneste vokser, gentager du den samme logik i hver handler: godkendelse, logning, tidsmåling og tilpasning af fejl. Det er skrøbeligt at kopiere dette ind i hver metode.

gRPC giver dig tre værktøjer til at håndtere dette på en ren måde:

  • Interceptors — middleware, der omslutter hvert kald på klient- eller serversiden.
  • Metadata — nøgle/værdi-headere, der følger med forespørgslen, og som er perfekte til godkendelsestokens og forespørgsels-id'er.
  • Deadlines — et absolut tidspunkt, som et kald skal være færdigt inden, og som videreføres på tværs af tjenester.

I denne lektion kobler vi alle tre sammen med @grpc/grpc-js i Node.js.

Hvad metadata faktisk er

Metadata er en multimap med tekstnøgler og værdier, der sendes med et gRPC-kald, på samme måde som HTTP-headere. Nøgler skelner ikke mellem store og små bogstaver. Værdier er normalt ASCII-tekst; nøgler, der slutter på -bin, indeholder binære Buffer-værdier.

På klienten knytter du metadata til kaldet; på serveren læser du dem fra call-objektet. Brug metadata til ting, der ikke er en del af forretningsdataene — godkendelsestokens, sporings-id'er og lokalitet.

const grpc = require('@grpc/grpc-js');

// Build metadata on the client
const md = new grpc.Metadata();
md.set('authorization', 'Bearer abc123');
md.set('x-request-id', 'req-42');

// Reading is case-insensitive
console.log(md.get('Authorization')); // [ 'Bearer abc123' ]
console.log(md.get('x-request-id')); // [ 'req-42' ]

Send metadata med et unary-kald

Alle genererede klientmetoder accepterer et valgfrit Metadata-argument før callbacket (eller indstillingerne). Det token, du angiver her, ankommer på serveren, før din handler kører.

Hold godkendelseslogik ude af dine protobuf-meddelelser — læg tokenet i metadata, så den samme godkendelseslogik fungerer for alle RPC-kald.

const grpc = require('@grpc/grpc-js');

function callWithAuth(client, token) {
  const md = new grpc.Metadata();
  md.set('authorization', 'Bearer ' + token);

  client.GetUser({ id: '7' }, md, (err, res) => {
    if (err) return console.error('RPC failed:', err.message);
    console.log('User:', res);
  });
}

Læs metadata på serveren

På serveren stiller call-objektet metadata til rådighed via call.metadata.get(key), som returnerer et array (en nøgle kan forekomme flere gange). Ved godkendelse skal du læse authorization-headeren og validere den, før du udfører arbejde.

Hvis valideringen mislykkes, skal du returnere en fejl med grpc.status.UNAUTHENTICATED, så klienterne kan reagere korrekt.

const grpc = require('@grpc/grpc-js');

function getUser(call, callback) {
  const auth = call.metadata.get('authorization')[0];
  if (!auth || !auth.startsWith('Bearer ')) {
    return callback({
      code: grpc.status.UNAUTHENTICATED,
      message: 'Missing or invalid token',
    });
  }
  const token = auth.slice('Bearer '.length);
  // ... verify token, then respond
  callback(null, { id: call.request.id, name: 'Ada' });
}

Serverinterceptors: idéen

Det er ensformigt at læse og validere tokens inde i hver handler. En serverinterceptor lader dig køre logik én gang for alle RPC-kald.

I @grpc/grpc-js leveres interception på serversiden via indstillingen interceptors på server.addService (og via ServerInterceptingCall i nyere versioner). Det klassiske, bredt understøttede mønster er en klient-interceptor; på serveren omslutter mange teams handlere med en lille højereordensfunktion til godkendelse og logning.

Lad os begynde med dette mønster, hvor handlere omsluttes — det er portabelt og nemt at teste.

En godkendelseswrapper til handlere

En højereordensfunktion modtager en handler og returnerer en ny handler, der først kontrollerer godkendelsen og derefter delegerer. Det er en ren måde uden framework til at tilføje et tværgående hensyn.

Wrapperen afbryder med UNAUTHENTICATED, når tokenet er ugyldigt; ellers kalder den videre.

const grpc = require('@grpc/grpc-js');

function withAuth(handler) {
  return (call, callback) => {
    const auth = call.metadata.get('authorization')[0];
    if (auth !== 'Bearer good-token') {
      return callback({
        code: grpc.status.UNAUTHENTICATED,
        message: 'Unauthorized',
      });
    }
    return handler(call, callback);
  };
}

// Usage when registering the service:
// server.addService(svc, { GetUser: withAuth(getUser) });

En logningswrapper, du kan kombinere

Det samme mønster fungerer til logning og tidsmåling. Fordi hver wrapper modtager en handler og returnerer en handler, kan du kombinere dem: withLogging(withAuth(getUser)).

Her registrerer loggeren metodeetiketten, latenstiden og forespørgsels-id'et fra metadata. Bemærk, at det er et komplet, selvstændigt program, som du kan køre for at se kombinationen i praksis med et falsk call-objekt.

function withLogging(name, handler) {
  return (call, callback) => {
    const start = Date.now();
    const reqId = call.metadata.reqId || 'none';
    handler(call, (err, res) => {
      const ms = Date.now() - start;
      console.log(`[${name}] req=${reqId} ${err ? 'ERR' : 'OK'} ${ms}ms`);
      callback(err, res);
    });
  };
}

function getUser(call, callback) {
  callback(null, { id: call.request.id, name: 'Ada' });
}

const handler = withLogging('GetUser', getUser);
handler(
  { request: { id: '7' }, metadata: { reqId: 'req-42' } },
  (err, res) => console.log('Response:', res)
);

Klientinterceptors

Det officielle udvidelsespunkt på klienten er interceptor-funktionen: en funktion, der modtager options og et nextCall og returnerer et InterceptingCall. Du tilsidesætter livscyklusmetoder som start for at indsætte metadata eller onReceiveStatus for at observere resultater.

Herunder får hvert udgående kald automatisk en authorization-header — den, der kalder, behøver aldrig at huske den.

const grpc = require('@grpc/grpc-js');

function authInterceptor(token) {
  return (options, nextCall) =>
    new grpc.InterceptingCall(nextCall(options), {
      start(metadata, listener, next) {
        metadata.set('authorization', 'Bearer ' + token);
        next(metadata, listener);
      },
    });
}

// const client = new UserService(addr, creds, {
//   interceptors: [authInterceptor('abc123')],
// });

Forstå deadlines

En deadline er et absolut tidspunkt, som et kald skal være færdigt inden. Det er IKKE en timeout pr. hop — det er et tidspunkt på væguret, som videreføres nedstrøms, så en kæde af tjenester deler det samme budget.

Angiv den i kaldets options som deadline: enten et Date-objekt eller millisekunder siden epoken. Det idiomatiske mønster er "nu plus N ms".

  • Hvis deadlinen overskrides, mislykkes kaldet med DEADLINE_EXCEEDED (status 4).
  • Servere kan kontrollere den resterende tid og opgive arbejdet tidligt.
const grpc = require('@grpc/grpc-js');

function callWithDeadline(client) {
  const deadline = new Date(Date.now() + 2000); // 2s budget
  client.GetUser({ id: '7' }, { deadline }, (err, res) => {
    if (err && err.code === grpc.status.DEADLINE_EXCEEDED) {
      return console.error('Timed out');
    }
    if (err) return console.error(err.message);
    console.log(res);
  });
}

Deadlines på serversiden

Serveren modtager deadlinen som call.getDeadline(). Før du starter krævende arbejde, skal du beregne det resterende budget og afslutte tidligt, hvis det allerede er udløbet — på den måde spilder du ikke CPU på et kald, som klienten har opgivet.

Når du sender kald videre til nedstrøms tjenester, skal du sende den SAMME deadline videre, så hele træet respekterer ét budget.

const grpc = require('@grpc/grpc-js');

function slowHandler(call, callback) {
  const deadline = call.getDeadline(); // ms or Date
  const remaining = Number(deadline) - Date.now();
  if (remaining <= 0) {
    return callback({ code: grpc.status.DEADLINE_EXCEEDED, message: 'No time left' });
  }
  // Forward the same deadline downstream:
  // downstream.Fetch(req, { deadline }, cb);
  callback(null, { ok: true, budgetMs: remaining });
}

Samling af det hele: en budgetteret pipeline

Her er den mentale model for én forespørgsel: Klienten angiver en deadline og godkendelsesmetadata. En klientinterceptor indsætter tokenet, og kaldet medfører et forespørgsels-id. På serveren validerer en godkendelseswrapper, en logningswrapper måler tiden, og handlerne kontrollerer den resterende deadline, før de sender kald videre — både metadata og deadline videreføres nedstrøms.

Denne selvstændige simulering viser hele kæden af wrappers og en deadlinekontrol uden nogen server, så du kan køre den direkte.

function withAuth(h) {
  return (call, cb) =>
    call.metadata.authorization === 'Bearer good'
      ? h(call, cb)
      : cb({ code: 16, message: 'UNAUTHENTICATED' });
}
function withDeadline(h) {
  return (call, cb) =>
    call.deadline - Date.now() <= 0
      ? cb({ code: 4, message: 'DEADLINE_EXCEEDED' })
      : h(call, cb);
}
function getUser(call, cb) {
  cb(null, { id: call.request.id, name: 'Ada' });
}

const pipeline = withAuth(withDeadline(getUser));
const call = {
  request: { id: '7' },
  metadata: { authorization: 'Bearer good' },
  deadline: Date.now() + 1000,
};
pipeline(call, (err, res) =>
  console.log(err ? 'Error ' + err.code : 'OK', res || '')
);

Hurtigt tjek

En klient angiver deadline = Date.now() + 3000 og kalder tjeneste A, som derefter kalder tjeneste B. Hvad er den korrekte adfærd for deadlinen?

Opsummering

Du har nu værktøjerne til gRPC-kald i produktionskvalitet:

  • Metadata transporterer data uden for selve nyttedataene, f.eks. authorization og x-request-id; læs dem på serversiden med call.metadata.get(key) (returnerer et array, og nøgler skelner ikke mellem store og små bogstaver).
  • Interceptors centraliserer tværgående logik: klientinterceptors bruger InterceptingCall til at indsætte metadata i start; på serveren tilføjer kombinerbare handlerwrappers godkendelse og logning ét sted.
  • Deadlines er absolutte tidspunkter, der angives via kaldets options.deadline og videreføres uændret nedstrøms, så en hel kæde deler ét budget; overskridelse giver DEADLINE_EXCEEDED (status 4).

Kombinér dem: indsæt godkendelse og forespørgsels-id via en interceptor, omslut handlere med wrappers til godkendelse og logning, og send altid deadlinen videre, når du fordeler kald.

Gratis at komme i gang

Lær JavaScript med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
22
Lektioner
92

Ofte stillede spørgsmål

Er lektionen “Interceptors, deadlines og metadata” gratis?

Ja — hele teksten til “Interceptors, deadlines og metadata” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Bootcamp i backendudvikling med Node.js-kurset, skal du opgradere til CoddyKit PRO. Bootcamp i backendudvikling med Node.js-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Interceptors, deadlines og metadata”?

Tilføj tværgående auth og logging via interceptors, samtidig med at deadlines håndhæves og metadata videregives. Du øver dig i Bootcamp i backendudvikling med Node.js med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Bootcamp i backendudvikling med Node.js?

Der kræves ingen tidligere erfaring. Bootcamp i backendudvikling med Node.js på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Interceptors, deadlines og metadata”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Bootcamp i backendudvikling med Node.js-lektion?

Ja. Alle Bootcamp i backendudvikling med Node.js-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Definition af services og beskeder med Protobuf IDL
  2. Unary-, server-, client- og bidirectional-streaming-RPC'er
  3. Interceptors, deadlines og metadata
  4. Udvikling af protoer og bagudkompatibilitet
← Tilbage til Bootcamp i backendudvikling med Node.js