OpenResty: Quando Nginx Da Solo Non Basta (e Non Vuoi un Backend Separato)

Hai mai avuto la necessità di implementare logica custom sul tuo web server senza dover creare un’applicazione backend separata?
Forse avevi la necessità di un rate limiting più sofisticato di quello che offre la versione standard di Nginx, o magari avevi bisogno di autenticazione JWT direttamente sul reverse proxy, oppure di manipolare dinamicamente le richieste.

Generalmente, la soluzione che si adopera è quella di mettere una app scritta in Node/Python/Go dietro Nginx, con tutto il sovraccarico che ciò comporta.

OpenResty risolve il problema in modo radicale: permette di implementare quella logica che implementeresti nei linguaggi soprannominati, dentro Nginx stesso, usando il linguaggio LUA.

Cos’è OpenResty?

OpenResty nasce come piattaforma web dinamica, basata su Nginx e LuaJIT.

Integra il core standard di Nginx, arricchito con molte librerie Lua, un botto di moduli Nginx di terze parti, e la maggior parte delle loro dipendenze.

OpenResty come piattaforma applicativa completa?


Come già accennato, OpenResty permette di eseguire la logica dell’applicazione all’interno del core Nginx, ed è sviluppato in modo che non vi sia la necessità di un applicativo server separato a cui proxare le richieste.

Ma chi usa OpenResty?

Cloudflare lo usa da anni, sin dalla sua nascita, e buona parte dell’ecosistema di Alibaba, come ad esempio la piattaforma di e-commerce TaoBao, ci gira sopra.
Come si può evincere, non è tecnologia sperimentale: è roba usata in produzione pesante.

Secondo le statistiche di NetCraft, nel 2026 sono più di 42 Milioni i siti web che girano su questa piattaforma.

Come funziona OpenResty?

Per capire il funzionamento di OpenResty, occorre richiamare alcuni principi di funzionamento di Nginx.

Perché Nginx si distingue dagli altri server web?

Hai presente il vecchio Apache in modalità pre-fork?
Funzionava così: arriva una richiesta, viene creato un processo, o un thread per gestirla, e finita la richiesta il processo viene terminato o torna in pool.

Sembra molto lineare e logico, ma ciò introduce un problema enorme: ad esempio, se arrivano 10.000 richieste, servono 10.000 processi o thread, e come risultato si avrebbe RAM esaurita, CPU in overload e server che si pianta.

Possiamo dunque affermare che Nginx ha rivoluzionato il sistema di gestione delle richieste con un approccio chiamato event-driven non blocking, cioè invece di assegnare un nuovo thread o processo ad ogni richiesta, mantiene pochi worker (di solito uno per ogni core CPU) che gestiscono migliaia di connessioni contemporaneamente.

Come funziona questo event-driven non blocking?


Nginx non si blocca mai ad aspettare; quando una richiesta è in attesa di qualcosa, ad esempio una risposta del database, o la lettura di un file da disco, la richiesta viene messa in “pausa” e vengono servite le altre. Quando l’attesa è finita, riprende il lavoro da dove lo aveva lasciato.

Per fare un esempio più tangibile, bisogna immaginare Nginx nelle vesti di un cameriere bravissimo, che invece di star fermo ad aspettare il cuoco che prepari la portata, va a prendere ordini agli altri tavoli, porta da bere ad un terzo e quando il cuoco ha finito di preparare il pasto, lo porta al cliente che lo aveva ordinato.
Risultato? Serve 50 tavoli da solo, mentre un cameriere non esperto ne servirebbe 5 con la stessa tempistica.

Questa architettura è il motivo per cui Nginx riesce a gestire carichi enormi di richieste consumando pochissime risorse, ed è il motivo per cui è diventato il server web più usato al mondo per siti ad elevato volume di traffico.

E qui entra in scena Lua

Nginx è un miracolo della tecnologia, su questo non ci piove, ma ha un suo limite:
la sua configurazione è statica, ovvero si scrivono le regole di routing nel file nginx.conf, si ricarica il servizio di nginx e quelle restano.


Non è dunque possibile implementare logica dinamica in Nginx “standard”, non possiamo chiedergli di fare cose del tipo: “se l’utente arriva da questa località geografica e ha questo cookie, allora fai X, altrimenti fai Y”, se non impazzendo con regole astruse o rassegnandosi con un’applicazione backend vera e propria dietro.
Dunque, eccoci in aiuto OpenResty, che risolve la cosa integrando dentro Nginx un interprete Lua che prende il nome di LuaJIT.

