OpenResty ist das erweiterte NGINX mit dynamischen Anfragen und Antworten
API7.ai
October 23, 2022
Nach der vorherigen Einführung sollten Sie das Konzept von OpenResty und die Art und Weise, wie man es lernt, verstanden haben. Dieser Artikel wird uns zeigen, wie OpenResty Client-Anfragen und -Antworten verarbeitet.
Obwohl OpenResty ein auf NGINX basierender Webserver ist, unterscheidet es sich grundlegend von NGINX: NGINX wird durch statische Konfigurationsdateien gesteuert, während OpenResty durch die Lua-API gesteuert wird, was mehr Flexibilität und Programmierbarkeit bietet.
Lassen Sie mich Ihnen die Vorteile der Lua-API zeigen.
API-Kategorien
Zunächst müssen wir wissen, dass die OpenResty-API in die folgenden groben Kategorien unterteilt ist.
- Anfrage- und Antwortverarbeitung.
- SSL-bezogene.
- Gemeinsames Wörterbuch.
- Cosocket.
- Verarbeitung von Traffic auf der vierten Schicht.
- Prozess und Worker.
- Zugriff auf NGINX-Variablen und -Konfiguration.
- Zeichenketten, Zeit, Kodierung und andere allgemeine Funktionen usw.
Hier schlage ich vor, dass Sie auch die Lua-API-Dokumentation von OpenResty öffnen und sie mit der API-Liste abgleichen, um zu sehen, ob Sie diese Kategorien nachvollziehen können.
OpenResty-APIs existieren nicht nur im lua-nginx-module-Projekt, sondern auch im lua-resty-core-Projekt, wie z.B. ngx.ssl
, ngx.base64
, ngx.errlog
, ngx.process
, ngx.re.split
, ngx.resp.add_header
, ngx.balancer
, ngx.semaphore
, ngx.ocsp
und andere APIs.
Für APIs, die nicht im lua-nginx-module-Projekt enthalten sind, müssen Sie sie separat einbinden, um sie zu verwenden. Wenn Sie beispielsweise die Split-Funktion verwenden möchten, müssen Sie sie wie folgt aufrufen.
$ resty -e 'local ngx_re = require "ngx.re"
local res, err = ngx_re.split("a,b,c,d", ",", nil, {pos = 5})
print(res)
'
Natürlich könnte es Sie verwirren: Im lua-nginx-module
-Projekt gibt es mehrere APIs, die mit ngx.re.sub
, ngx.re.find
usw. beginnen. Warum ist es so, dass die API ngx.re.split
die einzige ist, die zuerst eingebunden werden muss, bevor sie verwendet werden kann?
Wie wir im vorherigen Kapitel über lua-resty-core
erwähnt haben, werden die neuen OpenResty-APIs im lua-rety-core
-Repository durch FFI implementiert, was zwangsläufig zu einer gewissen Fragmentierung führt. Ich hoffe, dass dieses Problem in Zukunft durch die Zusammenführung der Projekte lua-nginx-module
und lua-resty-core
gelöst wird.
Anfrage
Als nächstes schauen wir uns an, wie OpenResty Client-Anfragen und -Antworten verarbeitet. Zuerst betrachten wir die API zur Verarbeitung von Anfragen, aber es gibt mehr als 20 APIs, die mit ngx.req
beginnen. Wie fangen wir also an?
Wir wissen, dass HTTP-Anfragenachrichten aus drei Teilen bestehen: der Anfragezeile, den Anfrageheadern und dem Anfragekörper. Daher werde ich die API in diesen drei Teilen vorstellen.
Anfragezeile
Zuerst die Anfragezeile, die die Anfragemethode, den URI und die HTTP-Protokollversion enthält. In NGINX können Sie diesen Wert mit einer eingebauten Variablen abrufen, während in OpenResty dies der ngx.var.*
-API entspricht. Schauen wir uns zwei Beispiele an.
- Die eingebaute Variable
$scheme
, die den Namen des Protokolls in NGINX darstellt, ist entwederhttp
oderhttps
; in OpenResty können Siengx.var.scheme
verwenden, um denselben Wert zurückzugeben. $request_method
stellt die Anfragemethode wieGET
,POST
usw. dar; in OpenResty können Sie denselben Wert überngx.var.request_method
zurückgeben.
Sie können die offizielle NGINX-Dokumentation besuchen, um eine vollständige Liste der NGINX-eingebauten Variablen zu erhalten: http://nginx.org/en/docs/http/ngx_http_core_module.html#variables.
Also stellt sich die Frage: Warum bietet OpenResty eine separate API für die Anfragezeile, wenn Sie die Daten in der Anfragezeile durch die Rückgabe des Werts einer Variablen wie ngx.var.*
erhalten können?
Das Ergebnis enthält viele Faktoren:
- Erstens wird nicht empfohlen,
ngx.var
wiederholt zu lesen, da dies ineffizient ist. - Zweitens, aus Gründen der Programmierfreundlichkeit, gibt
ngx.var
eine Zeichenkette zurück, kein Lua-Objekt. Es ist schwierig zu handhaben, wennargs
mehrere Werte zurückgibt. - Drittens, aus Gründen der Flexibilität, sind die meisten
ngx.var
schreibgeschützt, und nur wenige Variablen sind beschreibbar, wie z.B.$args
undlimit_rate
. Oft müssen wir jedoch die Methode, den URI und die Argumente ändern.
Daher bietet OpenResty mehrere APIs speziell für die Manipulation der Anfragezeile, die die Anfragezeile für nachfolgende Operationen wie Umleitungen neu schreiben können.
Schauen wir uns an, wie die HTTP-Protokollversionsnummer über die API abgerufen wird. Die OpenResty-API ngx.req.http_version
macht dasselbe wie die NGINX-Variable $server_protocol
: Sie gibt die Versionsnummer des HTTP-Protokolls zurück. Der Rückgabewert dieser API ist jedoch keine Zeichenkette, sondern ein numerisches Format, dessen mögliche Werte 2.0
, 1.0
, 1.1
und 0.9
sind. Nil
wird zurückgegeben, wenn das Ergebnis außerhalb dieses Bereichs liegt.
Schauen wir uns die Methode zur Abfrage der Anfragemethode in der Anfragezeile an. Wie bereits erwähnt, sind die Rolle und der Rückgabewert von ngx.req.get_method
und der NGINX-Variablen $request_method
gleich: im Zeichenkettenformat.
Allerdings ist das Parameterformat der aktuellen HTTP-Anfragemethode ngx.req.set_method
keine Zeichenkette, sondern eingebaute numerische Konstanten. Beispielsweise wird die Anfragemethode durch den folgenden Code in POST umgeschrieben.
ngx.req.set_method(ngx.HTTP_POST)
Um zu überprüfen, ob die eingebaute Konstante ngx.HTTP_POST
tatsächlich eine Zahl und keine Zeichenkette ist, können Sie ihren Wert ausdrucken und sehen, ob die Ausgabe 8 ist.
resty -e 'print(ngx.HTTP_POST)'
Auf diese Weise ist der Rückgabewert der get
-Methode eine Zeichenkette, während der Eingabewert der set
-Methode eine Zahl ist. Es ist in Ordnung, wenn die set
-Methode einen verwirrenden Wert übergibt, da die API abstürzen und einen 500
-Fehler melden kann. In der folgenden Logik:
if (ngx.req.get_method() == ngx.HTTP_POST) then
-- etwas tun
end
Diese Art von Code funktioniert einwandfrei, meldet keine Fehler und ist selbst bei Code-Reviews schwer zu finden. Ich habe einen ähnlichen Fehler gemacht und kann mich noch daran erinnern: Ich hatte bereits zwei Runden von Code-Reviews und unvollständige Testfälle durchlaufen, um ihn abzudecken. Letztendlich hat eine Anomalie in der Produktionsumgebung mich auf das Problem aufmerksam gemacht.
Es gibt keine praktische Möglichkeit, ein solches Problem zu lösen, außer vorsichtiger zu sein oder eine weitere Ebene der Kapselung hinzuzufügen. Wenn Sie Ihre Geschäfts-API entwerfen, können Sie auch überlegen und das konsistente Parameterformat der get
- und set
-Methoden beibehalten, selbst wenn Sie etwas Leistung opfern müssen.
Darüber hinaus gibt es unter den Methoden zum Umschreiben der Anfragezeile zwei APIs, ngx.req.set_uri
und ngx.req.set_uri_args
, die verwendet werden können, um URI und Argumente umzuschreiben. Schauen wir uns diese NGINX-Konfiguration an.
rewrite ^ /foo?a=3? break;
Wie können wir dies also mit der entsprechenden Lua-API lösen? Die Antwort sind die folgenden zwei Codezeilen.
ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo")
Wenn Sie die offizielle Dokumentation gelesen haben, werden Sie feststellen, dass ngx.req.set_uri
einen zweiten Parameter hat: jump
, der standardmäßig "false" ist. Wenn Sie ihn auf "true" setzen, entspricht dies dem Setzen des Flags des rewrite
-Befehls auf last
anstelle von break
im obigen Beispiel.
Ich bin jedoch kein großer Fan der Flag-Konfiguration des rewrite
-Befehls, da sie unlesbar und unerkennbar ist und weit weniger intuitiv und wartbar als Code.
Anfrageheader
Wie wir wissen, sind HTTP-Anfrageheader im Format key : value
, zum Beispiel:
Accept: text/css,*/*;q=0.1
Accept-Encoding: gzip, deflate, br
In OpenResty können Sie ngx.req.get_headers
verwenden, um die Anfrageheader zu parsen und zu erhalten, und der Rückgabewerttyp ist eine Tabelle.
local h, err = ngx.req.get_headers()
if err == "truncated" then
-- man kann wählen, die aktuelle Anfrage zu ignorieren oder abzulehnen
end
for k, v in pairs(h) do
...
end
Standardmäßig werden die ersten 100 Header zurückgegeben. Wenn die Anzahl 100 überschreitet, wird ein truncated
-Fehler gemeldet, und es liegt am Entwickler, zu entscheiden, wie damit umgegangen wird. Sie fragen sich vielleicht, warum dies so gehandhabt wird, was ich später im Abschnitt über Sicherheitslücken erwähnen werde.
Wir sollten jedoch beachten, dass OpenResty keine spezifische API zum Abrufen eines bestimmten Anfrageheaders bietet, was bedeutet, dass es keine Form wie ngx.req.header['host']
gibt. Wenn Sie ein solches Bedürfnis haben, müssen Sie sich auf die NGINX-Variable $http_xxx
verlassen, um dies zu erreichen. In OpenResty können Sie dies dann über ngx.var.http_xxx
erhalten.
Schauen wir uns nun an, wie wir den Anfrageheader umschreiben und löschen sollten. Die APIs für beide Operationen sind recht intuitiv:
ngx.req.set_header("Content-Type", "text/css")
ngx.req.clear_header("Content-Type")
Natürlich erwähnt die offizielle Dokumentation auch andere Methoden zum Entfernen des Anfrageheaders, wie das Setzen des Werts des Headers auf nil
usw. Ich empfehle jedoch immer noch, clear_header
zu verwenden, um dies einheitlich für die Klarheit des Codes zu tun.
Anfragekörper
Schließlich schauen wir uns den Anfragekörper an. Aus Leistungsgründen liest OpenResty den Anfragekörper nicht aktiv, es sei denn, Sie erzwingen die Aktivierung der lua_need_request_body
-Direktive in nginx.conf
. Darüber hinaus speichert OpenResty für größere Anfragekörper den Inhalt in einer temporären Datei auf der Festplatte, sodass der gesamte Prozess des Lesens des Anfragekörpers wie folgt aussieht.
ngx.req.read_body()
local data = ngx.req.get_body_data()
if not data then
local tmp_file = ngx.req.get_body_file()
-- io.open(tmp_file)
-- ...
end
Dieser Code enthält eine IO-blockierende Operation zum Lesen der Festplattendatei. Sie sollten die Konfiguration von client_body_buffer_size
(standardmäßig 16 KB auf 64-Bit-Systemen) anpassen, um die blockierenden Operationen zu minimieren; Sie können auch client_body_buffer_size
und client_max_body_size
gleich konfigurieren und sie vollständig im Speicher handhaben, abhängig von der Größe Ihres Speichers und der Anzahl der gleichzeitigen Anfragen, die Sie bearbeiten.
Darüber hinaus kann der Anfragekörper umgeschrieben werden. Die beiden APIs ngx.req.set_body_data
und ngx.req.set_body_file
akzeptieren eine Zeichenkette und eine lokale Festplattendatei als Eingabeparameter, um das Umschreiben des Anfragekörpers zu ermöglichen. Diese Art von Operation ist jedoch ungewöhnlich, und Sie können die Dokumentation für weitere Details überprüfen.
Antwort
Nach der Verarbeitung der Anfrage müssen wir eine Antwort an den Client senden. Wie die Anfragenachricht besteht auch die Antwortnachricht aus mehreren Teilen: der Statuszeile, den Antwortheadern und dem Antwortkörper. Ich werde die entsprechenden APIs nach diesen drei Teilen vorstellen.
Statuszeile
Das Hauptaugenmerk in der Statuszeile liegt auf dem Statuscode. Standardmäßig ist der zurückgegebene HTTP-Statuscode 200, was der in OpenResty eingebauten Konstante ngx.HTTP_OK
entspricht. Aber in der Welt des Codes ist es immer der Code, der die meisten Ausnahmefälle behandelt.
Wenn Sie die Anfragenachricht überprüfen und feststellen, dass es sich um eine bösartige Anfrage handelt, müssen Sie die Anfrage beenden:
ngx.exit(ngx.HTTP_BAD_REQUEST)
Es gibt jedoch eine besondere Konstante in den HTTP-Statuscodes von OpenResty: ngx.OK
. Im Fall von ngx.exit(ngx.OK)
verlässt die Anfrage die aktuelle Verarbeitungsphase und geht zur nächsten Phase über, anstatt direkt zum Client zurückzukehren.
Natürlich können Sie auch wählen, nicht zu beenden und den Statuscode einfach mit ngx.status
umzuschreiben, wie im folgenden Code geschrieben.
ngx.status = ngx.HTTP_FORBIDDEN
Sie können sie in der Dokumentation nachschlagen, wenn Sie mehr über die Statuscode-Konstanten wissen möchten.
Antwortheader
In Bezug auf die Antwortheader gibt es zwei Möglichkeiten, sie einzurichten. Die erste ist die einfachste.
ngx.header.content_type = 'text/plain'
ngx.header["X-My-Header"] = 'blah blah'
ngx.header["X-My-Header"] = nil -- löschen
Hier enthält ngx.header
die Informationen der Antwortheader, die gelesen, geändert und gelöscht werden können.
Die zweite Möglichkeit, die Antwortheader zu setzen, ist ngx_resp.add_header
aus dem lua-resty-core
-Repository, das eine Kopfzeilennachricht hinzufügt und wie folgt aufgerufen wird:
local ngx_resp = require "ngx.resp"
ngx_resp.add_header("Foo", "bar")
Der Unterschied zur ersten Methode besteht darin, dass add_header
ein vorhandenes Feld mit demselben Namen nicht überschreibt.
Antwortkörper
Schließlich schauen wir uns den Antwortkörper an. In OpenResty können Sie ngx.say
und ngx.print
verwenden, um den Antwortkörper auszugeben.
ngx.say('hello, world')
Die Funktionalität der beiden APIs ist identisch, der einzige Unterschied besteht darin, dass ngx.say
am Ende einen Zeilenumbruch hat.
Um die Ineffizienz der Zeichenkettenverkettung zu vermeiden, unterstützen ngx.say / ngx.print
sowohl Zeichenketten als auch Array-Formate als Parameter.
$ resty -e 'ngx.say({"hello", ", ", "world"})'
hello, world
Diese Methode überspringt die Zeichenkettenverkettung auf Lua-Ebene und überlässt sie den C-Funktionen.
Zusammenfassung
Lassen Sie uns den heutigen Inhalt überprüfen. Wir haben die OpenResty-APIs vorgestellt, die mit den Anfrage- und Antwortnachrichten verbunden sind. Wie Sie sehen können, ist die OpenResty-API flexibler und leistungsfähiger als die NGINX-Direktive.
Ist die von OpenResty bereitgestellte Lua-API also ausreichend, um Ihre Anforderungen bei der Verarbeitung von HTTP-Anfragen zu erfüllen? Bitte hinterlassen Sie Ihre Kommentare und teilen Sie diesen Artikel mit Ihren Kollegen und Freunden, damit wir uns austauschen und gemeinsam verbessern können.