Guzzle-Exceptions fangen

  • Hey,

    Das Fangen der SystemException ist mit Umstellung auf Guzzle nicht korrekt. Du musst das eine der Guzzle-Exceptions fangen, Beispiele findest du bei uns im Code bzw. erkläre ich es gerne genauer im Plugin-Entwicklungsbereich: Plugin development

    würde dieses Angebot hiermit gerne in Anspruch nehmen. Beispiele zu den Fundstellen im Code würden mir aber auch schon helfen.

    Liebe Grüße

    Julian

    Managed Webhosting, hochwertige Plugins und individuelle Auftragsarbeiten:

    Julian-Pfeil.de

  • Hallo,

    zu den Guzzle-Exceptions lässt sich zunächst einmal auf die Upstream-Dokumentation verweisen (einer der Gründe, warum wir verstärkt auf bekannte Bibliotheken setzen – die Use-Cases sind alle schon dokumentiert):

    Quickstart — Guzzle Documentation

    Ich sehe aber auch, dass die Guzzle-Doku unvollständig ist, denn die PSR-18-Exceptions werden da nicht vollständig dokumentiert.

    Welche Exception die korrekt ist, hängt ein wenig von deinem Use-Case ab:

    • Wenn dich einfach nur „Erfolg“ bzw. „Nicht-Erfolg“ interessiert, dann fängst du Psr\Http\Client\ClientExceptionInterface. Ein Beispiel wäre in der OAuth 2-Implementierung.
    • Wenn du aber beispielsweise genauer auf Basis der Status-Codes verfahren willst (404 vs 403 beispielsweise), dann brauchst du eine der spezifischeren Guzzle-Exceptions. Ein Beispiel ist in den Rich Embeds. Die Rich Embeds fangen aber zusätzlich noch „alle anderen“ Exceptions (also das oberhalb genannte Interface), um es in eine spezifischere „Rich Embed“-spezifische DownloadFailed-Exception zu verpacken.
  • De facto:

    Die bisherigen Antworten (im Thema und per Konversation Tim Düsterhus ) haben mich persönlich kein Stück weitergebracht, da auf mich die Thematik irgendwie total undokumentiert und intransparent wirkt. Aber das sehen erfahrenere Entwickler sicher anders.

    Ich bin jetzt so weit: Mein Code passt auch nicht, wenn ich die obigen Beispiele:

    Ein Beispiel wäre in der OAuth 2-Implementierung.

    Ein Beispiel ist in den Rich Embeds.

    nutze und mich an deren Logik orientiere. Soweit so gut, wahrscheinlich gibt es einfach keine Code-Stellen im WCF, die an mein Anwendungsbeispiel angelehnt sind.

    Das kann ich dir in allgemeiner Form nicht beantworten, siehe oben. Es ist deine Aufgabe als Entwickler dir über mögliche Fehlersituationen Gedanken zu machen und dann eine passende Fehlerbehandlung zu implementieren. Das ist unabhängig von unserer Software bzw. unserem Framework und zu einem gewissen Grad auch unabhängig von Programmierung an sich.


    Anders ausgedrückt: Angenommen die externe Anfrage schlägt fehl – was sollte das Plugin dann machen? Die Frage musst du beantworten, „Crash and Burn“ ist aber vermutlich nicht die richtige Antwort. Am einfachsten geht die Beantwortung, indem du einfach mal ausprobierst was passiert, wenn die Anfrage fehlschlägt (falsches Token oder mal manuell die URL verändert auf eine ungültige).

    Also folgende Exceptions hätte ich jetzt mal laut Guzzle Docs gefangen:

    Quote


    A Client MUST throw an instance of Psr\Http\Client\ClientExceptionInterface if and only if it is unable to send the HTTP request at all or if the HTTP response could not be parsed into a PSR-7 response object.

    If the request cannot be sent due to a network failure of any kind, including a timeout, the Client MUST throw an instance of Psr\Http\Client\NetworkExceptionInterface.

    Zusätzlich eventuell noch eine ClientException:

    sollte sich irgendwann der Link der Uptimerobot-API ändern.


    Dann hier einmal der Code und zwei weitere Fehler, bei denen ich irgendwelche Exceptions werfen sollte. Welche und wie, weiß ich leider nicht.

    Ich würde mich über jeden Tipp freuen. Wie man merkt, bin ich was die WSC-eigenen Exceptions angeht leider absolut unerfahren.

    Viele Grüße

    Julian

    Managed Webhosting, hochwertige Plugins und individuelle Auftragsarbeiten:

    Julian-Pfeil.de

  • Hallo,

    Dann hier einmal der Code und zwei weitere Fehler, bei denen ich irgendwelche Exceptions werfen sollte. Welche und wie, weiß ich leider nicht.

    werfen solltest du vermutlich gar keine.

    Wie man merkt, bin ich was die WSC-eigenen Exceptions angeht leider absolut unerfahren.

    Wie ich in der Konversation auch schon sagte: Das ist nichts, was spezifisch für WoltLab Suite ist. Exception-Handling (und allgemeiner: Error-Handling) ist unabhängig von unserer Software und auch unabhängig von Programmierung an sich. Den zweiten Absatz aus dem dritten Zitat von mir („Angenommen die externe Anfrage schlägt fehl – was sollte das Plugin dann machen?“) kann dir theoretisch auch jemand beantworten, der noch nie programmiert hat. Denn die Fragestellung ist unabhängig von einer spezifischen Programmiersprache.

    Mir ist auch nicht klar, wie ich das noch anders erklären soll.

    /cc Hanashi der viel mit externen Requests und damit auch den potentiellen Fehlerfällen gearbeitet hat, vielleicht?

  • Hallo,

    bei meinem Code kommt es tatsächlich auch ganz drauf an was ich vorhabe. Rein theoretisch kann man auch \Exception oder \Throwable abfangen, aber da ist dann wirklich alles gecatcht und nicht nur das was von Guzzle kam.

    In deinem Fall müsstest du auf jeden Fall die wcf\system\exception\SystemException abfangen, welche von JSON::decode kommen könnte. Außerdem könnte Guzzle eine GuzzleHttp\Exception\GuzzleException werfen, welche auf jeden Fall abgefangen werden muss. Bzw. hier besser Psr\Http\Client\ClientExceptionInterface abfangen. Je nachdem was du vor hast, kannst du aber auch viel früher einsteigen und z.B. eine GuzzleHttp\Exception\BadResponseException (oder besser Psr\Http\Client\RequestExceptionInterface) abfangen. Es kommt m.M.n. ganz darauf an was du am Ende vor hast, dann weißt du auch was du abfangen und verarbeiten musst.

  • Hanashi Vielen Dank schon mal. Ich denke, dass ich jetzt den ersten Anhalt habe.

    Dann "rethrowe" ich die genannten Exceptions einfach, oder?

    siehe:

    Es kommt m.M.n. ganz darauf an was du am Ende vor hast, dann weißt du auch was du abfangen und verarbeiten musst.

    Mir reicht es, wenn das Plugin durch die Prüfung kommt. Ein benutzerfreundliches Error-Handling schätze ich. Es soll hier ja die Request an die Uptimerobot-API gestellt und dann die entsprechende ReplyData angezeigt werden. Jetzt sollte ich ja irgendwie damit umgehen, falls das nicht reibungslos läuft.

    Viele Grüße

    Julia

    Managed Webhosting, hochwertige Plugins und individuelle Auftragsarbeiten:

    Julian-Pfeil.de

  • Dann "rethrowe" ich die genannten Exceptions einfach, oder?

    Wenn du die Exception einfach nur direkt rethrowst, kannst du dir den catch auch sparen. Das macht nur Sinn, wenn man abhängig von der Exception etwas abweichendes machen möchte.

    Bei einem Exception-Handling geht es immer darum, dass es verschiedene Arten von Fehlern geben kann. Für deinen Fall ist eigentlich nur relevant, welche erwartbaren Fehler auftreten können und ob du diese sinnvoll („graceful“) behandeln kannst.

    Um das mal konkreter zu beschreiben: Stellen wir uns eine fiktive API vor, die alle 10 Minuten abgefragt wird und das Ergebnis zwischengespeichert wird. Gleichzeitig gilt, dass die Antwort für bis zu 60 Minuten gültig ist, auch wenn es zwischenzeitlich eine aktuelle Antwort geben kann. Das Skript zum Abruf kann somit stillschweigend Verbindungsprobleme ignorieren, wenn mal ein Abruf nicht geht, aber der Cache noch „frisch genug“ ist. Einen Fehler im Log würde man somit nur erzeugen, wenn länger als 60 Minuten keine gültige Antwort mehr erhalten wurde. Die Information, dass nachts um 4:20 Uhr die API einmalig nicht erreichbar ist, hat in diesem Szenario keine Relevanz, aber wenn die API dauerhaft unerreichbar ist, will man eine harte Fehlermeldung.

    Übertragen auf dein Beispiel gilt: Du musst dich fragen, welche Probleme bei der Abfrage auftreten können und ob diese überhaupt relevant ist. Das ist „von außen“ schwer zu beurteilen, daher kann ich nur bedingt eine Aussage dazu treffen. Eventuell hilft dir dies: Ist es relevant, ob die API erreichbar ist oder nicht? Ist ein zeitweiser Ausfall stillschweigend tolerierbar?

  • Ist es relevant, ob die API erreichbar ist oder nicht? Ist ein zeitweiser Ausfall stillschweigend tolerierbar?

    Ja, das ist relevant. Ein zeitweiser Ausfall ist nicht stillschweigend tolerierbar, da die Daten in Echtzeit ausgelesen und angezeigt werden. Und auch nur so von Bedeutung sein können. Aber dafür muss ich ja die Exceptions an sich nicht catchen. Wenn die Monitore (ReplyData), wieso auch immer, nicht zur Verfügung stehen, dann werden sie nicht angezeigt. Oder geht es lediglich darum, dass ich das dem Nutzer auch so mitteilen kann?

    Managed Webhosting, hochwertige Plugins und individuelle Auftragsarbeiten:

    Julian-Pfeil.de

    Edited once, last by Julian Pfeil (November 7, 2022 at 6:25 PM).

  • Auch in dem Fall kann es aber Sinn machen, dies sinnvoll zu verarbeiten. Die Fehlermeldung kannst du in das Log schreiben [1] und trotzdem dem Benutzer eine reguläre Fehlermeldung statt einer Exception anzeigen lassen.

    PHP
    try {
        // yada yada yada
    } catch (SomeException $e) {
        \logThrowable($e);
    
        throw new UptimeRobotNotAvailable();
    }

    Der aufrufende Code kann dann spezifisch diese eigene Exception (die du anlegen musst!) fangen und entsprechend behandeln. So kannst du erwartbare Probleme für den Benutzer sauber aufbereiten, aber unerwartete Fehlermeldungen trotzdem in den globalen Exception-Handler laufen lassen.

    Ein Beispiel für unerwartbare Fehler sind etwa TLS-Fehler, weil die Zertifikatsdatenbank des Servers defekt ist. Dies sind eher das Symptom von allgemeinen Systemfehlern und sollten nicht von deinem Code gefangen werden, so dass der globale Exception-Handler greift.

    Deswegen sollte das Catchen so spezifisch wie möglich passieren, damit du nur Fälle berücksichtigst, für die du „zuständig“ bist.

  • So weit wäre ich jetzt:

    PHP
    <?php
    
    namespace wcf\system\exception;
    
    class TKUptimerobotNotAvailable extends \Exception
    {
    }


    Allerdings:

    die Fehlerbehandlung passt so immer noch nicht. Es ist nicht akzeptabel, dass ein Netzwerkfehler innerhalb einer Box dafür sorgt, dass die komplette Seite (!) nicht mehr erreichbar ist.

    Wie behebe ich das nun? Soll/kann ich die Exception als Warning werfen? Falls ja, wie?

    Soll ich sonst anders mit der Exception umgehen?

    Viele Grüße

    Julian

    Managed Webhosting, hochwertige Plugins und individuelle Auftragsarbeiten:

    Julian-Pfeil.de

  • Ich weiß nicht, wie dein gesamter Quelltext nun aussieht, den du im Plugin-Store eingereicht hast. Dementsprechend kann ich nur mutmaßen, was das Problem sein könnte.

    Du solltest prinzipiell zwei Logiken haben:

    1. Zum einen den Erfolgsfall, wenn die Abfrage fehlerfrei durchläuft und du deine Antwortdaten verarbeiten kannst, wie beispielsweise das Darstellen in einer Box.
    2. Zum anderen den Fehlerfall, wenn die Abfrage nicht fehlerfrei durchläuft (und eine Exception geworfen wird), du dementsprechend keine Antwortdaten verarbeiten kannst und dann beispielsweise eine Fehlermeldung in der Box darstellst.

    Folgende Anmerkungen:

    1. Fängst du die Exception TKUptimerobotNotAvailable an einer anderen Stelle in deinem Quelltext und befolgst dann die Logik für den Fehlerfall? Wenn die Exception an keiner Stelle gefangen wird, ist die Seite für den Benutzer nicht mehr erreichbar und ihm wird eine Fehlermeldung vom System präsentiert. Das soll natürlich nicht sein.
    2. RequestExceptionInterface und NetworkExceptionInterface erben von ClientExceptionInterface. Wenn du die spezifischeren Exceptions nicht anders behandeln möchtest (zum Beispiel, indem du dem Benutzer bei NetworkExceptionInterface anzeigst, dass ein Netzwerkfehler aufgetreten sei und bei RequestExceptionInterface anzeigst, dass ein Anfragefehler aufgetreten sei), reicht es aus, wenn du (neben SystemException) lediglich ClientExceptionInterface fängst.

    Um das nochmal in Form von nicht-WSC-spezifischem Pseudo-Quelltext zu verdeutlichen (die tatsächliche Implementation in einem Framework ist natürlich ein wenig komplexer, um auch ein entsprechend sinnvolles und benutzerfreundliches Ergebnis zu liefern):

    bzw. ab PHP 7.1:

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!