Dokumentation und Testfälle: Leistungsstarke Werkzeuge zur Lösung von OpenResty-Entwicklungsproblemen

API7.ai

October 23, 2022

OpenResty (NGINX + Lua)

Nachdem wir die Prinzipien und einige wesentliche Konzepte von OpenResty gelernt haben, werden wir nun endlich damit beginnen, die API zu erlernen.

Aus meiner persönlichen Erfahrung ist das Erlernen der OpenResty-API relativ einfach, daher benötigt es nicht viele Artikel, um sie vorzustellen. Sie fragen sich vielleicht: Ist die API nicht der gebräuchlichste und wesentlichste Teil? Warum nicht viel Zeit darauf verwenden? Es gibt zwei Hauptüberlegungen.

Erstens bietet OpenResty eine sehr detaillierte Dokumentation. Im Vergleich zu vielen anderen Programmiersprachen oder Plattformen bietet OpenResty nicht nur die API-Parameter und Rückgabewertdefinitionen, sondern auch vollständige und ausführbare Codebeispiele, die Ihnen klar zeigen, wie die API mit verschiedenen Randbedingungen umgeht.

Das Befolgen der API-Definition mit Beispielcode und Hinweisen ist ein konsistenter Stil der OpenResty-Dokumentation. Daher können Sie nach dem Lesen der API-Beschreibung den Beispielcode sofort in Ihrer Umgebung ausführen und die Parameter und die Dokumentation ändern, um sie zu überprüfen und Ihr Verständnis zu vertiefen.

Zweitens bietet OpenResty umfassende Testfälle. Wie ich bereits erwähnt habe, zeigt die OpenResty-Dokumentation Codebeispiele von APIs. Aufgrund von Platzbeschränkungen zeigt das Dokument jedoch nicht die Fehlermeldung und -behandlung in verschiedenen abnormalen Situationen und die Methode zur Verwendung mehrerer APIs.

Aber keine Sorge. Sie können die meisten dieser Inhalte im Testfallset finden.

Für OpenResty-Entwickler sind die besten API-Lernmaterialien die offizielle Dokumentation und die Testfälle, die professionell und leserfreundlich sind.

Gib einem Mann einen Fisch, und du ernährst ihn für einen Tag; lehre einen Mann zu fischen, und du ernährst ihn für ein Leben lang. Lassen Sie uns anhand eines echten Beispiels erleben, wie man die Kraft der Dokumentation und des Testfallsets in der OpenResty-Entwicklung nutzen kann.

Nehmen wir die get-API von shdict als Beispiel

Basierend auf dem NGINX Shared Memory-Bereich ist das Shared Dict (Shared Dictionary) ein Lua-Dictionary-Objekt, das Daten über mehrere Worker hinweg zugreifen und Daten wie Ratenbegrenzung, Cache usw. speichern kann. Es gibt mehr als 20 APIs, die mit Shared Dict zusammenhängen – die am häufigsten verwendete und entscheidende API in OpenResty.

Nehmen wir die einfachste get-Operation als Beispiel; Sie können auf den Dokumentationslink klicken, um einen Vergleich zu ziehen. Das folgende minimierte Codebeispiel ist an die offizielle Dokumentation angepasst.

http {
      lua_shared_dict dogs 10m;
      server {
          location /demo {
              content_by_lua_block {
                  local dogs = ngx.shared.dogs
                  dogs:set("Jim", 8)
                  local v = dogs:get("Jim")
                  ngx.say(v)
              }
          }
      }
  }

Als kurze Anmerkung: Bevor wir Shared Dict im Lua-Code verwenden können, müssen wir in nginx.conf mit der lua_shared_dict-Direktive einen Speicherblock hinzufügen, der "dogs" genannt wird und eine Größe von 10M hat. Nach der Änderung von nginx.conf müssen Sie den Prozess neu starten und mit einem Browser oder dem curl-Befehl darauf zugreifen, um die Ergebnisse zu sehen.

Scheint das nicht etwas mühsam zu sein? Lassen Sie es uns einfacher gestalten. Wie Sie sehen können, hat die Verwendung der resty CLI auf diese Weise den gleichen Effekt wie das Einbetten des Codes in nginx.conf.

$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
 dogs:set("Jim", 8)
 local v = dogs:get("Jim")
 ngx.say(v)
 '

