Kontakt

Strukturiertes Debugging am Beispiel eines Dateiuploads in Neos CMS/Flow

by Theo Salzmann on 23.01.2020

Zum Thema "Strukturierte Fehleranalysen vs. Stochern" hat Sebastian bereits eine mögliche Herangehensweise erläutert. Dabei erklärt er die Methode abstrakt. Im heutigen Beitrag wird sie auf ein konkretes Problem angewandt:

Problem-Beobachtung:

Ein simpler File-Upload funktioniert nicht und terminiert mit einer ebenso knappen Fehlermeldung.

Knappe Fehlermeldung

Knappe Fehlermeldung

Anmerkung: Referenzen auf den Kunden wurden im Beispiel geschwärzt.

Weiterer Hintergrund:

  • Reines Neos CMS/Flow-System
  • Formular mit Neos.Fusion.Form gebaut
  • Normalfall: Flow erkennt eine hochgeladene Binärdatei und ist in der Lage sie auf ein Modell zu mappen und automatisch eine "Persistent Resource" zu generieren
  • Zusätzlich zu der Fehlermeldung existieren keine weiteren Logs

TL;DR - Wichtigste Tipps

  • Überprüft, ob das Problem client- oder serverseitig besteht
  • Es lohnt sich immer, Probleme im lokalen Code zu verfolgen, anstatt nur in die Quelle auf Github/Gitlab zu schauen
  • File Uploads setzen multipart/form-data als enctype voraus
  • Haltet Dependencies wenn möglich up-to-date
  • Pair Debugging kann extrem hilfreich sein, um sich gegenseitig:
    • dabei zu unterstützen, sinnvolle Annahmen und Experimente zu formulieren
    • dabei zu ertappen, wenn gedanklich falsch abgebogen wird

Strukturierte Fehlersuche - Langfassung 🕵🏻‍♀️

Annahme 1

uploadSignedPDFAction() konnte nicht gecalled werden. Diese Methode erwartet zwei Parameter: ein Formular und die hochgeladene Resource. Wird der Resourcen-Parameter weggelassen, funktioniert der Aufruf. Daraus resultiert die Annahme, dass die im Backend ankommende Resource nicht der Erwartungshaltung des Backends entspricht und bereits im Client etwas schief gegangen ist.

Experiment 1

Wir beobachten in den DevTools im Netzwerk-Tab, wie das übermittelte Request aussieht und versuchen herauszufinden, ob die Parameter korrekt formatiert sind.

Es werden keine Binärdaten übertragen

Es werden keine Binärdaten übertragen

Beobachtungen 1

  • Im Netzwerk-Request wird statt den eigentlichen Binärdaten nur der Dateiname mitgeschickt
  • Flow kann diesen nicht sinnvoll auf unser Model mappen
  • Entscheidende Schlussfolgerung: das Problem liegt im Client, nicht im Backend

Runde 2

Annahme 2

Daraus resultierende Annahme: Die HTML-Struktur unseres Formulars passt nicht.

Recherche 2 im Internet

Fragestellung: Gibt es Besonderheiten, welche in einem HTML-Formular mit Datei-Upload beachtet werden müssen?

Ergebnis 2

Damit Datei-Uploads funktionieren, muss zwingend der enctype="multipart/form-data" am <form>-Tag gesetzt sein.

Dieser fehlt aktuell in unseren DOM.

Enctype fehlt

Enctype fehlt

Runde 3

Annahme 3

Der fehlende enctype  ist die Ursache dafür, dass keine Binärdaten übertragen werden.

Experiment 3

Wir schreiben den enctype manuell in den Browser-DevTools an das Formular-Tag und starten einen Upload. Dabei beobachten wir wieder das Request im Netzwerk-Tab und prüfen, ob der Payload nun korrekt übertragen wird.

Enctype manuell gesetzt

Enctype manuell gesetzt

