Bootcamp i backendutveckling med Node.js · Lektion

Interceptors, deadlines och metadata

Lägg till övergripande autentisering och loggning via interceptors, samtidigt som deadlines upprätthålls och metadata skickas.

Lektion 3 av 413 steg

Interceptors, deadlines och metadata är en gratis lektion i Bootcamp i backendutveckling med Node.js på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med Node.js, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.

Övergripande aspekter i gRPC

När din gRPC-tjänst växer kommer du att upprepa samma logik i varje hanterare: autentisering, loggning, tidsmätning och utformning av fel. Att kopiera in detta i varje metod är skört.

gRPC ger dig tre verktyg för att hantera detta på ett bra sätt:

  • Interceptors — middleware som omsluter varje anrop, på klient- eller serversidan.
  • Metadata — nyckel/värde-huvuden som följer med förfrågan, perfekta för autentiseringstoken och förfrågnings-ID:n.
  • Deadlines — en absolut tidpunkt då ett anrop senast måste vara klart, som vidarebefordras mellan tjänster.

I den här lektionen kopplar vi ihop alla tre med @grpc/grpc-js i Node.js.

Vad metadata faktiskt är

Metadata är en multimappning från strängnycklar till värden som skickas med ett gRPC-anrop, ungefär som HTTP-huvuden. Nycklarna är skiftlägesokänsliga. Värdena är vanligtvis ASCII-strängar; nycklar som slutar på -bin innehåller binära Buffer-värden.

På klienten lägger du till metadata och på servern läser du den från anropsobjektet. Använd den för sådant som inte ingår i nyttolasten — autentiseringstoken, spårnings-ID:n och språkinställning.

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' ]

Skicka metadata med ett unary-anrop

Varje genererad klientmetod accepterar ett valfritt Metadata-argument före callbacken, eller alternativen. Tokenen du anger här anländer till servern innan din hanterare körs.

Håll autentisering separat från dina protobuf-meddelanden — lägg tokenen i metadata så att samma autentiseringslogik fungerar för varje RPC.

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äsa metadata på servern

På servern exponerar anropsobjektet metadata via call.metadata.get(key), som returnerar en array eftersom en nyckel kan förekomma flera gånger. För autentisering läser du authorization-huvudet och validerar det innan du utför arbete.

Om valideringen misslyckas returnerar du ett fel med grpc.status.UNAUTHENTICATED så att klienterna kan reagera 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én

Att läsa och validera token i varje hanterare är repetitivt. En serverinterceptor låter dig köra logik en gång för alla RPC-anrop.

I @grpc/grpc-js tillhandahålls interception på serversidan via alternativet interceptors i server.addService (och via ServerInterceptingCall i nyare versioner). Det klassiska, brett stödda mönstret är en klientinterceptor; på servern omsluter många team hanterare med en liten högre ordningens funktion för autentisering och loggning.

Vi börjar med det mönstret där hanteraren omsluts — det är portabelt och enkelt att testa.

En autentiseringsomslutning för hanterare

En högre ordningens funktion tar emot en hanterare och returnerar en ny hanterare som först kontrollerar autentiseringen och sedan delegerar. Det är ett rent sätt utan ramverk att lägga till en övergripande funktion.

Omslutningen avbryter direkt med UNAUTHENTICATED när tokenen är ogiltig, och anropar annars den ursprungliga hanteraren.

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 loggningsomslutning som kan kombineras

Samma mönster fungerar för loggning och tidsmätning. Eftersom varje omslutning tar emot en hanterare och returnerar en hanterare kan du kombinera dem: withLogging(withAuth(getUser)).

Här registrerar loggaren metodetiketten, svarstiden och förfrågnings-ID:t från metadata. Observera att det är ett komplett, fristående program som du kan köra för att se hur kombinationen fungerar med ett simulerat anropsobjekt.

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

Klientens officiella utbyggnadspunkt är interceptorn: en funktion som tar emot options och en nextCall och returnerar en InterceptingCall. Du åsidosätter livscykelmetoder som start för att lägga till metadata eller onReceiveStatus för att observera resultat.

