Delphi + Synapse:如何使用TTCPBlockSocket检查响应的大小

3

我终于使用Synapse库通过HTTPS让我的Delphi应用程序发送数据了。

但是,我无法确定返回给我的数据的大小。

目前,我的代码如下:

Socket     := TTCPBlockSocket.Create;
status     := TStringList.Create;  

status.Append('LineBuffer length: ' + IntToStr(Length(Socket.LineBuffer)));
status.Append('Waiting bytes: ' + IntToStr(Socket.WaitingDataEx));
status.Append('recvBufferStr: ' + CRLF + Socket.RecvBufferStr(240, 25000) );
status.Append('LastError = ' + Socket.LastErrorDesc);
status.Append('Error code: ' + IntToStr(Socket.LastError));
Memo1.Lines.AddStrings(status); 

我在Memo1中得到以下内容:

socket lastError = 0
LineBuffer length: 0
Waiting bytes: 0
recvBufferStr: 
HTTP/1.1 200 OK
Date: Thu, 22 Dec 2011 01:06:07 GMT
Server: Apache
Content-Length: 237
Connection: close
Content-Type: text/plain; charset=utf-8

[***The returned Data***]

如果Socket.RecvBufferStr的第一个参数(要接收的长度)太大,我会收到winsock错误10054,因为尽管服务器已经发送完数据,但我仍在等待数据。
如果它太短,几乎总是如此,那么我只会收到指定数量的数据。
等待字节和行缓冲区长度显示为0(不确定是否因为它们是长整数并且我正在使用IntToStr),所以我认为这不是检查数据量的方法。我也尝试使用CanRead和CanReadEx,但没有成功。
我想做类似下面的事情:Socket.RecvBufferStr([要接收的长度],[直到接收所有数据]或25000)。
如果有另一个函数可以使用,那也没关系,但我想坚持使用TTCPBlockSocket,因为我从synapse中尝试的HTTPSend和其他函数不能满足我的需求。 如何使用Delphi/Pascal中的Synapse库的TTCPBlockSocket套接字检查服务器返回给我多少数据?

1
虽然我很感激你在帖子末尾对问题进行了额外的强调,但如果能稍微减弱一下就更好了。也许只使用粗体而不是同时放大字体会更好? - Marjan Venema
更改已完成。 - SgtPooki
2个回答

2
请尝试以下代码。它展示了如何从响应头中获取Content-Length并读取内容本身。
请注意,HTTP协议并不是那么简单,但如果您要与自己的服务器或您知道其工作原理的服务器通信,可以将此作为灵感。在使用此代码时,请不要忘记包含OpenSSL二进制文件(版本0.9.8肯定与Synapse兼容)。
uses
  blcksock, ssl_openssl;

function HTTPGetText(const Host: string): AnsiString;
var
  ContentLength: Integer;
  HTTPHeader: AnsiString;      
  TCPSocket: TTCPBlockSocket;
