Le API sono un ottimo modo per le applicazioni software di comunicare tra loro, perché permettono alle applicazioni software di interagire e condividere risorse o privilegi.

Oggi molte aziende B2B offrono i loro servizi tramite API che possono essere usate da applicazioni realizzate con qualsiasi linguaggio e framework di programmazione. Tuttavia, questo le rende vulnerabili agli attacchi DoS e DDoS e può anche portare a una distribuzione non uniforme della larghezza di banda tra gli utenti. Per affrontare questi problemi, viene implementata una tecnica nota come rate limiting delle API. L’idea è semplice: si limita il numero di richieste che gli utenti possono fare all’API.

In questa guida scopriremo cos’è il rate limiting delle API, i vari modi in cui può essere implementato e alcune best practice ed esempi da tenere a mente quando si impostano i limiti di richieste per le API.

Cos’è la limitazione delle richieste API?

In parole povere, la limitazione delle richieste API si riferisce alla definizione di una soglia o di un limite al numero di accessi a un’API da parte degli utenti. I limiti possono essere decisi in diversi modi.

1. Limiti basati sull’utente

Uno dei modi per impostare un limite di richieste è quello di ridurre il numero di volte in cui un determinato utente può accedere all’API in un determinato periodo di tempo. Ciò può essere ottenuto contando il numero di richieste effettuate utilizzando la stessa chiave API o lo stesso indirizzo IP; quando viene raggiunta una soglia, le ulteriori richieste vengono limitate o negate.

2. Limiti basati sulla posizione

In molti casi, gli sviluppatori vogliono distribuire equamente la larghezza di banda disponibile per le loro API in determinate località geografiche.

Il recente servizio di anteprima di ChatGPT è un buon esempio di limitazione delle richieste in base alla posizione geografica, in quanto ha iniziato a limitare le richieste in base alla posizione degli utenti sulla versione gratuita del servizio dopo l’introduzione della versione a pagamento. Questo ha senso in quanto la versione di anteprima gratuita doveva essere utilizzata da persone di tutto il mondo per generare un buon campione di dati di utilizzo per il servizio.

3. Limiti basati sul server

Il limite di richieste basato sul server è un limite interno implementato sul lato server per garantire una distribuzione equa delle risorse del server come CPU, memoria, spazio su disco, ecc. Si realizza implementando un limite su ogni server di una distribuzione.

Quando un server raggiunge il suo limite, le richieste in arrivo vengono instradate verso un altro server con capacità disponibile. Se tutti i server hanno raggiunto la capacità, l’utente riceve una risposta 429 Too Many Requests. È importante notare che i limiti di richieste basati sui server vengono applicati a tutti i client, indipendentemente dalla loro posizione geografica, dall’orario di accesso o da altri fattori.

Tipi di limiti alle richieste API

Oltre alla natura dell’implementazione dei limiti di richieste, è possibile classificare i limiti di richieste in base al loro effetto sull’utente finale. Alcuni tipi comuni sono:

  • Limiti rigidi: Si tratta di limiti rigidi che, una volta superati, limitano completamente l’accesso dell’utente alla risorsa fino a quando il limite non viene rimosso.
  • Limiti morbidi: Sono limiti flessibili che, una volta superati, possono permettere all’utente di accedere alla risorsa ancora un paio di volte (o di limitare le richieste) prima di chiudere l’accesso.
  • Limiti dinamici: Questi limiti dipendono da diversi fattori come il carico del server, il traffico di rete, la posizione dell’utente, l’attività dell’utente, la distribuzione del traffico, ecc. e vengono modificati in tempo reale per garantire un funzionamento efficiente delle risorse.
  • Limiti di accesso: Questi limiti non interrompono l’accesso alla risorsa, ma rallentano o mettono in coda le richieste in arrivo fino a quando il limite non viene rimosso.
  • Limiti fatturabili: Questi limiti non limitano l’accesso o la velocità, ma addebitano all’utente il costo di ulteriori richieste quando viene superata la soglia libera impostata.

Perché il rate limiting è necessario?