Beobachtungen 3

  • Der Upload funktioniert, es erscheint keine Fehlermeldung mehr und die Resource wird korrekt gespeichert
  • Der Request Payload ist korrekt
Korrekter Payload (Ausschnitt)

Korrekter Payload (Ausschnitt)

Runde 4

Annahme 4

In Neos.Fusion.Form im Upload  gibt es eine Möglichkeit, um den enctype explizit zu setzen.

Recherche 4

Da sich in der Dokumentation von Neos.Fusion.Form kein passendes Beispiel findet, prüfen wir auf Github den Quelltext (Achtung: hier hätten wir bereits im lokalen Code der Library schauen sollen, da die Versionen abweichen können).

Ergebnis 4

Es gibt drei Wege, wie das Encoding gesetzt werden kann:
   1. standardmäßig ist die `method` von Form bereits `post` -> in diesem Fall sollte das encoding automatisch `multipart/form-data` sein
   2. die `method` kann auch manuell mit `form.method` auf post gesetzt werden
   3. der enctype kann manuell mit `form.encoding` gesetzt werden

Neos.Fusion.Form-Ausschnitt, welcher zeigt, wie das encoding (enctype) gesetzt werden können soll

Neos.Fusion.Form-Ausschnitt, welcher zeigt, wie das encoding (enctype) gesetzt werden können soll

Runde 5

Annahme 5

Unser eigener Fusion code könnte das Form-Element der Library falsch benutzen, weshalb der enctype nicht gesetzt wird.

Experiment 5

Überprüfen der verschiedenen Varianten in unserem eigenen Fusion-Code, um herauszufinden, ob es eine Möglichkeit gibt, dass der enctype korrekt gerendert wird und die Binärdaten entsprechend korrekt versendet werden.

Ergebnis 5

Keine der Möglichkeiten führt zum Erfolg.

Runde 6

Annahme 6

Das Problem liegt im Neos.Fusion.Form Code, da wir die API (vermutlich - versteckte Annahme ;D) korrekt benutzen.

Recherche 6

Wir überprüfen den lokal ausgecheckten Library-Code im laufenden Docker-Container und schauen, ob es dort Auffälligkeiten beim Setzen des enctypes gibt. Dabei suchen wir nach der Stelle im Code, in welchem das Form-Model mit der für das Rendering verantwortlichen Fusion-Component verdrahtet wird.

Ergebnis 6

  • Feststellung:
    • in der Verdrahtung sollte `enctype={form.getEncoding()}` so gesetzt werden
    • Aber stattdessen: `form.getEncoding` ohne die aufrufenden Klammern
    • mit aufrufenden Klammern funktioniert der Upload entsprechend
Fehlerhafter Libary Code

Fehlerhafter Libary Code

Runde 7

Annahme 7

Wir haben den aktuellen Stand der Library ausgecheckt und in diesem ist das Problem noch vorhanden.

Recherche 7 und Klärung der Ursache

Wir prüfen unsere lokale Library-Version mit der des letzten offiziellen Library-Releases.
Dabei stellen wir fest, dass die lokale Version 1.0.0--beta3 der letzte Release aber bereits 1.0.3 ist.

Jetzt überprüfen wir noch, ob der Fehler im  Github-Repository noch vorhanden ist, oder ob er bereits behoben wurde.

Er ist behoben, wir können also einfach ein composer update machen. Andernfalls könnten wir selbst einen Fix mit entsprechendem Pull Request erstellen.

Nach dem Update der Library funktioniert der Code - unsere Detektivarbeit hat sich ausgezahlt =)

Schlusswort

Debugging kann manchmal tricky sein. Daher ist es umso wichtiger, strukturiert und systematisch vorzugehen. Ich empfehle daher, Annahmen immer so explizit wie möglich zu formulieren und sie im Anschluss entsprechend mit einem klar designten Experiment zu verifizieren.

Falls ihr Feedback oder Fragen habt, schreibt mir einfach via Twitter an @on3iro