Delphi, отправляйте сообщения через WinInet и отслеживайте прогресс загрузки

Связано с Как отправить HTTP POST Запрос в Delphi с использованием WinInet api:

Как я могу отправить запрос на публикацию и отслеживать прогресс?

Это не работает (проверьте комментарии):

procedure StatusCallback(
  hInet:        HINTERNET;
  Context:      DWORD_PTR;
  Status:       DWORD;
  pInformation: Pointer;
  InfoLength:   DWORD ); stdcall;
var
  s: string;
  c: Cardinal;
begin
  case Status of
    INTERNET_STATUS_CLOSING_CONNECTION: s := 'Closing the connection to the server';
    INTERNET_STATUS_CONNECTED_TO_SERVER: s := 'Successfully connected to the socket address: ';
    INTERNET_STATUS_CONNECTING_TO_SERVER: s := 'Connecting to the socket address';
    INTERNET_STATUS_CONNECTION_CLOSED: s := 'Successfully closed the connection to the server';
    INTERNET_STATUS_CTL_RESPONSE_RECEIVED: s := 'Not implemented';
    INTERNET_STATUS_HANDLE_CLOSING: s := 'This handle value has been terminated';
    INTERNET_STATUS_HANDLE_CREATED: s := 'InternetConnect has created the new handle';
    INTERNET_STATUS_INTERMEDIATE_RESPONSE: s :=
      'Received an intermediate (100 level) status code message from the server';
    INTERNET_STATUS_NAME_RESOLVED: s := 'Successfully found the IP address: ' + PAnsiChar(pInformation);
    INTERNET_STATUS_PREFETCH: s := 'Not implemented';
    INTERNET_STATUS_RECEIVING_RESPONSE: s := 'Waiting for the server to respond to a request ';
    INTERNET_STATUS_REDIRECT: s := 'HTTP request is about to automatically redirect the request ' +
      PAnsiChar(pInformation);
    INTERNET_STATUS_REQUEST_COMPLETE: s := 'An asynchronous operation has been completed';
    INTERNET_STATUS_REQUEST_SENT: s := 'Successfully sent the information request to the server: ' +
      IntToStr(NativeUInt(pInformation)) + ' Byte';
    INTERNET_STATUS_RESOLVING_NAME: s := 'Looking up the IP address: ' + PAnsiChar(pInformation);
    INTERNET_STATUS_RESPONSE_RECEIVED: s := 'Successfully received a response from the server: ' +
      IntToStr(NativeUInt(pInformation)) + ' Byte';
    INTERNET_STATUS_SENDING_REQUEST: s := 'Sending the information request to the server.';
    INTERNET_STATUS_STATE_CHANGE:
      begin
        s := 'Moved between a secure (HTTPS) and a nonsecure (HTTP) site.';
        case DWORD(pInformation) of
          INTERNET_STATE_CONNECTED: s := s + #13#10 + 'Connected state. Mutually exclusive with disconnected state.';
          INTERNET_STATE_DISCONNECTED: s := s + #13#10 +
            'Disconnected state. No network connection could be established.';
          INTERNET_STATE_DISCONNECTED_BY_USER: s := s + #13#10 + 'Disconnected by user request.';
          INTERNET_STATE_IDLE: s := s + #13#10 + 'No network requests are being made by Windows Internet.';
          INTERNET_STATE_BUSY: s := s + #13#10 + 'Network requests are being made by Windows Internet.';
        end;
      end;
  end;
  tss.Add(s);
end;

function Https_Post(var callSettings: httpCallSettings; xServer,xRes: string): Integer;
const
  BufferSize=1024*64;
var
  hInet    : HINTERNET;
  hConnect : HINTERNET;
  hRequest : HINTERNET;
  dwc: UInt64;
  ErrorCode : Integer;
  lpdwBufferLength: DWORD;
  lpdwReserved    : DWORD;
  dwBytesRead     : DWORD;
  lpdwNumberOfBytesAvailable: DWORD;
  heads: ansistring;
  header: TStringStream;
