2009-08-14 6 views
1

Dies ist ein interessantes Problem, das ich bisher nicht lösen konnte.Delphi Indy IdTcpClient-Leseoperation, die abgeschnittene Daten für eine bestimmte Anfrage zurückgibt

Ich schreibe einen Client, der über das Internet an einen Server kommuniziert. Ich verwende die TIdTcpClient Internet Direct (Indy) -Komponente in Indy 10 mit der ursprünglichen Persönlichkeit von RAD Studio 2007.

Um Daten vom Server abzurufen, erstelle ich eine HTTP-Anfrage mit SSL über Port 443, wo meine Anfragedetails im HTTP-Nachrichtentext enthalten sind. So weit, ist es gut. Der Code funktioniert mit einer Ausnahme wie Charme.

Es gibt eine Anfrage, die ich einreiche, die eine Antwort von etwa 336 KB vom Server produzieren sollte (der HTTP-Antwortheader enthält Content-Length: 344795). Das Problem ist, dass ich nur 320KB zurückbekomme. Die Antwort, die in XML ist, wird in der Mitte eines XML-Elements deutlich abgeschnitten.

Für was es wert ist, ist das XML einfacher Text. Es gibt keine Sonderzeichen, die für die Kürzung verantwortlich sein können. Meine TIdTcpClient-Komponente berichtet einfach, dass der Server die Verbindung ordnungsgemäß geschlossen hat, nachdem die Teilantwort empfangen wurde (auf diese Weise wird erwartet, dass jede Antwort abgeschlossen wird, auch wenn diese nicht abgeschnitten wird. Dies ist also kein Problem).

Ich kann fast identische Anrufe an den gleichen Server, wo die Antwort auch mehr als ein paar K Bytes ist, und alle diese funktionieren gut. Eine Anfrage, die ich mache, gibt ungefähr 850 KB zurück, eine andere gibt ungefähr 300 KB zurück und so weiter.

Kurz gesagt, ich stoße dieses Problem nur mit der einen spezifischen Anfrage. Alle anderen Anfragen, von denen es viele gibt, erhalten eine vollständige Antwort.

Ich habe mit dem Ersteller des Dienstes gesprochen und Beispiele meiner Anfrage geliefert. Er hat berichtet, dass die Anfrage korrekt ist. Er sagte mir auch, dass er, wenn er meine Anfrage an seinen Server richtet, eine vollständige Antwort bekommt.

Ich bin ratlos. Entweder ist der Ersteller des Dienstes falsch, und es gibt tatsächlich ein Problem mit der Antwort zu diesem Zweck, oder es ist etwas eigenartig an meiner Anfrage.

Gibt es hier eine Lösung, die ich vermisse? Beachten Sie, dass ich auch eine Reihe anderer Lesemechanismen (ReadString, ReadStrings, ReadBytes usw.) verwendet habe und alle das gleiche Ergebnis, eine Kürzung dieser einen spezifischen Antwort bei der 320KB-Markierung, erzeugen.

Der Code ist wahrscheinlich nicht relevant, aber ich werde es trotzdem aufnehmen. Entschuldigung, aber ich kann die XML-Anfrage nicht einschließen, da sie proprietäre Informationen enthält. (Readtimeout auf 20 Sekunden eingestellt, aber die Anforderung kehrt in etwa 1 Sekunde, also ist es kein Timeout Problem.)

 
function TClient.GetResponse(PayloadCDS: TClientDataSet): String; 
var 
    s: String; 
begin 
    try 
    try 
     s := GetBody(PayloadCDS); 
     IdTcpClient1.Host := Host; 
     IdTcpClient1.Port := StrToInt(Port); 
     IdTcpClient1.ReadTimeout := ReadTimeout; 
     IdTcpClient1.Connect; 
     IdTcpClient1.IOHandler.LargeStream := True; 
     //IdTcpClient1.IOHandler.RecvBufferSize := 2000000; 
     IdTcpClient1.IOHandler.Write(s); 
     Result := IdTcpClient1.IOHandler.AllData; 
    except 
     on E: EIdConnClosedGracefully do 
     begin 
     //eat the exception 
     end; 
     on e: Exception do 
     begin 
     raise; 
     end; 
    end; 
    finally 
    if IdTcpClient1.Connected then 
     IdTcpClient1.Disconnect; 
    end; 
end; 

Antwort

0

Wie ich in meiner ursprünglichen Frage erwähnt habe, verwendet diese Verbindung SSL. Dazu müssen Sie eine IdSSLIOHandlerSocketOpenSSL-Komponente verwenden, die Sie der IOHandler-Eigenschaft der IdTcpClient-Komponente zuweisen. All dies war in meinem ursprünglichen Design, und wie ich bereits erwähnte, wurden viele meiner Anfragen richtig beantwortet.

