Vielleicht ist ja einer der anderen Modulentwickler oder Perl Experten schon mal über dieses Problem gestolpert und kann mir kurz helfen.
Innerhalb eines eigenen Perl Moduls rufe ich ein Kommandozeilentool auf und hole mir den Output in eine Perl Variable. Irgendwann rufe ich dann encode_json() auf und wandle diese Daten in einen JSON String um. Später decodiere ich diesen String wieder mit decode_json() und hier scheint es unter bestimmten Umständen zu knallen. Meine Vermutung ist, dass das an irgendwelchen Sonderzeichen oder einem blöden encoding der Daten liegt und decode deshalb bei bestimmten Zeichen Probleme macht.
Hat sowas schon mal jemand beobachtet bzw. kann mir sagen, was man tun könnte, um den Fehler beim decode_json() Aufruf zu verhindern?
Was genau knallt denn? FHEM beendet sich komplett?
Rufe die JSON Funktionen in einem eval auf.
unabhängig von deiner konkreten problem ursache:
da decode_json bei allen möglichen problemen aussteigt statt einen fehler zurück zu melden sollte man es immer in ein eval steckenmy $decoded = eval { decode_json($json) };
if($@) {
Log3 $name, 2, "$name: decode_json failed: ".$@;
}
gruß
andre
Ein eval ist schon drum, habe nur die Fehlerauswertung vergessen gehabt, deshalb knackt das Modul an der Stelle relativ unspezifisch ab. Nachdem ich aber an anderer Stelle schon Probleme hatte Umlaute mit einem regex zu finden und hier ein explizites encode_utf8 verwenden musste, vermute ich hier ein ähnliches Problem.
regex und umlaute vertragen sich leider nicht wirklich. jedenfalls dann wenn es auf mehr als einer perl version laufen soll. zumal fhem zwar utf8 verwendet aber explizit keine perl utf8 strings.
Ja die beliebte perl und unicode - Problematik. Hat mich im telegrambot-Modul einiges an Aufwand gekostet, da sich hier auch die perl-Versionen unterschiedlich verhalten. Wenn Du Deinen Code mal postest, kann ich versuchen zu helfen.
https://github.com/mumpitzstuff/fhem-GCALVIEW/blob/master/FHEM/57_GCALVIEW.pm (https://github.com/mumpitzstuff/fhem-GCALVIEW/blob/master/FHEM/57_GCALVIEW.pm)
In der Funktion GCALVIEW_doEnd(). Hab auch grad gesehen das doch kein eval drum war. Aber ist auch egal ob es abkackt oder nichts raus kommt. Für den Anwender das Gleiche.
Zitat von: mumpitzstuff am 09 November 2017, 19:58:28
Aber ist auch egal ob es abkackt oder nichts raus kommt. Für den Anwender das Gleiche.
nein :). ein modul das abschmiert ist ganz sicher schlimmer als ein modul das ab und zu bei fehleingaben nichts tut aber zumindest einen fehler ins log schreibt. ersteres sollte niemals passieren, letzteres ist zwar blöd aber so lange es nur ab und zu passiert zumindest akzeptabel. vor allem wenn den fehler trotzdem sucht und versucht zu beheben.
Da gebe ich dir eigentlich recht, nur in diesem Fall ist das Problem nicht temporär. Hier wird ein Kalender eingelesen. Failed das 1x dann failed das immer. Ich habs jetzt nachvollziehen können indem ich diverse Sonderzeichen in einem Kalendereintrag geschrieben habe z.B. (){}[]|<>'"´ oder ähnliches:
unexpected end of string while parsing JSON string, at character offset 321 (before "(end of string)") at ./FHEM/57_GCALVIEW.pm line 510
Ich vermute es handelt sich hier um Zeichen, die im JSON Format bereits irgend eine Bedeutung haben. Diese müsste man wahrscheinlich irgendwie escapen und hinterher wieder zurück konvertieren.
Bevor ich das Modul jetzt bei mir einrichten muss, gib vielleicht noch ein paar Informationen:
- Verstehe ich das richtig, dass das JSON von Dir erzeugt wird um die Daten zwischen dem blockingcall und der finishfn zu übertragen?
- Welche Fehlermeldung kommt denn - ich vermute, dass es auch an "nonref"-Fehlern liegen könnte?
- Bau doch mal ein eval ein wie schon vorgeschlagen da kann dann so etwas stehen:
eval {
my $json = JSON->new->allow_nonref;
$jo = $json->decode($calDataJson);
}
Zitat von: mumpitzstuff am 09 November 2017, 22:32:18
Ich vermute es handelt sich hier um Zeichen, die im JSON Format bereits irgend eine Bedeutung haben. Diese müsste man wahrscheinlich irgendwie escapen und hinterher wieder zurück konvertieren.
Mmmh Du nicht zuerst ein base64 encoding in Deinem Code?
Also ich packe die Output Daten vom Kommandozeilen Programm in ein Array rein und will jetzt eigentlich nur dieses Array vom BlockingCall in die finishfn übertragen und dort wieder dieses array verwenden.
encode/decode json hat den charmanten Vorteil, das man hier quasi ein array einpacken und auch wieder auspacken kann. Allerdings macht das jetzt anscheinend erhebliche Probleme bei im JSON Format vorbelegten Zeichen wie z.B. ". Die jetzt alle zu escapen und unescapen wird eine elende Fummelei.
Deshalb andere Frage, die dieses Problem lösen würde:
Wie kann man ein Array in einen String umwandeln und aus diesem String wieder ein Array generieren, ohne dabei Probleme mit diversen Sonderzeichen zu erhalten?
Ich glaub ich habs:
use Storable qw(freeze thaw);
$calData = eval {encode_base64(freeze(\@calStruct), '')};
@calData = eval {@{thaw(decode_base64($calDataJson))} if ('' ne $calDataJson)};
Damit kackt mir das Modul zumindest nicht mehr ab.
json ist der richtige Weg.
* json (richtig verwendet) überträgt Text(!) Daten, kümmert sich selber um die richtige codierung, escaped selbstständig alle Sonderzeichen.
* die perl json module sind doof weil sie bei jeder sich bietenden Gelegenheit Fehler werfen die zum Abbruch des Programmes führen.
* Mime64 ist für Binärdaten (0x00 .. 0xff). Bei Unicode Zeichenketten nicht mehr verwendbar weil Unicode Zeichen enthält (enthalten kann) welche > 0xff sind.
JSON decode erwartet einen utf8 byte(!) Stream. Das Problem hier ist das perl bei diversen IO Operationen eigene Konvertierungen vornimmt und es oft unklar ist ob das nun ein UNICODE CHARACTER ist (Besteht aus einem einzigen Zeichen) oder ob es sich um einen UTF8 Bytestream handelt. Wenn man sich das ausgeben lässt sieht beides gleich aus ...
Testen kann man das jedoch. Dieses Zeichen 我 ist irgendwas chinesisches. Wenn man das (und jedes andere chinesische, kyrillische usw) in einen String kopiert, es dann überträgt und anschließend auf das was ankommt ein "length($in)" loslässt. Kommt dabei eine 1 raus handelt es sich um einen String mit UNICODE CHARACTER. Der darf nicht in JSON decode rein. Kommt eine Zahl > 1 raus ist es ein bytestream. Der darf rein. (EDIT: Wenn das passiert muss man sich die Übertragungskette anschauen und reparieren. Dann wird bei der Übertragung unzulässig de- und kodiert)
Wichtig ist es das wirklich sauber (beim Entwickeln) zu testen. Sonst kann es durchaus sein das alles gut zu sein scheint. Überträgt man nur "normale" Zeichen klappt alles. Wenn dann doch, durch Zufall, plötzlich ein Sonderzeichen (so etwas á) in der Übertragung auftaucht macht es buff ...
vg
joerg
edit in Absatz 3 zur Klarstellung ergänzt, de- kodierung
Ich hatte versucht den output der Konsolenapplikation als Ganzes mit encode_utf8 umzuwandeln, dann zu verarbeiten und dann in json zu konvertieren. Da ist mir das decode_json auch wieder abgeschmiert. Wahrscheinlich weil bei der Verarbeitung Perl wieder irgendwas umgewandelt hat (hast du ja auch erwähnt). Eventuell könnte hier ein encode_utf8 direkt beim pushen ins Array helfen, das man dann später zu json konvertiert. Wenn ich mehr Zeit habe probiere ich das der Vollständigkeit halber auch mal aus.
Danke für die ausführlichen Informationen!
Achtung, trial and error ist an der Stelle einzig Frust und Zeitverschwendung (ich sage das aus der Erfahrung vieler und langer Nächte ...). DONT DO IT !
Du kannst (und solltest) das strukturiert mit dem length() debuggen Vom Sender zum Empfänger, Stück für Stück.
Ganz wichtig ist das Verständnis des Unterschiedes zwischen UTF8 und UNICODE CHARACTER.
Pflichtlektüre (insbesondere die Antwort von jrockway):
https://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default