Ci sono diversi motivi per cui è necessario implementare il rate limiting nelle vostre API web. Alcuni dei motivi principali sono:

1. Proteggere l’accesso alle risorse

Il primo motivo per cui dovreste prendere in considerazione l’implementazione di un limite di richieste delle API nella vostra applicazione è quello di proteggere le vostre risorse dallo sfruttamento eccessivo da parte di utenti con intenti malevoli. Gli aggressori possono usare tecniche come gli attacchi DDoS per monopolizzare l’accesso alle vostre risorse e impedire alla vostra app di funzionare normalmente per gli altri utenti. La presenza di un limite di richieste fa in modo che gli aggressori non abbiano vita facile se vogliono interrompere le vostre API.

2. Dividere la quota tra gli utenti

Oltre a proteggere le vostre risorse, il limite di richieste vi permette di suddividere le risorse API tra gli utenti. Ciò significa che potete creare modelli di prezzo differenziati e soddisfare le esigenze dinamiche dei vostri clienti senza che queste si ripercuotano sugli altri.

3. Migliorare l’efficienza dei costi

La limitazione delle tariffe equivale anche a una limitazione dei costi. Ciò significa che potete distribuire in modo oculato le risorse tra i vostri utenti. Con una struttura partizionata, è più facile stimare i costi necessari per la manutenzione del sistema. Eventuali picchi possono essere gestiti in modo intelligente fornendo o disattivando la giusta quantità di risorse.

4. Gestire il flusso tra i worker

Molte API si basano su un’architettura distribuita che usa più worker/thread/istanze per gestire le richieste in arrivo. In una struttura di questo tipo, potete usare dei limiti di richieste per controllare il carico di lavoro passato a ciascun nodo worker. Questo può aiutarvi a garantire che i nodi worker ricevano carichi di lavoro equi e sostenibili. Potete facilmente aggiungere o rimuovere i worker quando necessario senza dover ristrutturare l’intero gateway API.

Capire i limiti di burst

Un altro modo comune di controllare l’uso delle API è quello di impostare un limite di burst (noto anche come throttling) invece di un limite di richieste. I limiti di burst sono limiti di richieste implementati per un intervallo di tempo molto piccolo, come alcuni secondi. Per esempio, invece di impostare un limite di 1,3 milioni di richieste al mese, potete impostare un limite di 5 richieste al secondo. Sebbene ciò equivalga allo stesso traffico mensile, garantisce che i vostri clienti non sovraccarichino i vostri server inviando migliaia di richieste in una volta sola.

Nel caso dei limiti di burst, le richieste vengono spesso rimandate all’intervallo successivo invece di essere rifiutate. Inoltre, spesso si consiglia di usare sia i limiti di richieste che quelli di burst insieme per un controllo ottimale del traffico e dell’utilizzo.

3 Metodi di implementazione del rate limiting

Per quanto riguarda l’implementazione, ci sono alcuni metodi che potete usare per impostare il rate limiting delle API nella vostra applicazione. Tra questi ci sono:

1. Code di richiesta

Uno dei metodi più semplici per limitare l’accesso alle API è quello delle code di richieste. Le code di richieste si riferiscono a un meccanismo in cui le richieste in arrivo vengono memorizzate sotto forma di coda ed elaborate una dopo l’altra fino a un certo limite.

Un caso d’uso comune delle code di richieste è la separazione delle richieste in arrivo da utenti gratuiti e a pagamento. Ecco come potete farlo in un’applicazione Express usando il pacchetto express-queue:

const express = require('express')
const expressQueue = require('express-queue');

const app = express()

const freeRequestsQueue = expressQueue({
	activeLimit: 1, // Maximum requests to process at once
	queuedLimit: -1 // Maximum requests allowed in queue (-1 means unlimited)
});

const paidRequestsQueue = expressQueue({
	activeLimit: 5, // Maximum requests to process at once
	queuedLimit: -1 // Maximum requests allowed in queue (-1 means unlimited)
});