Über das Wochenende entdeckte ich eine weitere Anfrage, die eine unvollständige Antwort zurückgab. Ich habe diese ursprüngliche HTTP-Anfrage erfasst und mithilfe eines Tools namens SOAP UI diese Anfrage an den Server gesendet. Mithilfe der SOAP-Benutzeroberfläche gab der Server eine vollständige Antwort zurück. Offensichtlich stimmt etwas nicht mit meinem Kunden und nicht mit dem Server.

Ich fand schließlich die Lösung, indem ich nur mit verschiedenen Eigenschaften herumfummelte. Die Eigenschaft, die das Problem schließlich korrumpierte, befand sich in der SSLOptions-Eigenschaft der IdSSLIOHandlerSocketOpenSSL-Klasse. Der Standardwert von SSLOptions.Method ist sslvSSLv2. Als ich die Methode in sslvSSLv23 änderte, sind alle Antworten vollständig.

Warum konnte ich einige Antworten vollständig abrufen und nicht alle, bevor ich diese Änderung vorgenommen habe, ist für mich immer noch ein Rätsel. Nichtsdestotrotz habe ich mein Problem gelöst, indem ich IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method auf sslvSSLv23 gesetzt habe.

Vielen Dank Remy Lebeau, für Ihre Vorschläge.

+1

TIdHTTP unterstützt SSL auf die gleiche Weise wie TIdTCPClient - durch Zuweisung eines SSL-fähigen IOHandlers vor der Verbindung. Sie sollten wirklich die TIdHTTP-Komponente für die Verarbeitung von HTTP-Datenverkehr verwenden. Deshalb existiert es an erster Stelle. –

+1

In der aktuellen Indy 10-Version ist der Standardwert der Eigenschaft SSLOptions.Method nicht sslvSSLv2, sondern sslvTLSv1. Es macht Sinn, dass das Setzen der Methode auf sslvSSLv23 einige Fehler beheben würde. Wenn die Methode auf etwas anderes als sslvSSLv23 eingestellt ist, muss * die andere Partei * diese spezifische SSL-Version am Ende verwenden oder der SSL-Handshake wird fehlschlagen. sslvSSLv23 dagegen ist eher ein Catch-All-Platzhalter, der alle SSL-Versionen von SSLv2 bis TLSv1 unterstützt und beide Parteien eine kompatible SSL-Version aushandeln kann. –

1

Da Sie eine HTTP-Anforderung senden, sollten Sie die TIdHTTP Komponente anstelle der unter Verwendung von TIdTCPClient-Komponente direkt. Es gibt viele Details über das HTTP-Protokoll, das TIdHTTP für Sie verwaltet, das Sie manuell behandeln müssten, wenn Sie TIdTCPClient weiterhin direkt verwenden.

Wenn Sie TIdTCPClient weiterhin direkt verwenden, sollten Sie zumindest die Methode TIdIOHandler.AllData() beenden. Extrahieren Sie den Header "Content-Length" (Sie können die Header in eine TStringList oder TIdHeaderList Capture() aufnehmen und dann die Values ​​[] -Eigenschaft) verwenden und die tatsächlich gemeldete Byteanzahl entweder an TIdiOHandler.ReadString() oder TIdiOHandler.ReadStream übergeben(). Dadurch wird sichergestellt, dass die Lese-E/A nicht zu früh aufliest, weil der Server am Ende der Antwort nicht verbunden ist.

+0

Vielen Dank für Ihre Eingabe. Ich habe anfangs die TIdHTTP-Komponente verwendet, aber festgestellt, dass ich das zusätzliche Steuerelement benötigt, das die TIdTcpClient-Komponente bereitgestellt hat. Ich werde den Capture-Ansatz versuchen, um zu sehen, ob das das Problem lösen kann, aber ich bin zweifelhaft, da alle meine anderen Anfragen korrekt behandelt werden. Es wird ein Tag oder so sein, bevor ich Ihren Vorschlag ausprobieren kann. Nochmals vielen Dank. Ich werde –

+0

Ich versuchte Capture, aber es scheint keine Lösung zu bieten. Wenn ich capture anrufe, fängt es an, bis die Verbindung vom Server geschlossen wird. Bis dahin ist es zu spät, die Bytezahl zu setzen. AllData funktioniert in diesem Fall gut. Wenn ich ReadStream und den Parameter AReadUntilDisconnect auf True setze, erhalte ich die gleiche Antwort wie bei der Verwendung von AllData. Ich werde mit dem Ersteller des Dienstes sprechen, um zu sehen, ob vielleicht das Problem an ihrem Ende ist. –

+1

Welche Art von extra Kontrolle brauchen Sie genau? Wie bei Capture() ist die einzige Möglichkeit, mit der Erfassung fortzufahren, die Trennung, wenn Sie sie mit den falschen Parameterwerten aufrufen. Der Standardterminator ist eine Zeile mit einem einzelnen '.' Zeichen darauf. HTTP-Header hingegen werden stattdessen durch eine Leerzeile beendet. Sie müssten also eine leere Zeichenfolge an den ADelim-Parameter von Capture() übergeben. –