Sie wissen jetzt, wie nginx.conf und Lua-Code zusammenarbeiten, und Sie haben die Set- und Get-Methoden des Shared Dict erfolgreich ausgeführt. Im Allgemeinen hören die meisten Entwickler hier auf. Es gibt jedoch einige Dinge, die hier erwähnenswert sind.

  1. In welchen Phasen können die Shared Memory-APIs nicht verwendet werden?
  2. Wir sehen im Beispielcode, dass die Get-Funktion nur einen Rückgabewert hat. Wann gibt es dann mehr als einen Rückgabewert?
  3. Welchen Typ hat der Eingabeparameter der Get-Funktion? Gibt es eine Längenbeschränkung?

Unterschätzen Sie diese Fragen nicht; sie können uns helfen, OpenResty besser zu verstehen, und ich werde Sie einzeln durch sie führen.

Frage 1: In welchen Phasen können Shared Memory-APIs nicht verwendet werden?

Schauen wir uns die erste Frage an. Die Antwort ist einfach; die Dokumentation hat einen speziellen context-Abschnitt (d.h. Kontextabschnitt), der die Umgebungen auflistet, in denen die API verwendet werden kann.

context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*

Wie Sie sehen können, sind die init- und init_worker-Phasen nicht enthalten, was bedeutet, dass die get-API von Shared Memory in diesen beiden Phasen nicht verwendet werden kann. Bitte beachten Sie, dass jede Shared Memory-API in verschiedenen Phasen verwendet werden kann. Zum Beispiel kann die set-API in der init-Phase verwendet werden.

Lesen Sie immer die Dokumentation, wenn Sie sie verwenden. Natürlich enthält die OpenResty-Dokumentation manchmal Fehler und Auslassungen, daher müssen Sie sie mit tatsächlichen Tests überprüfen.

Als Nächstes ändern wir das Testset, um sicherzustellen, dass die init-Phase die get-API des Shared Dict ausführen kann.

Wie können wir das Testfallset finden, das mit Shared Memory zusammenhängt? Die Testfälle von OpenResty befinden sich alle im Verzeichnis /t und sind regelmäßig benannt, d.h. selbst-incrementierte-Nummer-Funktionsname.t. Suchen Sie nach shdict, und Sie werden 043-shdict.t finden, das Shared Memory-Testfallset, das fast 100 Testfälle enthält, einschließlich Tests für verschiedene normale und abnormale Umstände.

Lassen Sie uns versuchen, den ersten Testfall zu ändern.

Sie können die content-Phase durch eine init-Phase ersetzen und den überflüssigen Code entfernen, um zu sehen, ob die get-Schnittstelle funktioniert. Sie müssen zu diesem Zeitpunkt nicht verstehen, wie der Testfall geschrieben, organisiert und ausgeführt wird. Sie müssen nur wissen, dass er die get-Schnittstelle testet.

 === TEST 1: string key, int value
     --- http_config
         lua_shared_dict dogs 1m;
     --- config
         location = /test {
             init_by_lua '
                 local dogs = ngx.shared.dogs
                 local val = dogs:get("foo")
                 ngx.say(val)
             ';
         }
     --- request
     GET /test
     --- response_body
     32
     --- no_error_log
     [error]
     --- ONLY

Sie haben wahrscheinlich bemerkt, dass ich am Ende des Testfalls das --ONLY-Flag hinzugefügt habe, was bedeutet, alle anderen Testfälle zu ignorieren und nur diesen einen auszuführen, wodurch die Ausführungsgeschwindigkeit erhöht wird. Später im Testabschnitt werde ich die verschiedenen Tags speziell erklären.

Nach der Änderung können wir den Testfall mit dem prove-Befehl ausführen.

prove t/043-shdict.t

Dann erhalten Sie einen Fehler, der die in der Dokumentation beschriebenen Phasenbeschränkungen bestätigt.

nginx: [emerg] "init_by_lua" directive is not allowed here

Frage 2: Wann hat die get-Funktion mehrere Rückgabewerte?

Schauen wir uns die zweite Frage an, die aus der offiziellen Dokumentation zusammengefasst werden kann. Die Dokumentation beginnt mit der syntax-Beschreibung dieser Schnittstelle.

value, flags = ngx.shared.DICT:get(key)

Unter normalen Umständen.

  • Der erste Parameter value gibt den Wert zurück, der dem key im Wörterbuch entspricht; wenn der key jedoch nicht existiert oder abgelaufen ist, ist der value-Wert nil.
  • Der zweite Parameter, flags, ist etwas komplizierter; wenn die Set-Schnittstelle Flags setzt, werden sie zurückgegeben. Andernfalls nicht.

Wenn der API-Aufruf fehlschlägt, gibt value nil zurück, und flags gibt eine spezifische Fehlermeldung zurück.