// Middleware that selects the appropriate queue handler based on the presence of an API token in the request
function queueHandlerMiddleware(req, res, next) {
	// Check if the request contains an API token
	const apiToken = req.headers['api-token'];

	if (apiToken && isValidToken(apiToken)) {
    	console.log("Paid request received")
    	paidRequestsQueue(req, res, next);
	} else {
    	console.log("Free request received")
    	freeRequestsQueue(req, res, next);
 	}
}

// Add the custom middleware function to the route
app.get('/route', queueHandlerMiddleware, (req, res) => {
	res.status(200).json({ message: "Processed!" })
});

// Check here is the API token is valid or not
const isValidToken = () => {
	return true;
}

app.listen(3000);

2. Throttling

Il throttling è un’altra tecnica usata per controllare l’accesso alle API. Invece di interrompere l’accesso dopo il raggiungimento di una soglia, il throttling si concentra sull’attenuazione dei picchi di traffico delle API implementando piccole soglie per piccoli intervalli di tempo. Invece di stabilire un limite di richieste come 3 milioni di chiamate al mese, il throttling stabilisce limiti di 10 chiamate al secondo. Una volta che un cliente invia più di 10 chiamate in un secondo, le richieste successive nello stesso secondo vengono automaticamente limitate, ma il cliente riacquista immediatamente l’accesso all’API nel secondo successivo.

Potete implementare il throttling in Express usando il pacchetto express-throttle. Ecco un esempio di applicazione Express che mostra come impostare il throttling nella vostra applicazione:

const express = require('express')
const throttle = require('express-throttle')

const app = express()

const throttleOptions = {
	"rate": "10/s",
	"burst": 5,
	"on_allowed": function (req, res, next, bucket) {
    	res.set("X-Rate-Limit-Limit", 10);
    	res.set("X-Rate-Limit-Remaining", bucket.tokens);
    	next()
	},
	"on_throttled": function (req, res, next, bucket) {
    	// Notify client
    	res.set("X-Rate-Limit-Limit", 10);
    	res.set("X-Rate-Limit-Remaining", 0);
    	res.status(503).send("System overloaded, try again after a few seconds.");
	}
}

// Add the custom middleware function to the route
app.get('/route', throttle(throttleOptions), (req, res) => {
	res.status(200).json({ message: "Processed!" })
});

app.listen(3000);

Potete testare l’applicazione usando uno strumento di test di carico come AutoCannon. Potete installare AutoCannon eseguendo il seguente comando nel vostro terminale:

npm install autocannon -g

Potete testare l’applicazione con il seguente comando:

autocannon http://localhost:3000/route

Il test usa 10 connessioni simultanee che inviano richieste all’API. Ecco il risultato del test:

Running 10s test @ http://localhost:3000/route

10 connections

┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐
│ Stat	│ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg 	│ Stdev   │ Max   │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.04 ms │ 0.24 ms │ 17 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘
┌───────────┬─────────┬─────────┬────────┬─────────┬────────┬─────────┬─────────┐
│ Stat  	│ 1%  	│ 2.5%	│ 50%	│ 97.5%   │ Avg	│ Stdev   │ Min 	│
├───────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┼─────────┤
│ Req/Sec   │ 16591   │ 16591   │ 19695  │ 19903   │ 19144  │ 1044.15 │ 16587   │
├───────────┼─────────┼─────────┼────────┼─────────┼────────┼─────────┼─────────┤
│ Bytes/Sec │ 5.73 MB │ 5.73 MB │ 6.8 MB │ 6.86 MB │ 6.6 MB │ 360 kB  │ 5.72 MB │
└───────────┴─────────┴─────────┴────────┴─────────┴────────┴─────────┴─────────┘

Req/Bytes counts sampled once per second.
# of samples: 11
114 2xx responses, 210455 non 2xx responses
211k requests in 11.01s, 72.6 MB read

Poiché erano consentite solo 10 richieste al secondo (con un picco extra di 5 richieste), solo 114 richieste sono state elaborate con successo dall’API, mentre alle restanti richieste è stato risposto con un codice di errore 503 che chiedeva di attendere un po’ di tempo.