begin
  Result := '';

  TCPSocket := TTCPBlockSocket.Create;
  try
    // initialize TTCPBlockSocket
    TCPSocket.ConvertLineEnd := True;
    TCPSocket.SizeRecvBuffer := c64k;
    TCPSocket.SizeSendBuffer := c64k;

    // connect to the host, port 443 is default for HTTP over SSL
    TCPSocket.Connect(Host, '443');
    // check for socket errors
    if TCPSocket.LastError <> 0 then
      Exit;

    // server name identification
    TCPSocket.SSL.SNIHost := Host;
    // initialize and connect over OpenSSL
    TCPSocket.SSLDoConnect;
    // server name identification
    TCPSocket.SSL.SNIHost := '';
    // check for socket errors
    if TCPSocket.LastError <> 0 then
      Exit;

    // build the HTTP request header
    HTTPHeader :=
      'GET / HTTP/1.0' + CRLF +
      'Host: ' + Host + ':443' + CRLF +
      'Keep-Alive: 300' + CRLF +
      'Connection: keep-alive' + CRLF +
      'User-Agent: Mozilla/4.0' + CRLF + CRLF;

    // send the HTTP request
    TCPSocket.SendString(HTTPHeader);
    // check for socket errors
    if TCPSocket.LastError <> 0 then
      Exit;

    // read the response status, here some servers might give you the content
    // note, that we are waiting in a loop until the timeout or another error
    // occurs; if we get some terminated line, we break the loop and continue
    // to check if that line is the HTTP status code
    repeat
      HTTPHeader := TCPSocket.RecvString(5000);
      if HTTPHeader <> '' then
        Break;
    until
      TCPSocket.LastError <> 0;

    // if the line we've received is the status code, like 'HTTP/1.1 200 OK'
    // we will set the default value for content length to be read
    if Pos('HTTP/', HTTPHeader) = 1 then
    begin
      ContentLength := -1;

      // read the response header lines and if we find the 'Content-Length' we
      // will store it for further content reading; note, that again, we are
      // waiting in a loop until the timeout or another error occurs; if the
      // empty string is received, it means we've read the whole response header
      repeat
        HTTPHeader := TCPSocket.RecvString(5000);
        if Pos('Content-Length:', HTTPHeader) = 1 then
          ContentLength := StrToIntDef(Trim(Copy(HTTPHeader, 16, MaxInt)), -1);
        if HTTPHeader = '' then
          Break;
      until
        TCPSocket.LastError <> 0;

      // and now let's get the content, we know it's size, so we just call the
      // mentioned RecvBufferStr function
      if ContentLength <> -1 then
        Result := TCPSocket.RecvBufferStr(ContentLength, 5000);
    end;

    TCPSocket.CloseSocket;

  finally
    TCPSocket.Free;
  end;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  Memo1.Text := HTTPGetText('www.meebo.com');
end;

1

在测试WaitingDataEx时,正如TLama所提到的,我决定在RecvBufferStr之后加入一行输出WaitingDataEx的代码。

IntToStr(WaitingDataEx)有效地显示了剩余的数据。

WaitingDataEx在特定的瞬间检查等待数据,因为从发送数据到查找数据并没有真正的暂停,所以它看到还没有数据可以接收。

我用以下代码解决了这个问题:

httpRsp := httpRsp + Socket.RecvBufferStr(1, 25000);
httpRsp := httpRsp + Socket.RecvBufferStr(Socket.WaitingDataEx, 100); 
  1. 此代码等待接收1个字节的数据,然后将该数据添加到字符串变量httpRsp中。
  2. 然后使用Socket.WaitingDataEx读取其余的字节,因为第一个RecvBufferStr命令已经等待确保有字节可接收。

以上内容的编辑

上述代码在我与特定服务器通信时出现了问题,尽管我不知道原因。它只能从我正在通信的一个服务器接收标头而不是内容。我通过以下代码解决了这个问题:

httpRsp := httpRsp + Socket.RecvPacket(50000);
while Socket.WaitingDataEx > 0 do
  httpRsp := httpRsp + Socket.RecvPacket(50000); 

1
你需要使用TBlockSocket.RecvPacket循环读取数据,直到你得到的是TBlockSocket.LastError <> 0而不是RecvBufferStr。如果HTTP响应头中没有Content-Length(即下载对话框无法知道剩余下载量的情况),则需要这样做。如果响应头有Content-Length,则可以使用TBlockSocket.RecvBufferStr函数读取相应字节数。但是,你可以在THTTPSend的657行找到所有内容。 - TLama
我在看到你的评论之前就已经做了。五星好评。感谢解释。虽然我收到了Content-Length,但recvBufferStr仍然无法工作。如果您知道一个可靠的THTTPSend,它更像Curl(带有SSL)而不是我被迫使用的东西,那么HTTPSend会更好。否则,就不行了。 - SgtPooki
其中 TE_UNKNOWN 表示您不知道 Content-LengthTE_IDENTITY 表示您知道它,而 TE_CHUNKED 表示您在标头中有 Transfer-Encoding: chunked。顺便说一句,您正在尝试重新发明轮子,您可以使用 THTTPSend 发布不起作用的内容。当使用低级别的 TBlockSocket.RecvBuffer 函数进行循环时,WaitingDataEx 对您很有用。如果您投反对票,请留下评论说明为什么您投反对票。 - TLama
问题在于我甚至不知道Curl是什么 :) THTTPSend可以在没有任何问题的情况下使用SSL。例如,这一行代码可以从HTTPS网页获取内容:HttpGetText('https://www.google.com/adsense', Memo1.Lines); - TLama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接