2009-08-14 132 views
1

这是一个有趣的问题,我还没有能够解决。Delphi Indy IdTcpClient读操作返回截断数据为一个特定的请求

我在写一个客户端,它可以通过Internet与服务器进行通信。我在Indy 10中使用RAD Studio 2007原生个性使用TIdTcpClient Internet Direct(Indy)组件。

要从服务器获取数据,我使用端口443上的SSL发出HTTP请求,其中我的请求详细信息包含在HTTP消息主体中。到现在为止还挺好。代码的作用就像魅力一样,只有一个例外。

我提交的一个请求应该从服务器产生一个约336 KB的响应(HTTP响应头包含Content-Length:344795)。问题是我只得到320KB。 XML中的响应显然在XML元素的中间被截断。

值得一提的是,XML是简单的文本。没有可以解释截断的特殊字符。我的TIdTcpClient组件简单地报告说,在收到部分响应后,服务器优雅地关闭了连接(这是每个响应预期完成的方式,即使那些未被截断的,所以这不是问题)。

我可以对同一台服务器进行几乎相同的调用,其中响应也超过几K字节,并且所有这些工作都很好。一个请求返回大约850 KB,另一个返回大约300 KB,依此类推。

总之,我遇到这个问题只与一个特定的请求。所有其他请求(其中有很多请求都会收到完整的响应)。

我已经跟服务的创建者交谈过,并提供了我的请求的示例。他报告说请求是正确的。他还告诉我,当他向我的服务器发出同样的请求时,他会得到完整的回复。

我不知所措。无论是服务的创建者都是错误的,实际上在响应方面存在问题,或者我的请求有一些特殊之处。

有没有解决方案,我错过了?请注意,我还使用了许多其他读取机制(ReadString,ReadStrings,ReadBytes等),并且都产生了相同的结果,在320KB标记处截断了此特定响应。

该代码可能不相关,但我仍然会将其包含在内。抱歉,我不能包含XML请求,因为它包含专有信息。 (ReadTimeout被设定为20秒,但在约1秒的请求返回,所以它不是超时问题。)

 
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; 

回答

0

正如我在我原来的问题中提到的,这个连接使用SSL。这要求您使用IdSSLIOHandlerSocketOpenSSL组件,该组件分配给IdTcpClient组件的IOHandler属性。所有这些都是我最初的设计,正如我所提到的,我的许多要求都得到了正确的回应。

周末我发现了另一个请求,它返回的是不完整的响应。我捕获了原始的HTTP请求,并使用称为SOAP UI的工具将该请求提交给服务器。使用SOAP UI,服务器返回了完整的答案。显然,我的客户端出了问题,而不是服务器。

我终于找到了解决方案,只是摆弄各种属性。最终导致问题的属性出现在IdSSLIOHandlerSocketOpenSSL类的SSLOptions属性中。 SSLOptions.Method的默认值是sslvSSLv2。当我将Method更改为sslvSSLv23时,所有响应都返回完成。

为什么我能够在完成之前检索到一些响应,而不是所有响应之前,我对这个改变仍然是一个谜。尽管如此,设置IdSSLIOHandlerSocketOpenSSL.SSLOptions.Method到sslvSSLv23解决了我的问题。

谢谢雷米Lebeau,为您的建议。

+1

TIdHTTP支持SSL,与TIdTCPClient一样 - 通过在连接之前分配启用SSL的IOHandler。你真的应该使用TIdHTTP组件来处理HTTP流量。这就是为什么它首先存在。 – 2009-08-18 20:06:49

+1

另外,在当前的Indy 10版本中,SSLOptions.Method属性的默认值不是sslvSSLv2,而是sslvTLSv1。不过,将方法设置为sslvSSLv23会清除一些错误确实有意义。当Method被设置为除sslvSSLv23以外的任何其他方时,* *必须*在其最后使用该特定的SSL版本,否则SSL握手将失败。另一方面,sslvSSLv23更像是一个全面通配符,它​​支持SSLv2到TLSv1的所有SSL版本,并允许双方协商兼容的SSL版本。 – 2009-08-18 20:09:45

1

由于要发送的HTTP请求,则应该使用TIdHTTP部件代替TIdTCPClient组件直接。 TIdHTTP为您管理的HTTP协议有很多细节,如果您继续直接使用TIdTCPClient,则必须手动处理。

如果您要直接继续使用TIdTCPClient,那么您至少应该停止使用TIdIOHandler.AllData()方法。提取'Content-Length'应答头(可以将头部捕获()成TStringList或TIdHeaderList,然后使用它的Values []属性),然后将实际报告的字节数传递给TIdIOHandler.ReadString()或TIdIOHandler.ReadStream ()。这将有助于确保读取I/O不会因答复结束时服务器断开而过早停止读取。

+0

非常感谢您的意见。我最初使用TIdHTTP组件,但发现我需要TIdTcpClient组件提供的附加控制。 我会尝试捕获方法,看看是否可以解决问题,但我怀疑,因为我的所有其他请求处理正确。在我可以尝试你的建议之前将会有一天左右的时间。 再次,谢谢。我试过 – 2009-08-14 22:25:18

+0

我试过Capture,但似乎没有提供解决方案当我调用捕获时,它会捕获,直到服务器关闭连接。届时设置字节数太迟了。 AllData在这种情况下工作正常。实际上,当我使用ReadStream并将AReadUntilDisconnect参数设置为True时,我得到的响应与我使用AllData时相同。 我打算与服务的创建者交谈,看看问题是否可以解决。 – 2009-08-16 20:44:20

+1

你需要什么样的额外控制?至于Capture(),它将继续捕获直到断开连接的唯一方式是如果您使用错误的参数值调用它。默认终止符是一个包含单个'。'的行。字符就可以了。另一方面,HTTP头则由空行终止。所以你需要传递一个空白字符串给Capture()的ADelim参数。 – 2009-08-18 20:04:17