LuaJIT è una versione estremamente veloce di Lua, un linguaggio di scripting nato per essere incorporato in altri programmi, in inglese diremmo “embedded”. Esso è uno dei linguaggi dinamici più veloci in circolazione e, in molti casi, si avvicina alle prestazioni del linguaggio C.

Possiamo quindi dire che LuaJIT eredita lo stesso modello non-blocking di Nginx, che come già detto, quando aspetta qualcosa (ad esempio una query Redis, o una chiamata HTTP esterna), non blocca il worker Nginx, ma continua a servire altre richieste finché l’operazione non è pronta per essere gestita.

Il ciclo di vita di una richiesta HTTP

image

Come mostrato nell’immagine, quando una richiesta http arriva ad Nginx, piuttosto che essere gestita in un colpo solo, passa attraverso una serie di fasi, ognuna con un compito specifico.

Le principali direttive che corrispondono alle fasi di cui sto parlando sono le seguenti:

  • rewrite_by_lua_block: viene eseguita nella fase di rewrite. Qui l’URL può essere modificato, riscritto e possono essere lette o scritte variabili Nginx. Utile per implementare logica di routing custom.
  • access_by_lua_block: viene eseguita nella fase di controllo accessi, qui si decide se il client può proseguire oppure no. Questa è la direttiva che consente di applicare rate limiting, gestire l’autenticazione o fare IP filtering custom.
  • content_by_lua_block: viene eseguita nella fase di generazione contenuto, qui attraverso LUA è possibile generare la risposta. È dunque l’equivalente di scrivere un endpoint applicativo.
  • header_filter_by_lua_block: come suggerisce il nome, questa direttiva viene eseguita mentre Nginx prepara gli header di risposta, si possono quindi aggiungere o modificare header al volo.
  • body_filter_by_lua_block: direttiva eseguita mentre Nginx invia il body della risposta. Il contenuto è dunque modificabile on-the-fly.
  • log_by_lua_block: eseguita quando la richiesta è già finita. Qui è possibile loggare su cache (ad esempio su Redis o Apache Kafka) senza rallentare la risposta al client.

Perché OpenResty è così potente

Dopo aver letto com’è facile influenzare il ciclo di vita di una richiesta http con le direttive che richiamano il codice LUA, è immediato rendersi conto di quanto sia comodo questo approccio:

Vuoi un rate limiter dinamico, del tipo “100 richieste per utente free, ma 1000 per utenti premium”? Bene, puoi metterlo in access_by_lua_block.

Vuoi loggare ogni richiesta su un sistema esterno, per svolgere analisi delle visite in tempo reale? Puoi usare log_by_lua_block.

Tutto ciò senza dover scrivere un’applicazione separata, senza overhead di rete interno e senza un altro processo da gestire. Tutto con la stessa efficienza event-driven e non blocking di Nginx. Niente context switch tra processi, niente serializzazione tra servizi, niente latenza aggiuntiva. Il tuo codice LUA diventa parte integrante del worker Nginx.

Quando NON usare OpenResty

Ovviamente come in ogni cosa non è tutto oro quello che luccica, OpenResty può essere un overkill a seconda del caso d’uso.
La learning curve è reale: devi conoscere Nginx, Lua e l’ecosistema dei moduli Lua-resty.

Lua è un linguaggio di nicchia, seppur elegante, veloce e piacevole da scrivere. Ma trovare sviluppatori è più difficile rispetto a Python o JavaScript, ad esempio.

Il debugging è meno comodo rispetto ad altri sistemi: devi fare affidamento sui log di Nginx e devi imparare a leggerli bene, non basta usare il debugger di Visual Studio Code come si farebbe con un’applicazione Node.js.

La community è piccola: meno pacchetti pronti, pochi tutorial, poche risposte online. A volte occorre interpretare direttamente il codice sorgente dei moduli, vista la scarsa documentazione su di essi.

Se hai logica di business complessa, dove è necessario utilizzare ORM pesanti, transazioni distribuite, workflow asincroni, è meglio sicuramente utilizzare un backend dedicato e mettere Nginx/OpenResty davanti, come gateway.

Insomma: Openresty ha senso quando ti serve logica custom all’edge dell’infrastruttura, dove performance e latenza contano davvero, e ovviamente hai la competenza per gestirlo.

Nel prossimo articolo vedremo come installare OpenResty su Ubuntu e scriveremo il primo “hello world” in LuaJIT.

Intanto, vi lascio i link al sito ufficiale e al repo GitHub di OpenResty:
https://openresty.org/

https://github.com/openresty/openresty

Lascia un commento