begin
tss := tstringlist.Create;
  Result   :=0;
  callSettings.Response :='';
  hInet    := InternetOpen(PChar(callSettings.uAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

  if hInet=nil then
  begin
    ErrorCode:=GetLastError;
    raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
  end;

  try
    hConnect := InternetConnect(hInet, PChar(xServer), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, dwc);
    if hConnect=nil then
    begin
      ErrorCode:=GetLastError;
      raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
    end;

    try
      hRequest := HttpOpenRequest(hConnect, 'POST', PChar(xRes), HTTP_VERSION, '', nil, INTERNET_FLAG_SECURE, dwc);
      if hRequest=nil then
      begin
        ErrorCode:=GetLastError;
        raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
      end;

      try

      Header := TStringStream.Create('');
      with Header do
        begin
          WriteString('Host: ' + xServer + sLineBreak);
          WriteString('User-Agent: '+ callSettings.uAgent + SLineBreak);
          WriteString('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'+SLineBreak);
          WriteString('Accept-Language: en-us,en;q=0.5' + SLineBreak);
          WriteString('Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'+SLineBreak);
          WriteString('Keep-Alive: 300'+ SLineBreak);
          if callSettings.ExtraHeader <> '' then WriteString(callSettings.ExtraHeader + SlineBreak);
          if callSettings.CType <> ''       then WriteString('Content-Type: ' + callSettings.cType + SlineBreak);
          WriteString('Connection: keep-alive'+ SlineBreak + SlineBreak);
        end;

        HttpAddRequestHeaders(hRequest, PChar(Header.DataString), Length(Header.DataString), HTTP_ADDREQ_FLAG_ADD);

        InternetSetStatusCallback( hRequest, @StatusCallback );

        //send the post request
        if not HTTPSendRequest(hRequest, nil, 0, @callSettings.postvars[1], Length(callSettings.postvars)) then
        begin
          ErrorCode:=GetLastError;
          raise Exception.Create(Format('HttpSendRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
        end;

          lpdwBufferLength:=SizeOf(Result);
          lpdwReserved    :=0;
          //get the response code
          if not HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER, @Result, lpdwBufferLength, lpdwReserved) then
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

         CallSettings.CallStatus := Result;
         //if the response code =200 then get the body
         if Result=200 then
          if InternetQueryDataAvailable(hRequest, lpdwNumberOfBytesAvailable, 0, 0) then
          begin
            SetLength(callSettings.response,lpdwNumberOfBytesAvailable);
            InternetReadFile(hRequest, @callSettings.response[1], lpdwNumberOfBytesAvailable, dwBytesRead);
          end
          else
          begin
            ErrorCode:=GetLastError;
            raise Exception.Create(Format('InternetQueryDataAvailable Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
          end;

      finally
        InternetCloseHandle(hRequest);
      end;
    finally
      InternetCloseHandle(hConnect);
    end;
  finally
    InternetCloseHandle(hInet);
  end;
  showmessage(tss.Text);
end;

log


person hikari    schedule 13.04.2016    source источник


Ответы (1)


Используйте InternetSetStatusCallback(), чтобы зарегистрировать функцию обратного вызова в сеансе HTTP. для получения информации о статусе на разных этапах обработки запроса.

Обратите внимание на следующее предупреждение в документации:

Примечание. Функция обратного вызова, указанная в параметре lpfnInternetCallback, не будет вызываться при асинхронных операциях для дескриптора запроса, если для параметра dwContext HttpOpenRequest установлено значение ноль (INTERNET_NO_CALLBACK) или дескриптор соединения, когда дескриптор dwContext для InternetConnect установлен в ноль (INTERNET_NO_CALLBACK).

Попробуйте что-нибудь еще вроде этого:

function SockAddrToString(pAddr: LPSOCKADDR; AddrSize: DWORD): String;
var
  Buf: array[0..40] of Char;
  Len: DWORD;
begin
  Result := '';
  Len := Length(Buf);
  if WSAAddressToString(pAddr, AddrSize, nil, Buf, Len) = 0 then
    SetString(Result, Buf, Len-1);
end;

procedure StatusCallback(
  hInet:        HINTERNET;
  Context:      DWORD_PTR;
  Status:       DWORD;
  pInformation: Pointer;
  InfoLength:   DWORD ); stdcall;
var
  s: string;
begin
  case Status of
    INTERNET_STATUS_CLOSING_CONNECTION: s := 'Closing the connection to the server';
    INTERNET_STATUS_CONNECTED_TO_SERVER: s := 'Successfully connected to the socket address: ' + SockAddrToString(PSOCKADDR(pInformation), InfoLength);
    INTERNET_STATUS_CONNECTING_TO_SERVER: s := 'Connecting to the socket address: ' + SockAddrToString(PSOCKADDR(pInformation), InfoLength);
    INTERNET_STATUS_CONNECTION_CLOSED: s := 'Successfully closed the connection to the server';
    INTERNET_STATUS_CTL_RESPONSE_RECEIVED: s := 'Not implemented';
    INTERNET_STATUS_HANDLE_CLOSING: s := 'This handle value has been terminated';
    INTERNET_STATUS_HANDLE_CREATED: s := 'InternetConnect has created the new handle';
    INTERNET_STATUS_INTERMEDIATE_RESPONSE: s := 'Received an intermediate (100 level) status code message from the server';
    INTERNET_STATUS_NAME_RESOLVED: s := 'Successfully found the IP address: ' + PAnsiChar(pInformation);
    INTERNET_STATUS_PREFETCH: s := 'Not implemented';
    INTERNET_STATUS_RECEIVING_RESPONSE: s := 'Waiting for the server to respond to a request';
    INTERNET_STATUS_REDIRECT: s := 'HTTP request is about to automatically redirect the request ' +
      PAnsiChar(pInformation);
    INTERNET_STATUS_REQUEST_COMPLETE: s := 'An asynchronous operation has been completed';
    INTERNET_STATUS_REQUEST_SENT: s := 'Successfully sent the information request to the server: ' + IntToStr(PDWORD(pInformation)^) + ' Bytes';
    INTERNET_STATUS_RESOLVING_NAME: s := 'Looking up the IP address: ' + PAnsiChar(pInformation);
    INTERNET_STATUS_RESPONSE_RECEIVED: s := 'Successfully received a response from the server';
    INTERNET_STATUS_SENDING_REQUEST: s := 'Sending the information request to the server';
    INTERNET_STATUS_STATE_CHANGE:
      begin
        s := 'Moved between a secure (HTTPS) and a nonsecure (HTTP) site.';
        case PDWORD(pInformation)^ of
          INTERNET_STATE_CONNECTED: s := s + #13#10 + 'Connected state. Mutually exclusive with disconnected state.';
          INTERNET_STATE_DISCONNECTED: s := s + #13#10 + 'Disconnected state. No network connection could be established.';
          INTERNET_STATE_DISCONNECTED_BY_USER: s := s + #13#10 + 'Disconnected by user request.';
          INTERNET_STATE_IDLE: s := s + #13#10 + 'No network requests are being made by Windows Internet.';
          INTERNET_STATE_BUSY: s := s + #13#10 + 'Network requests are being made by Windows Internet.';
        end;
      end;
  end;
  tss.Add(s);
end;

procedure WinInetCheck(Success: Boolean; Function: PChar);
var
  ErrorCode : Integer;
begin
  if not Success then
  begin
    ErrorCode := GetLastError;
    raise Exception.CreateFmt('%s Error %d: %s', [Function, ErrorCode, GetWinInetError(ErrorCode)]);
  end;
end;

function Https_Post(var callSettings: httpCallSettings; xServer, xRes: string): Integer;
const
  BufferSize = 1024*64;
  AcceptTypes: array[0..] of PChar = ('text/html', 'application/xhtml+xml', 'application/xml;q=0.9', '*/*;q=0.8', nil);
var
  hInet    : HINTERNET;
  hConnect : HINTERNET;
  hRequest : HINTERNET;
  dwBufferLength: DWORD;
  dwReserved    : DWORD;
  dwBytesRead     : DWORD;
  dwNumberOfBytesAvailable: DWORD;
  Header: TStringStream;
  sHeader: String;
begin
  Result := 0;
  tss := TStringList.Create;
  try
    callSettings.Response := '';
    hInet := InternetOpen(PChar(callSettings.uAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
    WinInetCheck(hInet <> nil, 'InternetOpen');
    try
      hConnect := InternetConnect(hInet, PChar(xServer), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 1);
      WinInetCheck(hConnect <> nil, 'InternetConnect');
      try
        hRequest := HttpOpenRequest(hConnect, 'POST', PChar(xRes), HTTP_VERSION, '', @AcceptTypes, INTERNET_FLAG_SECURE or INTERNET_FLAG_KEEP_CONNECTION, 1);
        WinInetCheck(hRequest <> nil, 'HttpOpenRequest');
        try    
          Header := TStringStream.Create('');
          try
            Header.WriteString('Accept-Language: en-us,en;q=0.5' + #13#10);
            Header.WriteString('Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7' + #13#10);
            Header.WriteString('Keep-Alive: 300' + #13#10);
            if callSettings.ExtraHeader <> '' then
              Header.WriteString(callSettings.ExtraHeader + #13#10);
            if callSettings.CType <> '' then
              Header.WriteString('Content-Type: ' + callSettings.cType + #13#10);
            sHeader := Header.DataString;
            WinInetCheck(HttpAddRequestHeaders(hRequest, PChar(sHeader), Length(sHeader), HTTP_ADDREQ_FLAG_ADD), 'HttpAddRequestHeaders');
          finally
            Header.Free;
          end;

          InternetSetStatusCallback(hRequest, @StatusCallback);

          //send the post request
          WinInetCheck(HTTPSendRequest(hRequest, nil, 0, @callSettings.postvars[1], Length(callSettings.postvars)), 'HttpSendRequest');

          //get the response code
          dwBufferLength := SizeOf(Result);
          dwReserved := 0;
          WinInetCheck(HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER, @Result, dwBufferLength, dwReserved), 'HttpQueryInfo');    
          CallSettings.CallStatus := Result;

          //if the response code =200 then get the body
          if Result = 200 then
          begin
            WinInetCheck(InternetQueryDataAvailable(hRequest, dwNumberOfBytesAvailable, 0, 0), 'InternetQueryDataAvailable');
            SetLength(callSettings.response, dwNumberOfBytesAvailable);
            if dwNumberOfBytesAvailable <> 0 then
              WinInetCheck(InternetReadFile(hRequest, @callSettings.response[1], dwNumberOfBytesAvailable, dwBytesRead), 'InternetReadFile');
          end;
        finally
          InternetCloseHandle(hRequest);
        end;
      finally
        InternetCloseHandle(hConnect);
      end;
    finally
      InternetCloseHandle(hInet);
    end;
    ShowMessage(tss.Text);
  finally
    tss.Free;
  end;
end;
person Remy Lebeau    schedule 13.04.2016
comment
Не могу заставить его работать, моя процедура обратного вызова никогда не вызывается, могу я получить небольшой пример кода? Я вызываю InternetSetStatusCallback сразу после InternetConnect с дескриптором, который я использовал для этого. - person hikari; 13.04.2016
comment
@hikari обратите внимание на документацию: Функция обратного вызова может быть установлена ​​для любого дескриптора и наследуется производными дескрипторами ... Вы должны изменить функцию обратного вызова на каждом уровне. Это означает, что обратный вызов может быть установлен для дескриптор InternetConnect(), который отличается от набора обратного вызова для дескриптора HttpOpenRequest(). Вы устанавливаете обратный вызов для дескриптора InternetConnect(), попробуйте вместо этого дескриптор HttpOpenRequest(). И убедитесь, что параметр dwContext не установлен на 0 при открытии ручки. Если вам по-прежнему не удается заставить его работать, отредактируйте свой вопрос, чтобы отобразить новый код. - person Remy Lebeau; 13.04.2016
comment
Добавлен небольшой код, он в основном тот же, что и в другом сообщении, связанном с моим вопросом, поэтому просто разместил соответствующую часть. - person hikari; 13.04.2016
comment
@hikari другой пост устанавливает dwContext на 0, вы все еще делаете то же самое? - person Remy Lebeau; 13.04.2016
comment
О да, я был. Не уверен, что все еще делаю все правильно; mycallback запущен сейчас, мне нужно искать INTERNET_STATUS_SENDING_REQUEST? Кажется, что ни один из параметров обратного вызова не указывает на размер отправленных данных. - person hikari; 13.04.2016
comment
Когда dwInternetStatus равно INTERNET_STATUS_REQUEST_SENT, параметр lpvStatusInformation указывает на значение DWORD, которое содержит количество отправленных байтов. - person Remy Lebeau; 13.04.2016
comment
Я видел это, но получаю неверные данные. Это журнал всех вызовов моего обратного вызова: i.imgur.com/aujCzdl.png Я добавлю полный код к своему первоначальному вопросу. Этот журнал предназначен для отправленного файла размером 100 КБ. - person hikari; 13.04.2016
comment
В вашем INTERNET_STATUS_REQUEST_SENT случае NativeUInt(pInformation) неверно, вместо этого должно быть PDWORD(pInformation)^. То же самое с INTERNET_STATUS_STATE_CHANGE, он должен использовать PDWORD(pInformation)^. И INTERNET_STATUS_RESPONSE_RECEIVED не предоставляет счетчика байтов, но вы пытаетесь получить к нему доступ. - person Remy Lebeau; 13.04.2016
comment
Позвольте нам продолжить это обсуждение в чате. - person Remy Lebeau; 13.04.2016