3. Algoritmi di rate limiting

Sebbene il rate limiting sembri un concetto semplice da implementare tramite una coda, in realtà può essere implementato in diversi modi che offrono altri vantaggi. Ecco alcuni algoritmi popolari che si usano per implementare il rate limiting:

Algoritmo a finestra fissa

L’algoritmo a finestra fissa è uno dei più semplici algoritmi di limitazione delle richieste. Limita il numero di richieste che possono essere gestite in un intervallo di tempo fisso.

Si stabilisce un numero fisso di richieste, per esempio 100, che possono essere gestite dal server API in un’ora. Ora, quando arriva la 101esima richiesta, l’algoritmo nega l’elaborazione. Quando l’intervallo di tempo si ripristina (cioè nell’ora successiva), è possibile elaborare altre 100 richieste in arrivo.

Questo algoritmo è semplice da implementare e funziona bene in molti casi in cui è necessario limitare la velocità sul lato server per controllare la larghezza di banda (a differenza della distribuzione della larghezza di banda tra gli utenti). Tuttavia, può dare luogo a un traffico/elaborazione a scatti verso i margini dell’intervallo di tempo fissato. L’algoritmo a finestra scorrevole è un’alternativa migliore nei casi in cui è necessaria un’elaborazione uniforme.

Algoritmo a Finestra Scorrevole

L’algoritmo a finestra scorrevole è una variante dell’algoritmo a finestra fissa. Invece di usare intervalli di tempo fissi e predefiniti, questo algoritmo usa una finestra temporale mobile per monitorare il numero di richieste elaborate e in arrivo.

Invece di considerare gli intervalli di tempo assoluti (per esempio di 60 secondi ciascuno), come da 0 a 60, da 61 a 120 e così via, l’algoritmo a finestra mobile considera i 60 secondi precedenti a partire dal momento in cui viene ricevuta una richiesta. Supponiamo che una richiesta venga ricevuta all’82° secondo; allora l’algoritmo conterà il numero di richieste elaborate tra 22s e 82s (invece dell’intervallo assoluto 60s – 120s) per determinare se questa richiesta può essere elaborata o meno. In questo modo si possono evitare situazioni in cui un gran numero di richieste viene elaborato sia al 59° che al 61° secondo, sovraccaricando il server per un periodo molto breve.

Questo algoritmo è in grado di gestire meglio i picchi di traffico, ma può essere più difficile da implementare e mantenere rispetto all’algoritmo a finestra fissa.

Algoritmo token bucket

In questo algoritmo, un contenitore (“bucket”) fittizio viene riempito di token e ogni volta che il server elabora una richiesta, un token viene prelevato dal contenitore. Quando il contenitore è vuoto, il server non può più elaborare richieste. Ulteriori richieste vengono ritardate o negate fino a quando il contenitore non viene riempito di nuovo.

Il token bucket viene riempito a una velocità fissa (nota come “token generation rate”, velocità di generazione dei token) e il numero massimo di token che possono essere immagazzinati nel contenitore è anch’esso fisso (noto come “bucket depth”, profondità del contenitore).

Controllando la velocità di rigenerazione dei token e la profondità del bucket, potete controllare la velocità massima del flusso di traffico consentito dall’API. Il pacchetto express-throttle, visto in precedenza, usa l’algoritmo del token bucket per limitare o controllare il flusso di traffico dell’API.

Il vantaggio principale di questo algoritmo è che supporta i picchi di traffico, a patto che possa essere contenuto nella profondità del bucket. Questo è particolarmente utile per il traffico imprevedibile.

Algoritmo leaky bucket

L’algoritmo leaky bucket è un altro algoritmo per gestire il traffico API. Invece di mantenere una profondità del bucket che determina quante richieste possono essere gestite in un lasso di tempo (come nel caso del token bucket), consente un flusso fisso di richieste dal bucket, come con un flusso costante di acqua da un contenitore che perde.