Nedan får varje utgående anrop automatiskt ett authorization-huvud — den som anropar behöver aldrig komma ihåg det.

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')],
// });

Förstå tidsgränser

En deadline är en absolut tidpunkt då ett anrop senast måste vara klart. Det är INTE en timeout per hopp — det är en tidpunkt enligt väggklockan som vidarebefordras nedströms, så en kedja av tjänster delar på samma budget.

Ange den i anropets options som deadline: antingen som ett Date-objekt eller som millisekunder sedan epoken. Det idiomatiska sättet är ”nu plus N ms”.

  • Om tidsgränsen passeras misslyckas anropet med DEADLINE_EXCEEDED (status 4).
  • Servrar kan kontrollera den återstående tiden och avsluta arbetet tidigt.
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);
  });
}

Tidsgränser på serversidan

Servern tar emot tidsgränsen som call.getDeadline(). Innan du påbörjar kostsamt arbete beräknar du den återstående budgeten och avbryter tidigt om den redan är slut — på så sätt slösar du inte CPU på ett anrop som klienten har gett upp.

När du skickar anrop vidare till nedströms tjänster ska du vidarebefordra SAMMA tidsgräns så att hela trädet respekterar en gemensam 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 });
}

Sätt ihop allt: en pipeline med budget

Här är tankemodellen för en förfrågan: klienten anger en tidsgräns och autentiseringsmetadata. En klientinterceptor lägger in tokenen och anropet bär med sig ett förfrågnings-ID. På servern validerar en autentiseringsomslutning, en loggningsomslutning mäter tiden och hanterarna kontrollerar den återstående tidsgränsen innan de skickar anrop vidare — både metadata och tidsgränsen vidarebefordras.

Den här fristående simuleringen visar hela kedjan av omslutningar och en kontroll av tidsgränsen utan någon server, så att du kan köra den direkt.

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 || '')
);

Snabbtest

En klient anger deadline = Date.now() + 3000 och anropar tjänst A, som sedan anropar tjänst B. Vilket beteende är korrekt för tidsgränsen?

Sammanfattning

Nu har du verktygen för gRPC-anrop av produktionskvalitet:

  • Metadata transporterar data vid sidan av nyttolasten, till exempel authorization och x-request-id; läs den på serversidan med call.metadata.get(key) (returnerar en array, nycklarna är skiftlägesokänsliga).
  • Interceptors centraliserar övergripande logik: klientinterceptors använder InterceptingCall för att lägga till metadata i start; på servern lägger kombinerbara hanteraromslutningar till autentisering och loggning på ett ställe.
  • Deadlines är absoluta tidpunkter som anges via anropets options.deadline och vidarebefordras oförändrade nedströms, så att en hel kedja delar på samma budget; om den överskrids fås DEADLINE_EXCEEDED (status 4).

Kombinera dem: lägg till autentisering och förfrågnings-ID via en interceptor, omslut hanterare för autentisering och loggning och vidarebefordra alltid tidsgränsen när du skickar anrop vidare.

Gratis att börja

Lär dig JavaScript med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
22
Lektioner
92

Vanliga frågor

Är lektionen ”Interceptors, deadlines och metadata” gratis?

Ja – hela texten till ”Interceptors, deadlines och metadata” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Bootcamp i backendutveckling med Node.js, kan Ni uppgradera till CoddyKit PRO. Kursen i Bootcamp i backendutveckling med Node.js innehåller totalt 4 lektioner.

Vad lär jag mig i ”Interceptors, deadlines och metadata”?

Lägg till övergripande autentisering och loggning via interceptors, samtidigt som deadlines upprätthålls och metadata skickas. Ni övar på Bootcamp i backendutveckling med Node.js med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med Node.js?

Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med Node.js på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Interceptors, deadlines och metadata”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med Node.js-lektionen?

Ja. Varje Bootcamp i backendutveckling med Node.js-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Definiera tjänster och meddelanden med Protobuf IDL
  2. Unära, server-, klient- och dubbelriktade streaming-RPC:er
  3. Interceptors, deadlines och metadata
  4. Utveckling av proto och bakåtkompatibilitet
← Tillbaka till Bootcamp i backendutveckling med Node.js