Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   JSON mit Indy verschicken -> Fehler (https://www.delphipraxis.net/184029-json-mit-indy-verschicken-fehler.html)

Crocotronic 20. Feb 2015 13:55

JSON mit Indy verschicken -> Fehler
 
Hallo,
ich scheitere zurzeit am Verschicken eines JSON-Objekts. Die Schnittstele ist hierbei JSON-RPC und die Verbindung wird verschlüsselt.
Mein Code zum wegschicken sieht folgendermaßen aus:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var sl: TStringList;
begin
 sl:= TStringList.Create;
 sl.Text:= '{"id":"ID","method":"authenticate","params":{"user":"USER","password":"PASSWORD"},"jsonrpc":"2.0"}';

 IdHTTP1.IOHandler:= IdSSLIOHandlerSocketOpenSSL1;

 IdHTTP1.Request.ContentType:= 'application/json-rpc';
 IdHTTP1.Request.Connection:= 'Keep-Alive';
 IdHTTP1.Request.ContentLength:= Length(sl.Text)* SizeOf(Char);
 IdHTTP1.Request.ContentEncoding := 'utf-8';

 showmessage(IdHTTP1.Post('https://www.test.de/jsonrpc.do', sl, IndyTextEncoding(encUTF8)));

 sl.Free;
end;
Als Antwort bekomme ich jedesmal:
Code:
{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error: Unexpected character ('%' (code 37)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')\n at [Source: org.apache.catalina.connector.CoyoteInputStream@2bcf7275; line: 1, column: 2]"}}
Es sieht so aus, als würde Indy irgendwo ein '%' in den Request basteln.
Ich habe herausgefunden, dass sobald aber am Anfang ein Sonderzeichen steht, wie z.B. '{', '"' oder '&', antwortet mir der Server mit der oben beschriebenen Nachricht, dass unerwarteterweise ein '%' gefunden wurde.
Ich hätte ja sofort gedacht, dass da die Zeichensätze nicht übereinstimmen, aber in der Post-Funktion gebe ich ja explizit den UTF-8-Encoder mit.

Ich hoffe, es kann mir jemand helfen! :)

Viele Grüße
Croco

mjustin 20. Feb 2015 15:02

AW: JSON mit Indy verschicken -> Fehler
 
Man kann keine TStringList verwenden um mit TIdHTTP einen JSON String zu posten. TIdHTTP.Post() wird den Inhalt der TStringList in einer Art kodieren die die JSON Daten ungültig macht. Stattdessen muss der String in einen TStream geschrieben werden.



Diese Zeile ist überflüssig, Indy berechnet die Länge selbst:

Delphi-Quellcode:
 IdHTTP1.Request.ContentLength:= Length(sl.Text)* SizeOf(Char);


Auch das Angeben des Zeichensatzes im Post ist nicht notwendig:

Delphi-Quellcode:
showmessage(IdHTTP1.Post('https://www.test.de/jsonrpc.do', sl));

Crocotronic 20. Feb 2015 15:51

AW: JSON mit Indy verschicken -> Fehler
 
Okay, das erklärt einiges, vielen Dank!

Wenn ich das nun mit dem Stream mache, bekomme ich wieder ein Error vom Server, mit dem kann ich aber noch weniger anfangen :pale:
Erstmal der überarbeitete Code:
Delphi-Quellcode:
procedure SaveStringToStream(AStream: TStream; AString: String);
var
  aStrLen: Integer;
begin
  aStrLen := Length(AString);
  AStream.WriteBuffer(aStrLen, SizeOf(Integer)); // <-- weglassen!
  AStream.WriteBuffer(Pointer(AString)^, aStrLen);
end;

procedure TForm1.Button1Click(Sender: TObject);
var Stream: TMemoryStream;
begin
 Stream:= TMemoryStream.Create;
 SaveStringToStream(Stream,'{"id":"ID","method":"authenticate","params":{"user":"USER","password":"PASSWORD"},"jsonrpc":"2.0"}');

 IdHTTP1.IOHandler:= IdSSLIOHandlerSocketOpenSSL1;

 IdHTTP1.Request.ContentType:= 'application/json-rpc';
 IdHTTP1.Request.Connection:= 'Keep-Alive';

 Stream.Seek(0, soFromBeginning);
 showmessage(IdHTTP1.Post('https://test.de/jsonrpc.do', Stream));

 Stream.Free;
end;
Folgende Antwort:
Code:
{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error: Invalid UTF-32 character 0x6469227b(above 10ffff) at char #1, byte #7)"}}
Die selbe Rückmeldung bekomme ich auch wenn ich totalen Mist hinschicke.

EDIT: Okay, hab meinen Fehler gefunden. Anscheinend darf die Länge des Strings nicht im Stream gespeichert werden. Funktioniert nun super, vielen Dank Michael!

mjustin 20. Feb 2015 16:12

AW: JSON mit Indy verschicken -> Fehler
 
Zitat:

Zitat von Crocotronic (Beitrag 1290777)
Funktioniert nun super, vielen Dank Michael!

Gerne!

Noch ein Tipp: der OpenSSLHandler muss in der aktuellen Indy Version nicht mehr erzeugt und zugewiesen werden - Indy macht das nun automatisch sobald eine https Adresse angesprochen wird.

Delphi-Quellcode:
IdHTTP1.IOHandler:= IdSSLIOHandlerSocketOpenSSL1;
kann damit entfallen.

p.s. es funktioniert zwar anscheinend auch ohne diese Angabe, aber das Encoding utf-8 sollte im Request Objekt angegeben werden, damit dem Server das gesendete Encoding sicherheitshalber mitgeteilt wird:

Delphi-Quellcode:
IdHTTP1.Request.ContentEncoding := 'utf-8';

michaelg 2. Jan 2018 13:06

AW: JSON mit Indy verschicken -> Fehler
 
Hallo,

sorry, wenn ich das Thema noch mal aufreisse, ich habe aktuell ein ähnliches Problem. Ich bekomme beim Versenden von Pushnachrichten per FCM einen "json parse error: unexcepted Character at ...". Das ist immer der erste Buchstabe des Parameters aMessage. Das Problem tritt auf, sobald mindestens ein Leerzeichen in der Message ist.

Schicke ich "Bitte ruf mal an", kommt der Fehler mit Hinweis auf den Buchstaben B.
Schicke ich "Bitterufmalan" oder "Bitte_ruf_mal_an", kommt kein Fehler und die Nachricht geht einwandfrei durch.

Meine Vermutung: Es muss irgendwas mit der Codierung der Zeichen zu tun haben. Aber ich steh hier seit heute morgen mit einem Brett vorm Kopf. Das kann nur eine Kleinigkeit sein, aber ich sehe sie nicht.

Noch ein Hinweis: Von der Firebase-Console kommen Pushnachrichten einwandfrei in meiner App an, auch wenn sie Leerzeichen beinhalten.

Hier der Quelltext der kleinen Funktion:

Code:
function PushSend(aToDeviceToken:String;aNick:String;aMessage:String):Boolean;
var
  idHTTP:tIdHTTP;
  IdIOHandler:TIdSSLIOHandlerSocketOpenSSL;
  jSonString:String;
  jSonToSend:tStringStream;
  response:String;
begin
  result:=True;
  IdHTTP := TIdHTTP.Create(nil);
  try
    IdIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
    IdIOHandler.SSLOptions.Method := sslvSSLv23;
    IdHTTP.Request.BasicAuthentication:=False;

    IdHTTP.IOHandler := IdIOHandler;

    IdHTTP.Request.ContentType := 'application/json';
    IdHTTP.Request.Charset := 'UTF-8';
    IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'key='+MG_SERVERKey;

    jsonString := '{"to" : "'+atoDeviceToken+'",' +
                   '"data" : {' +
                   '"nick" : "'+aNick+'",' +
                   '"body" : "'+aMessage+'",' +
                   '"room" : "Testraum"' +
                   '},}';

    JsonToSend := TStringStream.Create(jsonString,TEncoding.UTF8);
    try
      response := IdHTTP.Post('https://fcm.googleapis.com/fcm/send', JsonToSend);
      response := response.Replace(#10, '');
    except
      on E: EIdHTTPProtocolException do begin
        response := e.ErrorMessage;
        result:=false;
      end;
    end;
    showmessage('Response:'+response);
  finally
    IdHTTP.Free;
  end;
Ich habe
Code:
ContentEncoding='utf-8';
auch bereits mal gesetzt.

Und ich habe den Stringstream auch schon durch einen tStream ersetzt und bin genauso vorgegangen wie in dem vorangegangenen Post. Trotzdem kommt der Fehler immer, sobald in der Message ein Leerzeichen ist.

Kann mir jemand einen Schups geben?

Der schöne Günther 2. Jan 2018 13:25

AW: JSON mit Indy verschicken -> Fehler
 
Aufgrund des Syntax-Fehlers (letzte Zeile des Strings) wundert es mich dass das überhaupt durchkommt.

PS: Geh doch mal mit dem Debugger durch, was wirklich konkret in deinen Variablen drin steht. Ich würde so ein Json-Objekt echt nicht per Hand durch Strings zusammenbauen sondern das mit
Delphi-Quellcode:
uses System.Json
machen, da muss man sich um das Maskieren von Sonderzeichen in Strings auch nicht mehr kümmern.

michaelg 2. Jan 2018 14:19

AW: JSON mit Indy verschicken -> Fehler
 
Du meinst das Komma, ok, hab ich rausgenommen. Daran lag es nicht, das hat er tatsächlich gefressen.

Ich hab den Fehler gefunden, aMessage wurde komischerweise bereits mit Anführungszeichen übergeben, so stand im json dann

"body" : ""Bitte ruf mal an""

Die Message kam aus einem tMemo. Ich hatte den Inhalt unüblicherweise mit eMemo.lines.commatext ausgelesen und der fügt wohl selbst Anführungszeichen dazu, wenn Leerzeichen im String sind. War mir auch neu, aber ein blöder Nebeneffekt, der dann die Ursache für den Fehler war. Hab mir den Inhalt zig mal angesehen und immer übersehen.

Die Funktion ansich ist also völlig ok. Danke für die Hilfe und sorry.

himitsu 2. Jan 2018 15:40

AW: JSON mit Indy verschicken -> Fehler
 
.CommaText nutzt .DelimitedText und das achtet auf die Einstellung von .StrictDelimiter, was bei dir besser True sein sollte.
Denn standardmäßig reagiert .DelimitedText nicht nur auf das DelimiterChar, sondern auch auf #1 bis ' ' und somit werden diese Zeichen standardmäßig ebenfalls gequotet.

Wie auch beim manuellen Zusammenbauen von SQL-Queries würde ich davon abraten das " hart im Text einzubauen und stattdessen Delphi-Referenz durchsuchenQuoteString die passende Quote-Funktion zu benutzen. (eine, welche nach der Syntax von C-Strings quoted und nicht wie QuoteString nach der Syntax von Pascal)

Oder du machst es gleich richtig und nutzt parametrisierte Funktionen, also eine JSON-Klasse, um damit den JSON-String zu generieren. :stupid:

HolgerX 2. Jan 2018 18:24

AW: JSON mit Indy verschicken -> Fehler
 
Hmm..

und wenn doch ein " im Text sein soll, dann muss der Escaped werden, wie auch einige andere Zeichen!

Unten auf
https://www.json.org/

sind die Zeichen aufgelistet!


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:02 Uhr.

Powered by vBulletin® Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2019 by Daniel R. Wolf