La profondità del contenitore, in questo caso, serve a determinare quante richieste possono essere messe in coda per essere elaborate prima che il contenitore inizi a traboccare, cioè a negare le richieste in arrivo.

Il leaky bucket promette un flusso costante di richieste e, a differenza del token bucket, non gestisce i picchi di traffico.

Le best practice per il rate limiting delle API

Ora che avete capito cos’è il rate limiting delle API e come viene implementato, condividiamo alcune buone pratiche da prendere in considerazione per implementarlo nella vostra applicazione.

Offrire un livello gratuito agli utenti per esplorare i servizi

Quando pensate di implementare un limite di richieste per le API, cercate sempre di offrire un livello gratuito adeguato che i vostri potenziali utenti possano usare per provare le vostre API. Non deve essere molto generoso, ma dovrebbe essere sufficiente per consentire agli utenti di testare comodamente la vostra API nella loro applicazione di sviluppo.

Se da un lato i limiti di richieste delle API sono fondamentali per mantenere la qualità degli endpoint delle API per i vostri utenti, dall’altro un piccolo livello gratuito non soggetto a limitazioni può aiutarvi a conquistare nuovi utenti.

Decidere cosa succede quando il limite di richieste viene superato

Quando un utente supera il limite di richieste dell’API che avete impostato, ci sono un paio di cose di cui dovreste occuparvi per garantire un’esperienza positiva agli utenti e allo stesso tempo proteggere le vostre risorse. Alcune domande che dovreste porvi e considerazioni da fare sono:

Quale codice di errore e quale messaggio vedranno gli utenti?

La prima cosa che dovete fare è informare i vostri utenti che hanno superato il limite di richieste impostato per l’API . Per farlo, dovete modificare la risposta dell’API con un messaggio predefinito che spieghi il problema. È importante che il codice di stato di questa risposta sia 429 “Too Many Requests”. È inoltre consuetudine spiegare il problema nel corpo della risposta.
Ecco come potrebbe apparire un esempio di risposta:

{
	"error": "Too Many Requests",
	"message": "You have exceeded the set API rate limit of X requests per minute. Please try again in a few minutes.",
	"retry_after": 60
}

Il corpo di risposta di esempio mostrato sopra riporta il nome e la descrizione dell’errore e specifica anche una durata (solitamente in secondi) dopo la quale l’utente può riprovare a inviare le richieste. Un corpo di risposta descrittivo come questo aiuta gli utenti a capire cos’è andato storto e perché non hanno ricevuto la risposta che si aspettavano. Inoltre, consente loro di sapere quanto tempo attendere prima di inviare un’altra richiesta.

Le nuove richieste saranno limitate o completamente bloccate?

Un altro nodo di decisione è cosa fare dopo che un utente ha superato il limite di impostato stabilito per l’API. Di solito si limita l’interazione dell’utente con il server inviando una risposta 429 “Too Many Requests”, come abbiamo visto sopra. Tuttavia, dovreste prendere in considerazione anche un approccio alternativo: il throttling.

Invece di interrompere completamente l’accesso alla risorsa del server, potete rallentare il numero totale di richieste che l’utente può inviare in un determinato periodo di tempo. Questa soluzione è utile quando volete dare agli utenti una piccola “bacchettata”, ma permettere loro di continuare a lavorare se riducono il volume delle richieste.

Considerare il caching e il circuit breaking

I limiti di richieste delle API sono spiacevoli: limitano l’interazione e l’utilizzo dei vostri servizi API da parte dei vostri utenti. Questo è particolarmente grave per gli utenti che devono fare richieste simili più volte, come per esempio accedere a un set di dati sulle previsioni del tempo che viene aggiornato solo settimanalmente o recuperare un elenco di opzioni per un menu a tendina che potrebbe essere cambiato una volta ogni morte di papa. In questi casi, un approccio intelligente sarebbe quello di implementare il caching.