Aus den in der Dokumentation zusammengefassten Informationen können wir ersehen, dass local v = dogs:get("Jim") nur mit einem Empfangsparameter geschrieben ist. Eine solche Schreibweise ist unvollständig, da sie nur das typische Verwendungsszenario abdeckt, ohne einen zweiten Parameter zu empfangen oder eine Ausnahmebehandlung durchzuführen. Wir könnten es wie folgt ändern.

local data, err = dogs:get("Jim")
if data == nil and err then
    ngx.say("get not ok: ", err)
    return
end

Wie bei der ersten Frage können wir das Testfallset durchsuchen, um unser Verständnis der Dokumentation zu bestätigen.

  === TEST 65: get nil key
     --- http_config
         lua_shared_dict dogs 1m;
     --- config
         location = /test {
             content_by_lua '
                 local dogs = ngx.shared.dogs
                 local ok, err = dogs:get(nil)
                 if not ok then
                     ngx.say("not ok: ", err)
                     return
                 end
                 ngx.say("ok")
             ';
         }
     --- request
     GET /test
     --- response_body
     not ok: nil key
     --- no_error_log
     [error]

In diesem Testfall hat die get-Schnittstelle nil als Eingabe, und die zurückgegebene Fehlermeldung ist nil key. Dies bestätigt, dass unsere Analyse der Dokumentation korrekt ist, und liefert eine teilweise Antwort auf die dritte Frage. Zumindest kann die Eingabe für get nicht nil sein.

Frage 3: Welchen Typ hat der Eingabeparameter der get-Funktion?

Was die dritte Frage betrifft, welche Art von Eingabeparametern kann get akzeptieren? Schauen wir zuerst in der Dokumentation nach, aber leider werden Sie feststellen, dass die Dokumentation nicht angibt, welche Arten von Schlüsseln zulässig sind. Was sollen wir tun?

Keine Sorge. Zumindest wissen wir, dass der key ein String-Typ sein kann und nicht nil sein darf. Erinnern Sie sich an die Datentypen in Lua? Neben Strings und nil gibt es Zahlen, Arrays, Boolean-Typen und Funktionen. Die letzteren beiden sind als Schlüssel unnötig, daher müssen wir nur die ersten beiden überprüfen: Zahlen und Arrays. Wir können damit beginnen, die Testdatei nach Fällen zu durchsuchen, in denen Zahlen als key verwendet werden.

=== TEST 4: number keys, string values

Mit diesem Testfall können Sie sehen, dass Zahlen auch als Schlüssel verwendet werden können und intern in Strings umgewandelt werden. Was ist mit Arrays? Leider deckt der Testfall dies nicht ab, also müssen wir es selbst versuchen.

$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
 dogs:get({})
 '

Nicht überraschend wurde der folgende Fehler gemeldet.

ERROR: (command line -e):2: bad argument #1 to 'get' (string expected, got table)

Zusammenfassend können wir feststellen, dass die von der get-API akzeptierten key-Typen Strings und Zahlen sind.

Gibt es also eine Längenbeschränkung für den eingegebenen Schlüssel? Es gibt hier einen entsprechenden Testfall.

=== TEST 67: get a too-long key
     --- http_config
         lua_shared_dict dogs 1m;
     --- config
         location = /test {
             content_by_lua '
                 local dogs = ngx.shared.dogs
                 local ok, err = dogs:get(string.rep("a", 65536))
                 if not ok then
                     ngx.say("not ok: ", err)
                     return
                 end
                 ngx.say("ok")
             ';
         }
     --- request
     GET /test
     --- response_body
     not ok: key too long
     --- no_error_log
     [error]

Wenn die String-Länge 65536 beträgt, werden Sie darauf hingewiesen, dass der Schlüssel zu lang ist. Sie können versuchen, die Länge auf 65535 zu ändern, obwohl nur 1 Byte weniger, aber es werden keine Fehler mehr gemeldet. Dies bedeutet, dass die maximale Länge des Schlüssels genau 65535 beträgt.

Zusammenfassung

Abschließend möchte ich Sie daran erinnern, dass in der OpenResty-API jeder Rückgabewert mit einer Fehlermeldung eine Variable haben muss, die ihn empfängt und eine Fehlerbehandlung durchführt, sonst wird ein Fehler gemacht. Zum Beispiel, wenn die falsche Verbindung in den Verbindungspool gelegt wird oder wenn der API-Aufruf fehlschlägt und die dahinter liegende Logik fortgesetzt wird, führt dies zu ständigen Beschwerden.

Wenn Sie also ein Problem haben, wenn Sie OpenResty-Code schreiben, wie lösen Sie es normalerweise? Ist es die Dokumentation, Mailinglisten oder andere Kanäle?

Teilen Sie diesen Artikel gerne mit Ihren Kollegen und Freunden, damit wir uns austauschen und verbessern können.