2009-08-14 13 views
1

Il s'agit d'un problème intéressant que je n'ai pas encore réussi à résoudre. Je suis en train d'écrire un client qui communique à travers Internet à un serveur. J'utilise le composant TIdTcpClient Internet Direct (Indy) dans Indy 10 en utilisant la personnalité native de RAD Studio 2007.Delphi Indy IdTcpClient opération de lecture renvoyant des données tronquées pour une requête spécifique

Pour obtenir des données du serveur, j'émets une requête HTTP en utilisant SSL sur le port 443 où les détails de ma demande sont contenus dans le corps du message HTTP. Jusqu'ici tout va bien. Le code fonctionne comme le charme, à une exception près.

Une demande que je soumets qui devrait produire une réponse d'environ 336 Ko du serveur (l'en-tête de réponse HTTP contient Content-Length: 344795). Le problème est que je ne récupère que 320KB. La réponse, qui est en XML, est clairement tronquée au milieu d'un élément XML.

Pour ce que ça vaut, le XML est un texte simple. Aucun caractère spécial ne peut expliquer la troncature. Mon composant TIdTcpClient signale simplement qu'après avoir reçu la réponse partielle, le serveur a fermé la connexion correctement (ce qui explique comment chaque réponse devrait être terminée, même celles qui ne sont pas tronquées, donc ce n'est pas un problème).

Je peux faire des appels presque identiques au même serveur où la réponse est aussi plus que quelques kilo-octets, et tout cela fonctionne très bien. Une demande que je fais renvoie environ 850 Ko, une autre renvoie environ 300 Ko, et ainsi de suite.

En résumé, je rencontre ce problème uniquement avec la requête spécifique. Toutes les autres demandes, qui sont nombreuses, reçoivent une réponse complète.

J'ai parlé au créateur du service, et ai fourni des exemples de ma demande. Il a signalé que la demande est correcte. Il m'a aussi dit que quand il envoie la même requête à son serveur, il obtient une réponse complète.

Je suis à perte. Soit le créateur du service se trompe, et il y a effectivement un problème avec la réponse à cette fin, ou il y a quelque chose de particulier dans ma demande.

Y at-il une solution ici qui me manque? Notez que j'ai également utilisé un certain nombre d'autres mécanismes de lecture (ReadString, ReadStrings, ReadBytes, etc) et tous produisent le même résultat, une troncature de cette réponse spécifique à la marque 320 Ko.

Le code n'est probablement pas pertinent, mais je vais l'inclure quand même. Désolé, mais je ne peux pas inclure la requête XML, car elle contient des informations propriétaires. (ReadTimeout est réglé à 20 secondes, mais le retour de la requête en 1 seconde, il est donc pas un problème de délai d'attente.)

 
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; 

Répondre

0

Comme je l'ai mentionné dans ma question initiale, cette connexion utilise SSL. Cela nécessite que vous utilisez un composant IdSSLIOHandlerSocketOpenSSL, que vous affectez à la propriété IOHandler du composant IdTcpClient. Tout cela était dans ma conception originale, et comme je l'ai mentionné, beaucoup de mes demandes ont été répondues correctement.

Au cours du week-end, j'ai découvert une autre demande qui renvoyait une réponse incomplète. J'ai capturé cette requête HTTP originale, et en utilisant un outil appelé SOAP UI, j'ai soumis cette demande au serveur. À l'aide de l'interface utilisateur SOAP, le serveur a renvoyé une réponse complète. Clairement, quelque chose n'allait pas avec mon client, et pas le serveur.

J'ai finalement trouvé la solution en jouant avec différentes propriétés. La propriété qui a finalement corrigé le problème se trouvait dans la propriété SSLOptions de la classe IdSSLIOHandlerSocketOpenSSL. La valeur par défaut de SSLOptions.Method est sslvSSLv2. Lorsque j'ai changé de méthode en sslvSSLv23, toutes les réponses sont retournées.

Pourquoi j'ai été en mesure de récupérer certaines réponses dans leur intégralité et pas tous avant que j'ai fait ce changement est toujours un mystère pour moi. Néanmoins, la définition de IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method à sslvSSLv23 a résolu mon problème.

Merci Remy Lebeau pour vos suggestions.

+1

TIdHTTP prend en charge SSL de la même manière que TIdTCPClient - en affectant un IOHandler SSL avant la connexion. Vous devriez vraiment utiliser le composant TIdHTTP pour gérer le trafic HTTP. C'est pourquoi il existe en premier lieu. –

+1

De même, dans la version actuelle d'Indy 10, la valeur par défaut de la propriété SSLOptions.Method n'est pas sslvSSLv2, elle est sslvTLSv1. Il est logique que la définition de la méthode sur sslvSSLv23 élimine certaines erreurs. Lorsque la méthode est définie sur autre chose que sslvSSLv23, l'autre partie * doit * utiliser cette version SSL spécifique à sa fin, sinon l'établissement de liaison SSL échouera. D'autre part, sslvSSLv23 est plus un joker qui accepte toutes les versions SSL de SSLv2 à TLSv1, et permet aux deux parties de négocier une version SSL compatible. –

1

Puisque vous envoyez une requête HTTP, vous devriez utiliser à la place le composant TIdHTTP du TIdTCPClient directement. Il y a beaucoup de détails sur le protocole HTTP que TIdHTTP gère pour vous que vous devrez gérer manuellement si vous continuez à utiliser TIdTCPClient directement.

Si vous continuez à utiliser TIdTCPClient directement, vous devez au moins arrêter d'utiliser la méthode TIdIOHandler.AllData(). Extrayez l'en-tête de réponse 'Content-Length' (vous pouvez capturer() les en-têtes dans TStringList ou TIdHeaderList, puis utiliser sa propriété Values ​​[], puis transmettre le nombre réel d'octets déclaré à TIdIOHandler.ReadString() ou TIdIOHandler.ReadStream(). Cela permettra de s'assurer que les E/S de lecture n'arrêtent pas de lire prématurément à cause de la déconnexion du serveur à la fin de la réponse.

+0

Merci beaucoup pour votre contribution. J'ai d'abord utilisé le composant TIdHTTP, mais j'ai trouvé que j'avais besoin du contrôle supplémentaire fourni par le composant TIdTcpClient. Je vais essayer l'approche de capture pour voir si cela peut résoudre le problème, mais je suis douteux, étant donné que toutes mes autres demandes sont traitées correctement. Ce sera un jour ou deux avant que je puisse essayer votre suggestion. Encore une fois, merci. Je vais –

+0

J'ai essayé Capture, mais cela ne semble pas apporter de solution. Lorsque j'ai appelé la capture, elle capture jusqu'à ce que la connexion soit fermée par le serveur. Il est alors trop tard pour définir le nombre d'octets. AllData fonctionne correctement dans ce cas. En fait, quand je ReadStream et que je place le paramètre AReadUntilDisconnect sur True, j'obtiens la même réponse que lorsque j'utilise AllData. Je vais parler avec le créateur du service pour voir si le problème est peut-être de leur côté. –

+1

De quel type de contrôle supplémentaire avez-vous besoin exactement? Comme pour Capture(), la seule façon de continuer à capturer jusqu'à la déconnexion est de l'appeler avec les mauvaises valeurs de paramètre. Le terminateur par défaut est une ligne avec un seul '.' caractère sur lui. En revanche, les en-têtes HTTP se terminent par une ligne vide. Vous devrez donc passer une chaîne vide au paramètre ADelim de Capture(). –