Il caching è un’astrazione di archiviazione ad alta velocità che si implementa nei casi in cui il volume di accesso ai dati è elevato, ma i dati non cambiano molto spesso. Invece di effettuare una chiamata API che potrebbe invocare più servizi interni e comportare spese ingenti, potreste mettere in cache gli endpoint utilizzati più di frequente in modo che la seconda richiesta venga servita dalla cache statica, che di solito è più veloce, più economica e può ridurre il carico di lavoro dei vostri servizi principali.

Può esserci un altro caso in cui si riceve un numero insolitamente alto di richieste da parte di un utente. Anche dopo aver impostato un limite di richieste, l’utente raggiunge costantemente la sua capacità e viene limitato. Queste situazioni indicano la possibilità di un potenziale abuso dell’API.

Per proteggere i vostri servizi dal sovraccarico e mantenere un’esperienza uniforme per il resto degli utenti, dovreste considerare di limitare completamente l’accesso dell’utente sospetto all’API. Questo metodo è noto come circuit breaking e, sebbene sembri simile al rate limiting, viene generalmente utilizzato quando il sistema si trova ad affrontare un sovraccarico di richieste e ha bisogno di tempo per rallentare e recuperare la qualità del servizio.

Controllare attentamente la configurazione

Sebbene i limiti di richieste delle API abbiano lo scopo di distribuire equamente le risorse tra i vostri utenti, a volte possono causare loro problemi inutili o addirittura indicare attività sospette.

L’installazione di una solida soluzione di monitoraggio per la vostra API può aiutarvi a capire quanto spesso i limiti di richieste vengono raggiunti dai vostri utenti, a capire se è necessario riconsiderare i limiti generali tenendo conto del carico di lavoro medio degli utenti e a identificare gli utenti che superano frequentemente i loro limiti (il che potrebbe indicare che hanno bisogno di un aumento dei loro limiti a breve o che devono essere monitorati per attività sospette). In ogni caso, un monitoraggio attivo vi aiuterà a capire meglio l’impatto dei limiti di richieste delle API.

Implementare il rate limiting su più livelli

Il rate limiting può essere implementato su più livelli (utente, applicazione o sistema). Molte persone commettono l’errore di impostare i limiti di richieste a uno solo di questi livelli e di aspettarsi che copra tutti i casi possibili. Sebbene non sia esattamente un anti-pattern, in alcuni casi può rivelarsi inefficace.

Se le richieste in arrivo sovraccaricano l’interfaccia di rete del sistema, la limitazione delle richieste a livello di applicazione potrebbe non essere in grado di ottimizzare i carichi di lavoro. Per questo motivo è meglio impostare le regole di limitazione del tasso a più livelli, preferibilmente ai livelli più alti della vostra architettura, per garantire che non si creino colli di bottiglia.

Lavorare con i limiti di richieste delle API

In questa sezione vediamo come testare i limiti di richieste dell’API per un determinato endpoint API e come implementare un controllo dell’utilizzo sul vostro client per assicurarvi di non esaurire i limiti dell’API remota.

Come verificare i limiti di richieste delle API

Per identificare i limiti di richieste di un’API, il primo approccio dovrebbe essere quello di leggere i documenti dell’API per verificare se i limiti sono stati chiaramente definiti. Nella maggior parte dei casi, i documenti delle API vi indicheranno il limite e come è stato implementato. Dovreste ricorrere al “test” del limite di richieste dell’API per identificarlo solo quando non riuscite a individuarlo dai documenti dell’API, dal supporto o dalla comunità. Questo perché testare un’API per trovare il suo limite di richieste significa che finirete per esaurire il vostro limite di richieste almeno una volta, il che potrebbe comportare costi finanziari e/o l’indisponibilità dell’API per un certo periodo.

Se volete identificare manualmente il limite di richieste, dovreste iniziare con un semplice strumento di test delle API come Postman per effettuare richieste manuali all’API e vedere se riuscite a esaurire il suo limite di richieste. Se non ci riuscite, potete utilizzare uno strumento di test di carico come Autocannon o Gatling per simulare un gran numero di richieste e vedere quante richieste vengono gestite dall’API prima che inizi a rispondere con un codice di stato 429.

