Конференция "Сети" » NTLM-аутентификация - через что лучше и как? [D7]
 
  • GRAND © (07.09.10 10:04) [0]
    Сколько лет, сколько зим - здравствуйте, уважаемые!

    Вот проблемка, блин, всего-то выкачать страничку из инета для дальнейшей обработки - ан никак! :( В сети NTLM-аутентификация, Indy для этой цели не прокатила, а в synapse я такой возможности вообще не нашел. Я плохо искал или можно поюзать что-нибудь более прогрессивное?
  • Anatoly Podgoretsky © (07.09.10 20:58) [1]
    > GRAND  (07.09.2010 10:04:00)  [0]

    ICS?
  • GRAND © (08.09.10 15:46) [2]
    Да, вспомнил о существовании такой штуки, скачал, попробовал. Аутентификация проходит, все вроде бы нормально, но почему-то RcvdStream после Get возвращается абсолютно пустой. В хедере имеем 200 OK. Странно... Приведу кусок кода на всякий случай:

     HttpCli1.URL:='http://delphimaster.ru';
     HttpCli1.RcvdStream:=TStringStream.Create('');
     HttpCli1.Get;
     Memo1.Lines.LoadFromStream(HttpCli1.RcvdStream)

  • Anatoly Podgoretsky © (08.09.10 18:34) [3]
    > GRAND  (08.09.2010 15:46:02)  [2]

    А позицию потока Пушкин будет менять?
  • GRAND © (09.09.10 09:46) [4]
    А куды ж ее менять-то в ПУСТОМ потоке? Да и зачем?
  • GRAND © (09.09.10 11:36) [5]
    HttpCli1.RcvdStream.Seek(0,0) таки помогло, да :)
  • Дмитрий Тимохов (10.09.10 17:01) [6]
    Не надо ни Indy, ни ICS. Я долго бился и с тем и другим на предмет работы в различных конфигурация прокси (уже и деталей не помню) и всяких аутентификациях на них.

    В итоге остановился на WinINet (http://msdn.microsoft.com/en-us/library/aa385331%28VS.85%29.aspx).

    У меня эта штука ходит через все прокси, на которых я тестировал и обрабатывает всякую аутентификацию.
  • Дмитрий Тимохов (10.09.10 17:04) [7]
    У меня что-то типа такого (не судите строго):



    unit HTTPClientImpl;

    {$Q-}
    {$R-}

    interface

    uses
      Classes, Windows, SysUtils, WinINet;

    type
      THTTPClientImpl = class sealed(TObject)

         // == <<public>>

         // Синхронно выполняет запрос.
         public procedure Request(
            const aServerTimeout: Integer; // в секундах
            const aServerAddress: String;
            const aServerPort: Integer;
            const aServerPath: String;
            const aServerUserName: String;
            const aServerPassword: String;
            const aAccessType: Cardinal;
            const aProxyServerAddress: String;
            const aProxyServerPort: Integer;
            const aProxyServerUserName: String;
            const aProxyServerPassword: String;
            const aRequestData: TMemoryStream;
            const aReplyData: TMemoryStream);

         // == <<private>>

         strict private class procedure fRaiseLastWinINetError(
            const aDetails: String = ''); static;
         strict private class procedure fAddHttpHeader(
            const aRequest: HINTERNET; const aValue: String); static;
         strict private const CLRF = #13#10;
      end;

    implementation

    procedure THTTPClientImpl.Request;
    label
      ResendRequest;
    const
      cAgent = 'BC_HTTP_CLIENT_V1.1';
    var
      kSession, kConnect, kRequest: HINTERNET;
      kBytesRead: Cardinal;
      kReplyStatusCode, kReplyStatusCodeBufferSize,
      kReplyContentLength, kReplyContentLengthBufferSize,
      kHTTPQueryInfoIndex, kBytesAvailable: DWORD;
      kReplyStatusMessage: String;
      kReplyData: TMemoryStream;
      kReplyBuff: packed array of Byte;
      kServerTimeout: DWord;
      kServerPath: String;
      kProxyServerAuthIsSet, kServerAuthIsSet: Boolean;
    begin
      kHTTPQueryInfoIndex := 0;

      kSession := nil;
      kConnect := nil;
      kRequest := nil;
      kReplyData := nil;
      try

         case aAccessType of

            INTERNET_OPEN_TYPE_DIRECT:
            begin
               kSession := InternetOpen(
                  cAgent,
                  INTERNET_OPEN_TYPE_DIRECT,
                  '',
                  '',
                  0);
               if kSession = nil then
                  fRaiseLastWinINetError('InternetOpen');
            end;

            INTERNET_OPEN_TYPE_PROXY:
            begin
               kSession := InternetOpen(
                  cAgent,
                  INTERNET_OPEN_TYPE_PROXY,
                  PChar(aProxyServerAddress + ':' + IntToStr(aProxyServerPort)),
                  '',
                  0);
               if kSession = nil then
                  fRaiseLastWinINetError('InternetOpen');
            end;

            INTERNET_OPEN_TYPE_PRECONFIG:
            begin
               kSession := InternetOpen(
                  cAgent,
                  INTERNET_OPEN_TYPE_PRECONFIG,
                  '',
                  '',
                  0);
               if kSession = nil then
                  fRaiseLastWinINetError('InternetOpen');
            end;

            else
               Assert(False);
         end;

         // Установка параметров сессии
         kServerTimeout := aServerTimeout * 1000;

         if not InternetSetOption(kSession, INTERNET_OPTION_CONNECT_TIMEOUT,
            @kServerTimeout, SizeOf(kServerTimeout))
         then
            fRaiseLastWinINetError('InternetSetOption');

         if not InternetSetOption(kSession, INTERNET_OPTION_SEND_TIMEOUT,
            @kServerTimeout, SizeOf(kServerTimeout))
         then
            fRaiseLastWinINetError('InternetSetOption');

         if not InternetSetOption(kSession, INTERNET_OPTION_RECEIVE_TIMEOUT,
            @kServerTimeout, SizeOf(kServerTimeout))
         then
            fRaiseLastWinINetError('InternetSetOption');

         // Попытка соединения с сервером
         kConnect := InternetConnect(
            kSession,
            PChar(Trim(aServerAddress)),
            aServerPort,
            '',
            '',
            INTERNET_SERVICE_HTTP,
            0,
            0);
         if kConnect = nil then
            fRaiseLastWinINetError('InternetConnect');

         // Подготавливаем запрос
         kServerPath := Trim(aServerPath);
         if (Length(kServerPath) > 0) and (kServerPath[1] <> '/') then
            kServerPath := '/' + kServerPath;
         // Добавляем параметр, чтобы заведомо предотвратить кеширование
         kServerPath := kServerPath + '?param=' + IntToStr(Random(MaxInt));
         kRequest := HttpOpenRequest(
            kConnect,
            'POST',
            PChar(kServerPath),
            '',
            '',
            nil,
            INTERNET_FLAG_RELOAD or INTERNET_FLAG_NO_CACHE_WRITE or INTERNET_FLAG_KEEP_CONNECTION,
            0);
         if kRequest = nil then
            fRaiseLastWinINetError('HttpOpenRequest');

         // Добавление заголовков
         fAddHttpHeader(kRequest, 'Pragma: no-cache');
         fAddHttpHeader(kRequest, 'Accept: */*');
         fAddHttpHeader(kRequest, 'Content-Type: application/x-www-form-urlencoded');
         fAddHttpHeader(kRequest, 'Content-Length: ' + IntToStr(aRequestData.Size));
         fAddHttpHeader(kRequest, 'Connection: Keep-Alive');
         // Cтрока важна для работы через прокси.
         // Без этой строки в режиме INTERNET_OPEN_TYPE_PROXY происходит подвисание,
         // причем вроде как со стороны клиента.
         // Видимо причина в том, что рвется соединение.
         fAddHttpHeader(kRequest, 'Proxy-Connection: Keep-Alive');

         // Отправляем запрос

         kServerAuthIsSet := False;
         kProxyServerAuthIsSet := False;

         ResendRequest:

         if not HttpSendRequest(kRequest, nil, 0, aRequestData.Memory, aRequestData.Size) then
            fRaiseLastWinINetError('HttpSendRequest');

         // Получаем status code
         kReplyStatusCodeBufferSize := 4;
         if not HttpQueryInfo(kRequest, HTTP_QUERY_STATUS_CODE or HTTP_QUERY_FLAG_NUMBER,
            @kReplyStatusCode, kReplyStatusCodeBufferSize, kHTTPQueryInfoIndex)
         then
            fRaiseLastWinINetError('HttpQueryInfo');

         // Обработаем известные ошибки сразу
         if kReplyStatusCode = 407 then
         begin
            if kProxyServerAuthIsSet or (aProxyServerUserName = '') then
            begin
               raise Exception.Create(
                  'Для доступа к прокси-серверу необходимо задать '+
                  'имя пользователя и пароль!');
            end
            else
            begin
               InternetSetOption(
                  kRequest,
                  INTERNET_OPTION_PROXY_USERNAME,
                  PChar(aProxyServerUserName),
                  Length(aProxyServerUserName)+1);

               InternetSetOption(
                  kRequest,
                  INTERNET_OPTION_PROXY_PASSWORD,
                  PChar(aProxyServerPassword),
                  Length(aProxyServerPassword)+1);

               kProxyServerAuthIsSet := True;

               goto ResendRequest;
            end;
         end;

    to be continued

  • Дмитрий Тимохов (10.09.10 17:04) [8]

         if kReplyStatusCode = 401 then
         begin
            if kServerAuthIsSet or (aServerUserName = '') then
            begin
               raise Exception.Create(
                  'Для доступа к серверу необходимо задать '+
                  'имя пользователя и пароль!');
            end
            else
            begin
               InternetSetOption(
                  kRequest,
                  INTERNET_OPTION_USERNAME,
                  PChar(aServerUserName),
                  Length(aServerUserName)+1);

               InternetSetOption(
                  kRequest,
                  INTERNET_OPTION_PASSWORD,
                  PChar(aServerPassword),
                  Length(aServerPassword)+1);

               kServerAuthIsSet := True;

               goto ResendRequest;
            end;
         end;

         if kReplyStatusCode = 403 then
            raise Exception.Create(
               'Доступ к указанному пути на сервере запрещен!');

         if (kReplyStatusCode = 404) or
            (kReplyStatusCode = 405)
         then
            raise Exception.Create(
               'Неверно задан путь!');

         if kReplyStatusCode = 502 then
            raise Exception.Create(
               'Неверно заданы параметры прокси-сервера!');

         // Временный поток для ответа. Потом либо получаем из него детали ошибки,
         // либо (если нет ошибки) преобразуем его к aReplyData.
         kReplyData := TMemoryStream.Create();

         // Получаем длину данных
         kReplyContentLengthBufferSize := 4;
         kHTTPQueryInfoIndex := 0;
         if HttpQueryInfo(kRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER,
            @kReplyContentLength, kReplyContentLengthBufferSize, kHTTPQueryInfoIndex) then
         begin
            while kReplyData.Size < kReplyContentLength do
            begin
               if not InternetQueryDataAvailable(kRequest, kBytesAvailable, 0, 0) then
                  fRaiseLastWinINetError('InternetQueryDataAvailable');

               SetLength(kReplyBuff, kBytesAvailable);

               if not InternetReadFile(kRequest, @kReplyBuff[0], kBytesAvailable, kBytesRead) then
                  fRaiseLastWinINetError('InternetReadFile');

               kReplyData.Write(kReplyBuff[0], kBytesRead);
            end;
         end
         else
         begin
            // Я плохо разбираюсь в HTTP, но я понял, что вообще говоря
            // Content-Length может часто не возвращаться. В этом случае
            // WinInet сам как-то определит, длину сообщения. Насколько я понимаю
            // это может быть либо по разрыву соедения, либо по специальному
            // кодированию тела с помощью заголовка Transfer-Endoding.

            if GetLastError <> ERROR_HTTP_HEADER_NOT_FOUND then
               fRaiseLastWinINetError('HttpQueryInfo.2');

            // Считаем, что прочтется
            SetLength(kReplyBuff, 8*1024);
            while InternetReadFile(kRequest, @kReplyBuff[0],
               Length(kReplyBuff), kBytesRead) do
            begin
               if kBytesRead > 0 then
                  kReplyData.Write(kReplyBuff[0], kBytesRead)
               else
                 Break;
            end;
         end;

         // В зависимости от статуса считаем - либо ошибкой HTTP, либо нет.
         if kReplyStatusCode = 200 then
         begin
            aReplyData.Size := 0;
            aReplyData.CopyFrom(kReplyData, 0{т.е. все копируем});
            aReplyData.Position := 0; // Так вроде где-то в контракте обещал делать
         end
         else
         begin
            // Далее нужно решить, что показывать пользователю: если сообщение
            // не является HTML и не длинней заданной длины, то покажем его,
            // иначе покажем только строку c номером ошибки.

            SetLength(kReplyStatusMessage, kReplyData.Size);
            kReplyData.Position := 0;
            kReplyData.Read(Pointer(kReplyStatusMessage)^, kReplyData.Size);

            if (Length(kReplyStatusMessage) < 300) and
               (Pos('<', kReplyStatusMessage) = 0) and
               (Pos('>', kReplyStatusMessage) = 0) and
               (Pos('</', kReplyStatusMessage) = 0)
            then
               raise Exception.Create(Format('Неверный ответ сервера %d:'#13'%s!',
                  [kReplyStatusCode, kReplyStatusMessage]))
            else
               raise Exception.Create(Format('Неверный ответ сервера %d',
                  [kReplyStatusCode]));
         end;
      finally
         if kRequest <> nil then
            InternetCloseHandle(kRequest);
         if kConnect <> nil then
            InternetCloseHandle(kConnect);
         if kSession <> nil then
            InternetCloseHandle(kSession);
         if kReplyData <> nil then
            kReplyData.Free;
      end;
    end;

    class procedure THTTPClientImpl.fRaiseLastWinINetError(const aDetails: String = '');
    var
      kLastError: DWORD;
    begin
      kLastError := GetLastError();

      if (kLastError >= INTERNET_ERROR_BASE) and (kLastError <= INTERNET_ERROR_LAST) then
      begin
         case kLastError of
            ERROR_INTERNET_TIMEOUT:
               raise Exception.CreateFmt(
                  'Первышено время выполнения операции (12002, %s)', [aDetails]);
            ERROR_INTERNET_INVALID_URL:
               raise Exception.CreateFmt(
                  'Не корректный URL (12005, %s)', [aDetails]);
            ERROR_INTERNET_NAME_NOT_RESOLVED:
               raise Exception.CreateFmt(
                  'Сервер не найден по имени (12007, %s)', [aDetails]);
            ERROR_INTERNET_CANNOT_CONNECT:
               raise Exception.CreateFmt(
                  'Не могу соединиться с сервером (12029, %s)', [aDetails]);
            else
               RaiseLastOsError();
         end;
      end
      else
      begin
         RaiseLastOsError();
      end;
    end;

    class procedure THTTPClientImpl.fAddHttpHeader(
      const aRequest: HINTERNET; const aValue: String);
    var
      kValue: String;
    begin
      kValue := aValue + CLRF;
      if not HttpAddRequestHeaders(aRequest, PChar(kValue),
         Length(kValue), HTTP_ADDREQ_FLAG_REPLACE or HTTP_ADDREQ_FLAG_ADD)
      then
         fRaiseLastWinINetError('HttpAddRequestHeaders');
    end;

    end.


  • Дмитрий Тимохов (10.09.10 17:06) [9]
    Тут в общем, некая моя специфика, но идея, думаю, понятна.
 
Конференция "Сети" » NTLM-аутентификация - через что лучше и как? [D7]
Есть новые Нет новых   [134436   +25][b:0][p:0.011]