Delphi + Synapse: как проверить размер ответа с помощью TTCPBlockSocket

Наконец-то я получил приложение Delphi для отправки данных с использованием прямых сокетов с библиотекой Synapse через HTTPS.

Однако у меня возникли проблемы с определением размера возвращаемых мне данных.

В настоящее время у меня есть следующий код:

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 (не уверен, что это потому, что они longInt, а я делаю IntToStr), поэтому я не думаю, что так я проверяю количество данных. И я также безрезультатно пытался использовать CanRead и CanReadEx.

Я хотел бы сделать что-то вроде следующего: Socket.RecvBufferStr ([длина для получения], [до получения всех данных] или 25000).

Если есть другая функция, это нормально, но я хотел бы придерживаться TTCPBlockSocket поскольку HTTPSend и другие, которые я пробовал из синапса, не работают для моих целей.

Как проверить — используя сокет TTCPBlockSocket из < библиотека href="http://synapse.ararat.cz/doku.php" rel="nofollow">Synapse с Delphi/Pascal — сколько данных мне возвращает сервер?


person SgtPooki    schedule 22.12.2011    source источник
comment
Хотя я ценю, что вы сделали дополнительный акцент на вопросе в конце своего поста, я не возражал бы, если бы он был немного смягчен. Возможно, используя только жирный шрифт, а не увеличивая его?   -  person Marjan Venema    schedule 22.12.2011
comment
Изменение было внесено   -  person SgtPooki    schedule 22.12.2011


Ответы (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;
person TLama    schedule 22.12.2011

При повторном тестировании WaitingDataEx, как упомянул TLama, я решил поместить строку, выводящую WaitingDataEx, после RecvBufferStr.

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); 
person SgtPooki    schedule 22.12.2011
comment
Вам придется читать данные в цикле, используя TBlockSocket.RecvPacket, пока вы не получите TBlockSocket.LastError <> 0 вместо RecvBufferStr в случае, если у вас нет Content-Length в заголовке ответа HTTP (вы знаете случаи, когда даже диалог загрузки не знает, сколько Осталось скачать). Если в заголовке ответа есть Content-Length, прочитайте это количество байтов, например. используя функцию TBlockSocket.RecvBufferStr. Но все, что вы можете найти в THTTPSend в строке 657. - person TLama; 23.12.2011
comment
Я сделал это до того, как увидел твой комментарий. 5 звезд. Спасибо за объяснение. Однако я получал Content-Length:, а recvBufferStr все еще не работал. Если вы знаете надежный THTTPSend, который больше похож на Curl (с SSL), чем на то, что я вынужден делать, то HTTPSend будет лучше. В противном случае это не так. - person SgtPooki; 23.12.2011
comment
Где TE_UNKNOWN означает, что вы не знаете, Content-Length, TE_IDENTITY что знаете, а TE_CHUNKED что у вас есть в заголовке Transfer-Encoding: chunked. Кстати, вы пытаетесь заново изобрести колесо, вы могли бы опубликовать то, что не работает для вас с THTTPSend. WaitingDataEx будет полезно для вас при использовании низкоуровневой функции TBlockSocket.RecvBuffer в цикле. И если вы голосуете, пожалуйста, оставьте комментарий, почему вы проголосовали против. - person TLama; 23.12.2011
comment
Проблема в том, что я даже не знаю, что такое Curl :) THTTPSend работает с SSL без проблем. Например, эта единственная строка получает содержимое с веб-страницы HTTPS HttpGetText('https://www.google.com/adsense', Memo1.Lines);. - person TLama; 23.12.2011