Un altro approccio può essere quello di usare uno strumento di controllo dei limiti di richieste come AppBrokers rate-limit-test-tool. Strumenti dedicati come questo automatizzano il processo e vi forniscono anche un’interfaccia utente per analizzare attentamente i risultati del test.

Tuttavia, se non siete sicuri del limite di richieste di un’API, potete sempre provare a stimare le vostre richieste e impostare dei limiti sul lato client per assicurarvi che il numero di richieste della vostra applicazione non superi tale numero. Scoprirete come farlo nella prossima sezione.

Come limitare le chiamate API

Se dal vostro codice effettuate delle chiamate a un’API, potreste voler implementare il throttle lato vostro per evitare di effettuare accidentalmente troppe chiamate all’API e di esaurire il vostro limite API. Ci sono diversi modi per farlo. Uno dei metodi più diffusi è quello di utilizzare il metodo throttle della libreria di utility lodash.

Prima di iniziare a fare il throttling di una chiamata API, dovrete creare un’API. Ecco un esempio di codice per un’API basata su Node.js che restituisce nella console il numero medio di richieste ricevute al minuto:

const express = require('express');
const app = express();

// maintain a count of total requests
let requestTotalCount = 0;
let startTime = Date.now();

// increase the count whenever any request is received
app.use((req, res, next) => {
	requestTotalCount++;
	next();
});

// After each second, print the average number of requests received per second since the server was started
setInterval(() => {
	const elapsedTime = (Date.now() - startTime) / 1000;
	const averageRequestsPerSecond = requestTotalCount / elapsedTime;
	console.log(`Average requests per second: ${averageRequestsPerSecond.toFixed(2)}`);
}, 1000);

app.get('/', (req, res) => {
	res.send('Hello World!');
});

app.listen(3000, () => {
	console.log('Server listening on port 3000!');
});

Una volta eseguita, l’applicazione restituirà il numero medio di richieste ricevute ogni secondo:

Average requests per second: 0
Average requests per second: 0
Average requests per second: 0

Quindi, create un nuovo file JavaScript con il nome di test-throttle.js e salvate in esso il seguente codice:

// function that calls the API and prints the response
const request = () => {
	fetch('http://localhost:3000')
	.then(r => r.text())
	.then(r => console.log(r))
}

// Loop to call the request function once every 100 ms, i.e., 10 times per second
setInterval(request, 100)

Una volta eseguito questo script, noterete che il numero medio di richieste del server salta quasi a 10:

Average requests per second: 9.87
Average requests per second: 9.87
Average requests per second: 9.88

E se questa API permettesse solo 6 richieste al secondo, per esempio? Vorreste mantenere il numero medio di richieste al di sotto di questa soglia. Tuttavia, se il vostro client invia una richiesta in base a un’attività dell’utente, come il clic di un pulsante o uno scroll, potreste non essere in grado di limitare il numero di volte in cui la chiamata API viene attivata.

La funzione throttle() della libreria lodash può aiutarvi in questo caso. Prima di tutto, installate la libreria eseguendo il seguente comando:

npm install lodash

Quindi, aggiornate il file test-throttle.js in modo che contenga il seguente codice:

// import the lodash library
const { throttle } = require('lodash');

// function that calls the API and prints the response
const request = () => {
	fetch('http://localhost:3000')
	.then(r => r.text())
	.then(r => console.log(r))
}

// create a throttled function that can only be called once every 200 ms, i.e., only 5 times every second
const throttledRequest = throttle(request, 200)

// loop this throttled function to be called once every 100 ms, i.e., 10 times every second
setInterval(throttledRequest, 100)

Ora, se guardate i log del server, vedrete un output simile:

Average requests per second: 4.74
Average requests per second: 4.80
Average requests per second: 4.83

Questo significa che anche se la vostra applicazione chiama la funzione request 10 volte al secondo, la funzione di throttling fa in modo che venga chiamata solo 5 volte al secondo, aiutandovi a rimanere sotto il limite di richieste. Ecco come potete impostare il throttling lato client per evitare di esaurire i limiti di richieste delle API.

Errori comuni dei limiti di richieste delle API

Quando lavorate con le API rate-limited, potreste incontrare una serie di risposte che indicano il superamento di un limite di richieste. Nella maggior parte dei casi, riceverete il codice di stato 429 con un messaggio simile a uno di questi:

  • Calls to this API have exceeded the rate limit (“le chiamate a questa API hanno superato il limite impostato di richieste”)
  • ”API rate limit exceeded” (limite di richieste API superato)
  • 429 Too Many Requests (“troppe richieste”)

Il messaggio che ricevete però dipende dall’implementazione dell’API che state usando. Questa implementazione può variare e alcune API potrebbero anche non utilizzare affatto il codice di stato 429. Ecco alcuni altri tipi di codici di errore e messaggi relativi al limite di rate-limited:

  • 403 Forbidden o 401 Unauthorized: Alcune API possono iniziare a trattare le vostre richieste come non autorizzate, negandovi quindi l’accesso alla risorsa.
  • 503 Service Unavailable o 500 Internal Server Error: Se un’API è sovraccarica di richieste in arrivo, potrebbe iniziare a inviare messaggi di errore 5XX che indicano che il server non è in salute. Di solito si tratta di un problema temporaneo che viene risolto dal fornitore del servizio a tempo debito.

Come i migliori fornitori di API implementano i limiti di richieste delle API

Quando dovete impostare il limite di richieste per la vostra API, può essere utile dare un’occhiata a come lo fanno alcuni dei migliori fornitori di API:

  • Discord: Discord implementa la limitazione delle richieste in due modi: c’è un limite globale di 50 richieste al secondo. Oltre al limite globale, bisogna tenere a mente anche i limiti specifici per ogni percorso. Potete leggere tutto in questa documentazione. Quando il limite di richieste viene superato, riceverete una risposta HTTP 429 con un valore retry_after che potrete usare per attendere prima di inviare un’altra richiesta.
  • Twitter: anche Twitter ha dei limiti di richieste specifici per le route che potete trovare nella sua documentazione. Una volta superato il limite di richieste, riceverete una risposta HTTP 429 con un valore di intestazione x-rate-limit-reset che vi indicherà quando potrete riprendere l’accesso.
  • Reddit: il wiki sulle API archiviate di Reddit indica che il limite di accesso all’API di Reddit è di 60 richieste al minuto (solo tramite OAuth2). La risposta a ogni chiamata all’API di Reddit restituisce i valori delle intestazioni X-Ratelimit-Used, X-Ratelimit-Remaining e X-Ratelimit-Reset con cui potete determinare quando il limite potrebbe essere superato e in che modo
  • Facebook: anche Facebook stabilisce dei limiti di richieste basati sulle route. Per esempio, le chiamate effettuate dalle app basate su Facebook sono limitate a 200 * (numero di utenti dell’app) richieste all’ora. Potete trovare i dettagli completi qui. Le risposte dell’API di Facebook conterranno un’intestazione X-App-Usage o X-Ad-Account-Usage che vi aiuta a capire quando il vostro utilizzo sarà limitato.

Riepilogo

Quando si creano delle API, è fondamentale garantire un controllo ottimale del traffico. Se non tenete sotto controllo la gestione del traffico, vi ritroverete presto con un’API sovraccarica e non funzionale. Al contrario, quando lavorate con un’API a velocità limitata, è importante capire come funziona la limitazione delle richieste e come dovete usare l’API per garantire la massima disponibilità e utilizzo.

In questa guida avete imparato a conoscere la limitazione delle richieste delle API, i motivi per cui è necessaria, come può essere implementata e alcune best practice da tenere a mente quando si lavora con i limiti di richieste delle API.

Scoprite l’Hosting di Applicazioni di Kinsta e avviate il vostro prossimo progetto Node.js oggi stesso!

State lavorando con un’API in rate limiting? Oppure avete implementato la limitazione delle richieste nella vostra API? Fatecelo sapere nei commenti qui sotto!