Пример авторизации на сайте с помощью idHTTP.Post

Несмотря на то, что все мои заготовки статей канули в Лету вместе с другими данными (в том числе исходниками), которые были на флешке, я не пала духом и попытаюсь написать что-нибудь полезное заново.

Сегодня расскажу, как использовать idHTTP.Post для авторизации на сайте. Я возьму для примера сайт LiveJournal.com.

Немного теории для начинающих. Итак, вызов метода Post компонента idHTTP отличается от вызова Get-а только тем, что помимо URL-а необходимо передать параметры. Параметры можно передавать в виде StringList-а, или каких-нибудь Stream-ов, или чего-нибудь еще подходящего.)

Пример Post-процедуры (параметры передаются в виде StringList-а):


procedure TForm1.Button1Click(Sender: TObject);  
var  
  LoginInfo: TStringList;  
  Response: TStringStream;  
begin  
  try  
    LoginInfo := TStringList.Create;  
    Response := TStringStream.Create('');  
    LoginInfo.Add('username=MyName');  
    LoginInfo.Add('password=MyPass');  
    IdHTTP1.Post('http://mywebsite.xxx/login.php',LoginInfo,Response);  
    Showmessage(Response.DataString);  
  finally  
    begin  
      Response.Free;  
      LoginInfo.Free;  
    end;  
  end;  
end;  

Пример Post-функции (параметры передаются в виде IdMultiPartFormDataStream-а):


uses IdMultipartFormData;  
{ .... }  
  
procedure TForm1.Button1Click(Sender: TObject);  
var  
  data: TIdMultiPartFormDataStream;  
begin  
  data := TIdMultiPartFormDataStream.Create;  
  try  
    // добавляем нужные параметры  
    data.AddFormField('param1', 'value1');  
    data.AddFormField('param2', 'value2');  
    // для примера выводим в мемо все, что вернулось  
    Memo1.Lines.Text := IdHTTP1.Post('http://localhost/script.php', data);  
  finally  
    data.Free;  
  end;  
end;  

Сейчас попробуем применить полученные знания. Идем на LiveJournal.com, включаем сниффер, логинимся на сайте и смотрим, какие параметры надо передавать ('mode=login', 'user=логин', 'password=пароль'). Авторизация не произойдет, если на стороне клиента не будут сохранены кукисы. Для сохранения кукисов среди компонентов Indy существует TidCookieManager. IdCookieManager подключается к idHTTP через свойство CookieManager.


idHttp.CookieManager := IdCookieManager;  

В этом случае при запросах в заголовок добавляются кукисы, автоматически сохраненные в IdCookieManager. IdCookieManager можно найти на закладке Indy Misc или создать динамически.



Поместим на форму 2 TEdit-а, TMemo и кнопку, на которую повесим следующий работающий код авторизации:


procedure TForm1.Button1Click(Sender: TObject);  
var  
  Http  : TidHttp;  
  CM    : TidCookieManager;  
  Data  : TStringList;  
  StrPage, UserID,  UserName  :  String;  
  i : integer;  
begin  
  try  
    Http := TIdHTTP.Create(Self);  
    Data := TStringList.Create;  
    CM := TidCookieManager.Create(Http);  
    Http.AllowCookies := true;  
    Http.CookieManager := CM;  
    Http.HandleRedirects := true;  
  
    Http.Request.Host:='livejournal.com';  
    Http.Request.UserAgent:='Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10';  
    Http.Request.Accept:='text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';  
    Http.Request.AcceptLanguage:='ru,en-us;q=0.7,en;q=0.3';  
    Http.Request.AcceptCharSet:='windows-1251,utf-8;q=0.7,*;q=0.7';  
    Http.Request.Referer:='http://www.livejournal.com/';  
  
    Data.Add('mode=login');  
    Data.Add('user=' + Edit1.Text);  
    Data.Add('password=' + Edit2.Text);  
    StrPage := Http.Post('http://www.livejournal.com/login.bml?ret=1', Data);  
  finally  
    Data.Free;  
    CM.Free;  
    Http.Free;  
  end;  
  
  if Pos('<input class="logoutlj_hidden" id="user" name="user" type="hidden" value="'+Edit1.Text,StrPage) <> 0 then  
    ShowMessage('Авторизация прошла успешно')  
  else  
    ShowMessage('Авторизация провалилась');  
  
  Memo1.Lines.Text := StrPage;  
end;  

Возвращенные заголовки (после ответа сервера) можно посмотреть так:


idHttp.Response.RawHeaders.GetText;  

Сохраненные в CookieManager-е кукисы можно посмотреть так:


for i := 0 to Http.CookieManager.CookieCollection.Count - 1 do  
  StrPage := StrPage + CM.CookieCollection.Items[i].CookieText + #13#10;  

Добавлено: 07 Августа 2018 21:34:31 Добавил: Андрей Ковальчук

Компоненты Internet Direct (Indy). Вводная статья для новичков

Если в двух словах, Indy — компоненты для удобной работы с популярными интернет-протоколами. Принцип их работы основывается на использовании сокетов в блокирующем режиме. Indy интересен и удобен тем, что достаточно сильно абстрагирован. И программирование в Indy сводится к линейному программированию. Кстати, в интернете широко распространена переводная статья, в которой есть слова "блокирующий режим не является дьяволом" :)) В свое время меня очень позабавил этот перевод. Статья — часть книги Хувера и Харири "Глубины Indy". В принципе, для работы с Indy вам вовсе не обязательно всю ее читать, но ознакомиться с принципами работы протоколов интернета я все-таки рекомендую. Что касается "дьявольского" режима. Вызов блокирующего сокета действительно не возвращает управления, пока не выполнит свою задачу. Когда вызовы делаются в главном потоке, интерфейс приложения может "подвиснуть". Чтобы позволить избежать этой неприятной ситуации, разработчики индей создали компонент TIdAntiFreeze. Достаточно просто кинуть его на форму — и пользовательский интерфейс будет преспокойно перерисовываться во время выполнения блокирующих вызовов.

Вы наверняка уже ознакомились с содержимым различных закладок "Indy (...)" в Delphi. Компонентов там немало, и каждый из них может быть полезным. Я сама работала далеко не со всеми, так как не вижу надобности их изучать без определенной задачи.

В базовый дистрибутив Delphi входят Indy v.9 с копейками. Наверное, желательно сразу сделать обновление до более новой версии (например, у меня сейчас 10.0.76, но есть и более поздние, вроде).

Есть клиентские и серверные компоненты для работы с одними и теми же протоколами. Indy реально упрощают разработку приложений, в отличие от варианта разработки того же самого функционала на сокетах. Например, чтобы установить связь с сервером, вы должны просто-напросто вызвать метод Connect. Успешное установление соединения ознаменуется возвратом из метода без возникновения эксепшена. Если же соединение неызовется установится — вызовется исключение.

"Академический" пример (код не рабочий, не запускайте :) ):


with IndyClient do   
begin  
  Host := 'test.com';  
  Port := 2000;  
  Connect;   
  Try  
    // работа с данными (чтение, запись...)  
  finally   
    Disconnect;   
  end;  
end;  

Host и port могут быть установлены в инспекторе объектов или в рантайме.

Для чего же можно использовать компоненты Indy в задачах парсинга? Применение разнообразно! Самое простое — получение содержимого страницы (с этим уже все, наверное, сталкивались) с использованием компонента IdHTTP:


var  
  rcvrdata: TMemoryStream;  
  idHttp1: TidHttp;  
begin  
  idHttp1 := TidHttp.Create(nil);  
  rcvrdata := TMemoryStream.Create;  
  idHttp1.Request.UserAgent := 'Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)';  
  idHttp1.Request.AcceptLanguage := 'ru';  
  idHttp1.Response.KeepAlive := true;  
  idHttp1.HandleRedirects := true;  
  try  
    idHttp1.Get(Edit1.Text, rcvrdata);  
  finally  
    idHttp1.Free;  
  end;  
  if rcvrdata.Size > 0 then begin  
    ShowMessage('Получено ' + inttostr(rcvrdata.Size));  
    rcvrdata.SaveToFile('c:\111.tmp');  
  end;  
  rcvrdata.Free;  
end;  

Как видно, можно не только получить содержимое страницы, но и имитировать загрузку документа с определенного клиента. Компоненты Indy вообще удобны для формирования заголовков и POST-запросов. Об этом с примерами в следующий раз.

Добавлено: 02 Августа 2018 08:04:11 Добавил: Андрей Ковальчук

Сохранить исходник HTML из TWebBrowser

Сегодня мы научимся сохранять исходник HTML из TWebBrowser.Document на диск. Делается это очень просто и я думаю трудностей с этим у вас возникнуть недолжно.

Ну что поехали, как всегда для начала открываем Delphi и первое что нам нужно будет сделать это дописать в раздел uses ActiveX. Двигаемся дальше теперь кидаем на форму две кнопочки (button) с закладки standart, компонент SaveDialog с закладки Dialog и компонент webBrowser с закладки internet. Так с компонентами вроде разобрались

Создаем обработчик событий(onclick) на первой кнопке и в нем прописываем следующий код:


webbrowser1.Navigate('http://www.delphiexpert.ru');  

Так теперь переходим на самый верх и сразу же после public пишем:


procedure SaveHTMLSourceToFile(const FileName: string; WB: TWebBrowser);  

Нажимаем комбинацию клавиш Ctrl+Shift+C

И Delphi автоматически генерирует процедуру, получиться должно примерно так:


procedure TForm1.SaveHTMLSourceToFile(const FileName: string;  
WB: TWebBrowser);  
begin  
  
end;  

Сейчас давайте пропишем действия, которые будет выполнять данная процедура, а именно она должна сохранять текущий открытый в компоненте webbrowser документ в виде исходника HTML


procedure TForm1.SaveHTMLSourceToFile(const FileName: string;  
WB: TWebBrowser);  
var  
PersistStream: IPersistStreamInit;  
FileStream: TFileStream;  
Stream: IStream;  
SaveResult: HRESULT;  
begin  
PersistStream := WB.Document as IPersistStreamInit;  
FileStream := TFileStream.Create(FileName, fmCreate);  
try  
Stream := TStreamAdapter.Create(FileStream, soReference) as IStream;  
SaveResult := PersistStream.Save(Stream, True);  
if FAILED(SaveResult) then  
MessageBox(Handle, 'Fail to save HTML source', 'Error', 0);  
finally  
FileStream.Free;  
end;  
end;  

Вот практически и все осталось только создать обработчик событий (onClick) на второй кнопке и прописать там:


if SaveDialog1.Execute then  
SaveHTMLSourceToFile(SaveDialog1.FileName, WebBrowser1);   

Запускаем программу, жмем сначала на первую кнопку, ждем пока в компоненте WebBrowser загрузиться страница.

Теперь нажимаем на вторую кнопку и сохраняем загруженную страницу, в Поле Имя файла: вводим например delphi.txt или expert.html

Добавлено: 01 Августа 2018 08:43:50 Добавил: Андрей Ковальчук

Скриншот страницы в компоненте WebBrpwser

Сегодня мы, будем делать скриншот активной web страницы в компоненте webbrowser.
Итак нам понадобятся следующие компоненты: WebBrowser с закладки Internet, 2 компонента Button с зкладки Standard.
Для начала после ключевого слова uses допишите ActiveX, jpeg

Далее нам нужно создать небольшую процедурку которая и будет делать скриншоты, поэтому после ключевого слова public пишем:


procedure WebBrowserScreenShot(const wb: TWebBrowser; const fileName: TFileName) ;  

Нажимаем комбинацию клавиш CTRL+SHIFT+C, Delphi создаст шаблон процедуры, а вот что должно получиться в итоге:


procedure TForm1.WebBrowserScreenShot(const wb: TWebBrowser;  
const fileName: TFileName);  
var  
viewObject : IViewObject;  
r : TRect;  
bitmap : TBitmap;  
begin  
if wb.Document <> nil then  
begin  
wb.Document.QueryInterface(IViewObject, viewObject) ;  
if Assigned(viewObject) then  
try  
bitmap := TBitmap.Create;  
try  
r := Rect(0, 0, wb.Width, wb.Height) ;  
bitmap.Height := wb.Height;  
bitmap.Width := wb.Width;  
viewObject.Draw(DVASPECT_CONTENT, 1, nil, nil, Application.Handle, bitmap.Canvas.Handle, @r, nil, nil, 0) ;  
with TJPEGImage.Create do  
try  
Assign(bitmap) ;  
SaveToFile(fileName) ;  
finally  
Free;  
end;  
finally  
bitmap.Free;  
end;  
finally  
viewObject._Release;  
end;  
end;  
end;   

*Посмотрите на этот код и на свой шаблон, допишите не достающие строки !
Двигаемся дальше, создаем обработчик событий OnClick на первой кнопке, в нем прописываем вот такую строку:


WebBrowser1.Navigate('http://delphiexpert.ru') ;   

Теперь создаем обработчик событий на второй кнопке, между begin ... end пропишите:


WebBrowserScreenShot(WebBrowser1,'c:\WebBrowserImage.jpg') ;   

Вот в принципе и все, запускаем проект, нажимаем сначала на первую кнопку - в WebBrouser'e открывается страница www.delphiexpert.ru. Теперь жмем на вторую кнопку и на диске C:\ создается файл с именем WebBroserImage.jpg в котором будет находиться скриншот сайта delphiexpert.ru

На этом всё.

Добавлено: 01 Августа 2018 08:42:10 Добавил: Андрей Ковальчук

Для начинающих работать с компонентом IdHTTP в Delphi

Найти компонент idHTTP можно на вкладке Indy Clients.

После того, как поместите его на форму, посмотрите его параметры и свойства в Object Inspector- е. Вы увидите, что работать с протоколом HTTP с помощью этого компонента достаточно просто. Для "составления" правильных заголовков запросов к серверу будем плотно работать с TIdHTTP.Request. Многие поля вам уже знакомы. Там же есть возможность привязки к объекту idHTTP компонента для удобной работы с кукисами (компонент idCookieManager) и настройка работы через прокси.

Рассмотрим способы отправки запросов Get и Post. С такими названиями существуют и функции, и процедуры. Непосредственно перед отправкой запроса следует настроить свойства вышеупомянутого IdHTTP.Request-а, если есть необходимость. Например, UserAgent в idHTTP по умолчанию Mozilla/3.0 (compatible; Indy Library), и, чтобы не палиться, следует его заменить на что-нибудь более безобидное :) А если вы создаете экземпляры компонента idHTTP динамически для парсинга страниц какого-нибудь большого сайта, то юзерагента можно вообще брать Рандомом из заранее подготовленного списка.

Пример использования процедуры idHTTP.Get без дополнительных настроек:


var   
  mStream: TMemoryStream;   
   
mStream := TMemoryStream.Create;   
try   idHttp := TIdHTTP.Create(nil);   
  {  тут  следует  "настроить " параметры  idHTTP }   
  { ... }   
  try   
    idHttp.Get(URL, mStream);   
  finally   
    idHttp.Free;   
  end;   
finally   
  mStream.Free;   
end;  

Или с помощью функции получить содержимое страницы в строковую переменную:


Str := idHttp.Get(URL); 

С POST-запросом дела обстоят аналогично, с одним отличием: при отправке POST-запроса передаются еще и параметры. Думаю, что более подробно рассмотрю POST-запрос в следующем посте, с примером авторизации на сайте (чтобы не пихать слишком много информации в одну запись ).

Какие еще аспекты работы с idHTTP следует отметить?
После посылки запроса проверить ответ сервера можно, посмотрев содержимое свойства


idHTTP.Response.ResponseText   

Стандартым ответом о том , что все прошло удачно , является HTTP/1.0 200 OK.

Бывают и другие ответы сервера (про коды ответов я упоминала в одной из предыдущих записей). Остановлю внимание только на двух наиболее часто встречающихся группах:
2хх: Успешно. Сигнал был успешно принят, понят и принят к обработке.
Зхх: Перемаршрутизация . Необходимо предпринять определенные действия , чтобы выполнить запрос.

Исключения, возникающие при работе компонентов Indy (тип EIdH TTPProtocolException),
классифицируются особым образом.


Except   
on E:EIdHTTPProtocolException do   
ShowMessage(E.ErrorMessage);  

Классификация исключений может пригодиться . У меня был случай , когда написанный парсер работал без проблем , но спустя какое -то время отказался работать . Помог анализ ответа сервера : на сайте временно поставили перенаправление (Код 302 Found:
HTTP_FOUND — Запрошенный ресурс был временно перемещен на новый URI), возможность которого я в начальной версии не предусмотрела. С тех пор во всех своих парсерах я проверяю код ответа: не 302 ли случаем? (Если код ответа 301 (Moved Permanently:
HTTP MOVED PERMANENTLY — Запрошенный документ был перенесен на новый URI), то информация берется без проблем, если у idHTTP свойство HandleRedirects := true, а вот с 302 это не прокатывает. Так же отдельно я обрабатываю код 404). Еще у компонента надо грамотно настроить все таймауты, чтобы, если обнаружится исключительная ситуация, запрос не "подвисал".

Пример кода для обработки исключений разного типа:


procedure TForm1.Button1Click(Sender: TObject);   
begin   
 Memo1.Lines.Clear;   
 try   
   Memo1.Lines.Add(IdHTTP1.Get(Edit1. Text));   
 except on e : EIDHttpProtocolExcepti on do   
   Begin      if e.ErrorCode = 302 then   
       begin   
         try   
           // получаем новый адрес - адрес перенаправления   
           Memo1.lines.add(idhttp1.Ge t(IdHTTP1.Response.Locati on));   
         except on e:Exception do   
    // предусматриваем, что исключение  может возникнуть и тут    
           ShowMessage(' Ошибка  при  получении  нового  адреса .'+e.Message);   
         end;   
       end   
     else   
       //http 404, 501  и так  далее    
       ShowMessage(' Ошибка  другого  вида , не  302:'+e.Message);   
   end;   
 on e:Exception do   
   ShowMessage('Ошибка: ' + e.Message);    
 end;   
end;  

Добавлено: 01 Августа 2018 07:26:12 Добавил: Андрей Ковальчук

Открываем почту и заполняем поля на автомате

В этом уроке я покажу вам, как открыть почтовую программу установленную в системе по умолчанию и на автомате заполнит необходимые поля для отправки сообщения (кому, копия, текст сообщения).

Ну что поехали... Я надеюсь что Delphi у вас уже открыт, поэтому сразу создаем новый проект и кидаем на форму один компонент Button с закладки Standard. Больше на форме у нас ничего не будет.

Теперь переходим в код и добавим в раздел uses модуль ShellApi. Дальше, создаем обработчик событий на кнопке, между begin и end прописываем вот такой код:


ShellExecute(GetDesktopWindow,'open',  
'mailto:Имя получателя <yourmail@mail.ru>'+  
'?Subject=Тема'+  
'&Body=Текст сообщения'+  
'&CC=Копия'+  
'&BCC= Скрытая копия',  
0,0,SW_SHOWNORMAL);  
</yourmail@mail.ru>  

Запускаем - тестим - улыбаемся )

Вот и всё, в моём The Bat код работает !

Добавлено: 31 Июля 2018 22:00:53 Добавил: Андрей Ковальчук

Обмен данными через COM-порт в Windows

Разработчики высокоуровневых языков программирования, очевидно, считают прием/передачу данных по протоколу rs232 через коммуникационный порт экзотической процедурой: мол, рядовому пользователю как бы и без надобности, а нерядовой - разберется самостоятельно. Потому ни в turbo pascal, ни в delphi нет штатных средств обмена данными таким способом. Однако в последнее время, особенно в связи с распространением микропроцессорных устройств, такая задача встает в любительских программах все чаще - в силу простоты и дешевизны реализации последовательным портом оборудованы многие научные и инженерные приборы, разнообразные датчики и измерители.

Простота аппаратного исполнения (для обычной двусторонней связи требуется всего три провода) асинхронного коммуникационного порта ведет, однако, к некоторому усложнению необходимого программного обеспечения. Можно, конечно, попробовать послать байт на устройство com1 средствами dos, подобно тому, как это делается для lpt1 (оно же prn), но успех вряд будет достигнут - как минимум, надо сначала настроить скорость обмена. Потому для dos-программ это делается средствами bios или прямым программированием порта "по железу". А в windows, к счастью, есть соответствующие функции api.

Для организации обмена нужно проделать следующие шаги:

получить дескриптор порта (handle - указатель, куда посылать все относящееся к порту);
получить адрес dcb - data control block;
установить новые параметры dcb;
послать установленные параметры в порт;
приступить к чтению принимаемых данных или к передаче.
Рассмотрим все по порядку. Получить дескриптор можно с помощью универсальной функции createfile. Так как СОМ - устройство последовательное, то его можно рассматривать как файл, что и делается (и в dos, между прочим, тоже). У этой функции множество применений (описание в help, если распечатать, занимает страниц семь). Для нее требуется масса входных параметров, но большинство из них нам не нужны и приравниваются, в зависимости от типа, либо к 0, либо к величине nil (указатели, которые никуда не указывают). В нашем случае получается следующий синтаксис функции:


createfile (stcom, generic_read+generic_write, 0, nil, open_existing, 0, 0); 

Здесь stcom - строка типа pchar, в которой записано имя файла, в данном случае - просто 'com1' или 'com2', смотря какой порт нужен. Параметр generic_read+generic_write означает, что порт открывается как для вывода, так и для ввода. open_existing означает проверку существования, и если объявленного порта нет, после вызова функции возникнет ошибка, которую можно проанализировать обычным методом delphi: try ... except.

Получить адрес dcb можно, если применить функцию getcommstate (pcom, pdcb); здесь pcom - дескриптор порта, pdcb - возвращаемый адрес структуры dcb. Установить параметры порта в этой структуре можно непосредственно, но если вы обратитесь к ее описанию в help, то бессонная ночь вам обеспечена. Поэтому проще сделать это с помощью вызова функции buildcommdcb (stcom, pdcb), в которой stcom в данном случае содержит набор устанавливаемых параметров в виде строки (см. пример ниже). Установленные параметры посылаются в порт с помощью функции setcommstate (pcom, pdcb). Все эти функции возвращают значение типа boolean, которое равно true, если операция прошла успешно. Чтение из порта и запись в порт осуществляются с помощью симметричных функций readfile и writefile, также универсальных и потому содержащих ненужный нам параметр, который мы приравняем к nil:


boolean readfile (pcom, xb, 1, xn, nil);  

Здесь xb - переменная любого целого типа, в которой возвращается прочитанное значение (для writefile в ней содержится, наоборот, записываемое), 1 - столько байт прочесть из xb (для СОМ-порта это, очевидно, всегда 1), xn - сколько байт действительно прочитано. Следующий программный фрагмент инициализирует порт СОМ1 и осуществляет прием данных до тех пор, пока не будет нажата любая клавиша на клавиатуре. Принимаемые данные преобразуются в строку и выводятся на экран через пробел в компоненте label1 (с переводом строки через каждые 32 значения; разумеется, можно выводить в любой компонент, имеющий свойство text или caption, или еще куда-нибудь):


stcom:='com1';  
try  
pcom:= createfile (stcom, generic_read+generic_write, 0, nil, open_existing, 0, 0);  
except ;  
if getcommstate(pcom,pdcb)  
then stcom:='com1: baud=9600 parity=n data=8 stop=1'  
else ;  
if buildcommdcb(stcom,pdcb) then setcommstate(pcom,pdcb)  
else ;  
while (msg.message <> wm_keydown) do  
begin  
bb:=readfile(pcom,xb,1,xn,nil);  
if not bb then break;  
st:=st+' '+inttostr(xb);  
if length(st) mod 32=0  
then st:=st+chr(10)+chr(13);  
label1.caption:=st;  
peekmessage(msg,form1.handle,0, 0,pm_remove); {читаем сообщение из очереди, если есть - удаляем}  
application.processmessages; {очищаем очередь сообщений - на всякий случай}  
end;  

Прежде чем подключать к компьютеру какие-либо устройства, следует сказать пару слов о технике безопасности. Если прибор беспаспортный или, тем более, самодельный, следует проверить следующее: а) напряжение на выходе порта прибора не должно превышать значений +/-15 В (минимум +/-3 В), и на экране осциллографа не должно наблюдаться заметных выбросов и "шпилек", превышающих эти значения, - в противном случае вы рискуете лишиться com-порта (это особенно неприятно, если порт расположен на материнской плате; в сомнительных случаях лучше экспериментировать с дополнительной isa-картой, оборудованной com-портами; б) нужно убедиться, что выход прибора электрически развязан с сетью.

Автор: Юрий Ревич

Добавлено: 31 Июля 2018 07:16:18 Добавил: Андрей Ковальчук

Управление приложением через Telnet

Итак, начнем с главного - почему для удаленного администрирования своей программы следует использовать именно Telnet? Ответ на этот вопрос достаточно прост:

Утилита Telnet есть на любом компьютере с операционной системой Windows, UNIX, AIX и т.п., поэтому ее не требуется писать или устанавливать
Telnet является штатным средством удаленного администрирования.
Telnet подразумевает текстовый обмен, поэтому его очень легко поддерживать в своей программе
Возможностей текстового терминала как правило достаточно для управления программой, ее настройки и администрирования
Рассмотрим немного теории. Утилиту Telnet легче всего запустить через Start->Run (Пуск -> Выполнить). После запуска необходимо произвести соединение с удаленным хостом, для чего выполняется используется меню "Connect->Remote System". При этом выводится меню соединения, в котором необходимо указать три параметра: хост, порт и тип терминала. В качестве хоста указывается имя удаленного компьютера (или его IP адрес), порт можно задать двумя путями - выбором/вводом символического имени (например, telnet), или вводом номера порта. Мы будем пользоваться вторым путем, т.е. будем использовать нестандартные номера портов. Тип терминала оставим vt100.

Утилита Telnet поддерживает параметры командой строки:

telnet [remote_host]


где

remote_host представляет собой имя или IP адрес удаленной машины. 
port номер порта. Если соединение идет по стандартному порту, то этот параметр опускается. 
Пример:

telnet zaitsevov или telnet zaitsevov 5000

Протокол Telnet очень прост - сначала устанавливается TCP/IP соединение с удаленной машиной. Затем, когда пользователь вводит символ, происходит его передача удаленному хосту. Для простоты будем называть его сервером.

Далее возможно два режима работы - с локальным эхом или без локального эха (режим по умолчанию). Если работа ведется с локальным эхом, то каждый вводимый пользователем символ немедленно отображается на экране. При работе без локального эха сервер обязан создавать эхо, дублирую принимаемые данные клиенту. Это позволят тестировать канал (каждый символ проходит по кругу) и организовывать ввод данных без эха (например, для ввода пароля). Мои примеры ориентированы на работу без локального эха.

При приеме любой информации от сервера утилита Telnet немедленно отображает его на экране. Это позволяет серверу организовывать эхо и выводить любую информацию в текстовом виде. При этом поддерживатся некоторые управляющие коды, например, код "забой", стирающий один символ.

Итак, приступим к разработке приложения. Создадим пустой проект и поместим на форму компонент ServerSocket1 типа TServerSocket. Зададим ему порт, например 5000. Напоминаю, что:

номер порта должен быть нестандартным, чтобы не пересекаться с другими программами. При этом желательно считывать его из INI файла, что даст возможность настройки при необходимости. 
Свойство Active должно быть false и устанавливаться в true при запуске программы. Иначе приложение свалится при попытке запуска второй копии или при отсутствии сети. Установку Active := true следует делать в блоке try ... except 
Итак, в обработчике OnCreate формы пишем:


[DELPHI]begin  
  try  
    ServerSocket1.Active := true;  
  except  
    ShowMessage('Ошибки при активации ServerSocket');  
  end;  
end;   

Далее необходимо научиться определять моменты соединения и отключения клиента. Для этого следует создать обработчики OnClientConnect и OnClientDisconnect. Сразу отмечу, что при подключении клиента обычно принято выдывать ему заголовок, ообщающий о том, что он соединился с программой *** версии NN. С учетом этого обработчик OnClientConnect будет иметь вид:


procedure TMain.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);  
begin  
  Socket.SendText('Connected. Программа Telnet1 Example на проводе.'+#$0D+#$0A);  
  Socket.SendText('Enter password : ');  
  Connected := false;  
  Memo1.Lines.Add('Произошло соединение с пользователем');  
end;  

При этом я хочу подчеркнуть особенность - нормально поддерживается одно соединение, для нескольких необходимы некоторые усложнения и мых их пока опустим.

Особенности:

Выводить информацию при соединении желательно на английском языке. Это позволяет избежать ситуации, когда на компьтере администратора не окажется поддержки русского языка и Telnet выведет ему абракадабру. У меня это наблюдается постоянно на английской NT 4 - приходится каждый раз лазить в настройки Telnet и задавать русский CharSet.
При соединении следует спросить пароль. Иначе каждый, кому нечего делать, залезет в программу и будет там ковыряться (из практики - преценденты были).
Переменная Connected отмечает, что пользователь еще не соединился с программой (т.е. не провел свою идентификацию). Рассмотрим сразу обработчик OnClientDisconnect, он еще проще:


// Поддержка связи по TCP/IP для удаленного конфигурирования - действия при отключении  
procedure TMain.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);  
begin  
  Connected := false;  
  Memo1.Lines.Add('Соединение разорвано');  
end;   

Итак, теперь настало время для самого интересного - написания обработчика OnClientRead. Этот обработчик вызывается всякий раз, когда от клиента приходят данные. Т.е. в свете приведенных выше теоретических замечаний это будет происходить при вводе каждого отдельного символа. Задачи обработчика:

Создавать (при необходимости) эхо для всех принимаемых символов. Очевидно, что при вводе паролей эхо создавать не нужно. При созании эха необходимо учитывать, что символ с кодом FF (буква "я") должен повторяться дважды, иначе он будет погложен Telnet - ом как служебный и не отобразится
Накапливать вводимые символы, ожидая прихода признака конца команы. Как правило, признаком конца команды считают перевод код строки (следует заметить, что тут разработчик сам себе стандарт, но отклоняться от общепринятых правил не рекомендуется. Для накопления принимаемой информации стоит завести буферную переменную, в моем случае она будет называться TelnetS.
При получении символа с кодом 08h ("BackSpace") необходимо не помещать ее в буфер, а стереть из буфера последний символ. Но в виде эха его отправить необходимо, т.к. это приведет к стиранию символа на экране Telnet (при подавлении эха он останется на экране, но сотрется в буфере программы, что приведет к путанице).
При обнаружении символа перевода строки (код $0D) следует считать содержимое буфера командой и интерпретировать. Как - это отдельный разговор
Все вышеописанное реализует примерно следующий код:


// Поддержка связи по TCP/IP для удаленного конфигурирования - действия при получении данных  
procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket);  
var  
  s, st: string;  
begin  
  s := Socket.ReceiveText;  
  
  // Это код перевода строки ? Если да, то выполняем команду и передаем ее ответ клиенту  
  if ord(s[1]) = $0D then  
  begin  
    st := ExecuteCMD(TelnetS);  
    if st <> '' then  
      st := #$0D + #$0A + st;  
    st := st + #$0D + #$0A + '>';  
    TelnetSendText(Socket, st);  
    TelnetS := '';  
    exit;  
  end;  
  
  // Это код клавиши BackSpace. Если да, то передадим его клиенту  
  // и удалим последний символ из буфера  
  if ord(s[1]) = $08 then  
  begin  
    Delete(TelnetS, length(TelnetS), 1);  
    TelnetSendText(Socket, s);  
    exit;  
  end;  
  
  // Добавим очередной символ к буферу  
  TelnetS := TelnetS + s;  
  
  // Передадим его клиенту для организации эха  
  if connected then  
    TelnetSendText(Socket, s);  
end;   

Как легко заметить, приведенный выше код реализует эхо, обрабатывает BackSpace и дожидается ввода команды, считая код $OD (Enter) признаком завершения ввода команды. При обнаружении этого кода вызывается функция пользователя ExecuteCMD, которая должна разобрать и проанализировать команду, выполнить ее и вернуть (при необходомости) ответ пользователю. Эта же функция занимается проверкой вводимого пользователем пароля. Так ка передача ответа/эха имеет некоторые особенности, например, необходимость удвоения символа с кодом FF и подавления передачи для реализации невидимого ввода, имеет смысл выполнить ее в виде отдельной функции:


// Передача ответа/эха клиенту  
function TForm1.TelnetSendText(Socket: TCustomWinSocket; AText: string): boolean;  
var  
  i: integer;  
  St: string;  
begin  
  Result := false;  
  if not(connected) then  
    exit;  
  St := '';  
  for i := 1 to length(AText) do  
    if AText[i] <> #$FF then  
      st := st + AText[i]  
    else  
      st := st + #$FF + #$FF;  
  Socket.SendText(st);  
end;  
  
// В моем примере функция ExecuteCMD имеет вид:  
// Интерретатор команд  
function TForm1.ExecuteCMD(ACmd: string): string;  
var  
  UCmd, Params: string;  
begin  
  Result := '';  
  Memo1.Lines.Add('Выполняется: '+ACmd);  
  if not(connected) then  
  begin  
    if UpperCase(ACmd) = '123' then  
    begin  
      Connected := true;  
      Result := 'Пользователь идентифицирован!';  
    end;  
    exit;  
  end;  
  
  // Выделение команды  
  UCmd := ACmd;  
  Params := '';  
  if pos(' ', UCmd) > 0 then  
  begin  
    Params := Copy(UCmd, pos(' ', UCmd)+1, Length(UCmd));  
    UCmd := Copy(UCmd, 1, pos(' ', UCmd)-1);  
  end;  
  UCmd := Trim(UpperCase(UCMD));  
  Memo1.Lines.Add('Выделена команда: '+UCmd);  
  
  // ? или HLP или HELP - вывод справки  
  if (UCmd = '?') or (UCmd = 'HLP') or (UCmd = 'HELP') then  
  begin  
    Result :=  
    'Краткая справка по командам Telnet интерфейса'+CRLF+  
    ' ?, HLP, HELP - вызов справки'+CRLF+  
    ' EXIT - завершение работы по Telnen интерфейсу'+CRLF+  
    ' HALT - немедленный останов программы'+CRLF+  
    ' VER - версия программы'+CRLF+  
    ' MESS <собщение> - вывод сообщения для пользователя'+CRLF+  
    ' INP <собщение> - вывод сообщения для пользователя и возврат его ответа';  
    exit;  
  end;  
  
  if (UCmd = 'EXIT') then  
  begin  
    ServerSocket1.Socket.Connections[0].Close;  
    exit;  
  end;  
  
  if (UCmd = 'VER') then  
  begin  
    Result := 'Версия 1.00 от 27.01.2001 (C) Зайцев Олег';  
    exit;  
  end;  
  
  if (UCmd = 'HALT') then  
    halt;  
  
  if (UCmd = 'MESS') then  
  begin  
    ShowMessage(Params);  
    exit;  
  end;  
  
  if (UCmd = 'INP') then  
  begin  
    Result := InputBox(Params,'Введите ответ', '');  
    exit;  
  end;  
  
  Result := 'Неизвестная команда ' + ACmd;  
end;  

Реальная система команд естественно определяется разработчиком, но рекомендуется предусмотреть следующие команды:

?, HLP, HELP для вывода справочной информации (практика показала, что при поддерке 20-30 команд больше половины забываются за месяц)
EXIT - завершение обмена
И, наконец, в завершении следует отметить одну особенность - пользователь может завершить обмен корректно (путем ввода команды EXIT (если таковая поддерживается) или выбором опции "Отключить" в Telnet; и некорректно - путем закрытия Telnet во время обмена. В этом случае в программе будет ошибка сокета 10054. Ее имеет смысл поймать и подавить при помощи обработчика OnClientError следующего вида:


procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket;  
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);  
begin  
  // Обработка события "разрыв соединения"  
  if ErrorCode = 10054 then  
  begin  
    Socket.Close;  
    ErrorCode := 0;  
  end;  
end;  

И в завершении хочется сказать, что подобная система внедрена в несколько моих программ, испрользуемых в ОАО Смоленскэнерго и отлично себя зарекомендовала, т.к. предприятие большое и возможность удаленной настройки/управления в ряде случаев освобождает разработчика от ненужной беготни.

Добавлено: 31 Июля 2018 07:13:54 Добавил: Андрей Ковальчук

Счетчик посещений на Delphi

Счетчики предназначены для учета количества посетителей на Ваш сайт. Кроме этого на счетчик можно возложить операции ведения статистики, учет хостов откуда пришли посетители и т.д.

Данный пример демонстрирует работу простого текстового счетчика с ведением списка IP адресов посетителей.

Сначала пропишем обработчик WebActionItem


procedure TWM.WMWebActionItemMainAction(Sender: TObject;  
    Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);  
var  
    f:TextFile;  
begin  
    Response.Content:=SetCounter; // Устанавливаем счетчик  
  
    // Записываем IP посетителя  
    AssignFile(f,log_path);  
    Append(f);  
    Writeln(f,Request.RemoteAddr);  
    CloseFile(f);  
end;  

Осталось реализовать функцию SetCounter


function TWM.SetCounter: String;  
var  
    f:TextFile;  
    count:Integer;  
begin  
    AssignFile(f,counter_path);  
    Reset(f);  
    // Считываем значение счетчика  
    Readln(f,count);  
    CloseFile(f);  
    //Инкреминируем  
    Inc(count);  
    Rewrite(f);  
    // Записываем  
    writeln(f,count);  
    CloseFile(f);  
    Result:=IntToStr(count);  
end;  

И еще необходимо определить константы имен файлов const


counter_path='counter.dat'; // Файл для значений счетчика  
log_path='counter.log'; // Файл для IP адресов  

Для работы этого скрипта необходимо создать два файла, для ведения счета и для списка IP. В файле счета необходимо установить начальное значение счетчика, сделать это можно в любом текстовом редакторе.

Добавлено: 31 Июля 2018 07:12:56 Добавил: Андрей Ковальчук

Создаем свой GetRigrht

Постановка задачи

А задача наша проста как угол дома - сесть за комп и максимум за полчаса сообразить себе собственный GetRight. Конечно, он не будет производить докачку после дисконнекта, качать в заданное тобой время, не сможет качать несколько файлов сразу... но зато он сможет качать файлы по ftp и http, для начала совсем неплохо. Соображать прогу будем на Delphi. Ну что, задача ясна? От винта!

Делаем фейс

Запускай Delphi, желательно в версии 4 или 5. Перед тобой раскроется целехонькая и нетронутая (хе-хе, пока) форма. На ней мы и будем мутить. Что, ты не знаешь, что такое форма и где она там перед тобой раскрывается? Тогда позырь на монитор - вон она, большой серый прямоугольник в центре. И вообще, я не буду тебе щас про дельфийский интерфейс шибко подробно рассказывать. Лучше сразу за дело! Сейчас мы немного покликаем мышом и потаскаем. Итак, начнем: сначала нужно изменить название формы. Она сейчас называется Form1, а на кой тебе такое название? Это ж название окна твоей проги. Изменить! Кликай по форме, она становится активной, теперь смотри налево - там Object Inspector, фича для конфигурирования разных свойств. Свойство, ответственное за название, называется Caption, ищи его в Object Inspector'е и нажимай на него. В белом окошке вводи что-то типа "Крутая программа-качалка". Ну как, все понятно? Позырь вон на скриншот, там Object Inspector есть.

Теперь смотри над формой - там палитра компонентов. На закладке Standard кликай мышом по букве А. Эта буква А - ни что иное, как Метка (Label). Нам она нужна, чтобы писать на форме. Кликай мышом по ней, а потом по форме, куда кликнешь на форме - там она и будет. Она сейчас выделена (в черных квадратиках вся), посему Object Inspector отображает ее свойства. Меняй Caption (в нем напиши "Введи адрес файла сюда: ") и Font (он находится ниже, кликни на нем, а потом на трех точках; я ставлю 10-ый MS Sans Serif полужирный). Добавь еще одну метку (поставь ее ниже), в ней Caption поставь "Сохранить файл сюда: ", ну и шрифт поставь аналогичный. В третьей метке поменяй шрифт и сотри все в Caption, здесь мы будем выдавать сообщения по ходу загрузки. Теперь добавь два Edit'а (справа от метки в палитре компонентов). Первый поставь напротив первой метки справа (это будет для ввода адреса), второй - напротив второй метки справа (это для сохранения). У обоих сотри текст (там написано Edit1, Edit2, на фиг оно надо), для этого выдели (клик по нему мышом) и стирай текст в свойстве Text. Затем кинь на форму две кнопки (через одну справа от Edit'а в палитре компонентов), первую назови "Закачать", вторую - "Закрыть" (это все свойства Caption кнопок). Потом открой закладку Internet (для Delphi 4) или FastNet (для Delphi 5) палитры компонентов. Помести на форму компоненты NMFTP и NMHTTP, это для закачки. Они не будут видны в готовой программе, посему тыкай их куда хочешь. Уфф, можешь сохраняться. Дави File -> Save All. Форму обзови main, например, а проект - downloader. Посмотри, что у меня получилось.

Готовимся кодить

Увы, программирование состоит не только из кликанья мышой и редактирования свойств. Приходится и кодить, и сейчас мы вплотную приблизились к этому. Но начнем с простого. Кликни два раза по кнопке "Закрыть". Опа! На экране появилось следующее:


procedure TForm1.Button2Click(Sender: TObject);  
begin  
end;  

и твой курсор мигает между begin и end. Это - обработчик события OnClick для кнопки "Закрыть", и все, что ты напишешь между begin и end, будет выполняться при клике по этой кнопке. А нам нужно, чтобы прога закрывалась, посему пишем


procedure TForm1.Button2Click(Sender: TObject);  
begin  
NMFTP1.Disconnect ; // разрываем связь по фтп, если она установлена  
NMHTTP1.Disconnect ; // разрываем связь по хттп, если она установлена  
Close ; // Закрываем прогу  
end ; 

Если не разорвать связь, прога просто откажется выходить. Поэтому и разрываем.

Кстати, после // в Delphi до конца строки идет комментарий, его она не учитывает при компиляции. Комментарии для тебя, чтобы понятнее было, не нужно их переписывать. Обрати внимание, там написано Button2Click, а не "Закрыть" Click. Button2 - это рабочее имя кнопки для взаимодействия с программой (как в вышеописанном примере), а Caption - это всего лишь надпись на кнопке. Все имена компонентов находятся в их свойстве Name, можешь посмотреть в Object Inspector'е. Теперь кликни два раза по первому Edit'у, появляется нечто похожее на обработчик для кнопки:


procedure TForm1.Edit1Change(Sender: TObject) ;  
begin  
end ;  

Тут вместо OnClick мы видим OnChange - все, что находится между begin и end, будет выполняться, когда в Edit'е что-то напишут. Мы изменим Tag (свойство Edit'а, некоторое число, по умолчанию 0), это нам понадобится потом, для проверки заполнения всех полей.


procedure TForm1.Edit1Change(Sender: TObject) ;  
begin  
Edit1.Tag := 1 ; // присвоить тагу первого эдита значение 1  
end ;  

Аналогичную операцию необходимо проделать и со вторым эдитом, только там нужно писать:


Edit2.Tag := 1 ;

Не забывай об именах! Вот и все с подготовкой, а теперь начинается...

Реальный кодинг

Да, именно он. Читай внимательнее и вникай. Перейдем к нашей главной кнопке - "Закачать". Кликай по ней два раза и создавай обработчик, далее вписывай код, чтобы получилось вот что:


procedure TForm1.Button1Click(Sender: TObject) ;  
begin  
Label3.Caption := '' ; // очищаем метку для сообщений  
if ( Edit1.Tag = 1 ) and ( Edit2.Tag = 1 ) and ( Edit1.Text <> '' )  
and ( Edit2.Text <> '' ) then // проверка данных  
begin  
// данные введены, выполнять действия  
end  
else Label3.Caption := 'Введи все данные, чувак!' // не все данные  
end ;  

Что есть что: сначала очищаем метку, просто присваиваем ее Caption'у пустое место (сначала она, правда, и так пустая, но потом будет полная, поэтому очищаем), затем идет стандартный оператор if then else, используем этот оператор для проверки, все ли поля заполнены - помнишь, мы тагам единицы присваивали? Так вот, теперь и проверяем - если чувак поля не заполнил, то таги - "0", и проверка не пройдет. Но юзверь мог ввести дату, а потом все стереть - вот для этого и нужна проверка значений эдитов на '' - если там пустое место (''), то проверка не пройдет. Дальше просто: если проверка прошла - гоним далее, нет - пишем в метке "Введи все данные, чувак!". Ну как, врубаешься? Если нет, просто пиши код, но все-таки старайся понять.

Продолжаем разговор. Нам нужно узнать, по фтп загружать или по хттп. Для этого нужно знать, что впереди: ftp:// или http://. Чтобы вытянуть это из оригинального адреса, проведем ряд извращений со строками. Дописывай код:


procedure TForm1.Button1Click(Sender: TObject) ;  
begin  
Label3.Caption := '' ; // очищаем метку для сообщений  
if ( Edit1.Tag = 1 ) and ( Edit2.Tag = 1 ) and ( Edit1.Text <> '' )  
and ( Edit2.Text <> '' ) then // проверка данных  
begin // данные введены, выполнять действия  
addr := Edit1.Text ; // сохраняем адрес в переменную  
serv := Copy( addr , 1 , 6 ) ; // копируем 6 символов из адреса в  
// переменную serv  
if serv = 'ftp://' then // впереди ftp://  
begin  
// качаем по фтп  
end  
else  
if serv = 'http:/' then // впереди http:/  
begin  
// качаем по хттп  
end  
else Label3.Caption := 'Что за корявый адрес?' ;  
end  
else Label3.Caption := 'Введи все данные, чувак!' // не все данные  
end ;  

Сначала мы сохранили адрес из первого эдита в переменную addr, затем в переменную serv мы из переменной addr копируем 6 символов. Используем функцию Copy. В скобках идут параметры - сначала пишем переменную, из которой копировать, потом номер символа, с которого начинать, ну и потом сколько символов копировать. Всю скопированную бурду сохраняем в переменной serv, теперь проверяем: если серв равен ftp://, то качать будем по фтп, а если http:/ - по хттп. Ну а если серв другой, то адрес корявый - это и напишем в метке. Кстати, переменные serv и addr еще не определены, и программа будет ругаться (а работать не будет). Надо их определить, иди в самый верх кода и там ищи слово var. Вот после этого слова и допиши переменные, заодно добавь еще несколько - они нам потом пригодятся. Вот так:


var  
serv , addr , host , dir : String ;  

String - это строка, соответственно, все эти переменные - строки. Уфф! Теперь последние штрихи на этой стадии - для скачки по фтп нужно сначала соединиться с хостом (сервером), а потом уже скачивать файло. Нужно разбить адрес на хост и собственно адрес файла, а это опять извращения со строками. Дописывай:


procedure TForm1.Button1Click(Sender: TObject) ;  
begin  
Label3.Caption := '' ; // очищаем метку для сообщений  
if ( Edit1.Tag = 1 ) and ( Edit2.Tag = 1 ) and ( Edit1.Text <> '' )  
and ( Edit2.Text <> '' ) then // проверка данных  
begin // данные введены, выполнять действия  
addr := Edit1.Text ; // сохраняем адрес в переменную  
serv := Copy( addr , 1 , 6 ) ; // копируем 6 символов из адреса в  
// переменную serv  
if serv = 'ftp://' then // впереди ftp://  
begin  
Delete( addr , 1 , 6 ) ; // удаляем первые 6 символов из  
//адреса  
host := Copy ( addr , 1 , ( Pos( '/' , addr ) - 1 ) ) ;  
// находим хост  
Delete( addr , 1 , ( Pos( '/' , addr ) - 1 ) ) ;  
dir := addr ;  
// находим путь к файлу  
// качаем по фтп  
end  
else  
if serv = 'http:/' then // впереди http:/  
begin  
// качаем по хттп  
end  
else Label3.Caption := 'Что за корявый адрес?' ;  
end  
else Label3.Caption := 'Введи все данные, чувак!' // не все данные  
end ;  

Итак, сначала функцией Delete стираем первые 6 символов из адреса. Функция берет строку, из которой стирать, символ, с которого начинать стирать, количество стираемых символов - все очень похоже на Copy. Затем в переменную host копируем адрес сервака, но вместо количества символов там стоит конструкция типа

(Pos( '/' , addr ) -1). Все путем! Функция Pos находит положение символа / в строке addr и выдает число, которое показывает, какой этот символ по счету. Но нам его копировать не надо, поэтому и отнимаем единицу. Теперь, если адрес файла ftp://my.host.com/file.exe, то адрес сервера будет my.host.com. Дальше стираем из адреса хост, и остается адрес файла - его и присваиваем переменной dir. Все, подготовка закончена, все данные обработаны, сохраняйся.

Кодим скачку по фтп

Обработаем теперь докачку по фтп. Ниже идущий код пиши там, где надпись:

// качаем по фтп.


Сначала поработаем с кнопками, дописывай код:


Edit1.Enabled := false ;  
Edit2.Enabled := false ; // вырубаем эдиты  
Button1.Enabled := false ; // выключаем кнопку "Закачать"   

Тут, я думаю, все понятно, прописываем закачку:


NMFTP1.Host := host ;  
NMFTP1.UserID := 'anonymous' ;  
NMFTP1.Password := '' ;  
Label3.Caption := 'Подключаемся ... ' ;  
NMFTP1.Connect ;  
NMFTP1.Mode( MODE_IMAGE ) ;  
Label3.Caption := 'Начинаем качать ...' ;  
NMFTP1.Download( dir , Edit2.Text ) ;  
Label3.Caption := 'Ура !!!' ;  
Label3.Caption := 'Отключаемся ...' ;  
NMFTP1.Disconnect ;  

Тут тоже все понятно - соединяемся, скачиваем, отсоединяемся. Download берет себе сначала адрес файла в Инете, а потом адрес, куда его сохранить на диске. Вот и все. Обрабатываем кнопки:


Button1.Enabled := true ; // включаем кнопку "Закачать"  
Edit1.Enabled := true ;  
Edit2.Enabled := true ; // врубаем эдиты  
Edit1.Clear ;  
Edit2.Clear ; // очищаем эдиты   

Поработай немного мышом - добавим несколько комментариев. Выдели компонент NMFTF1 у себя на форме, затем кликни в Object Inspector'е на вкладку Events (кликай на OnConnect, затем на белое место справа - два раза).

Пиши:


procedure TForm1.NMFTP1Connect(Sender: TObject);  
begin  
Label3.Caption := 'Подключено успешно!!!' ;  
end; 

Теперь, когда соединение установлено, прога выдаст в метку сообщение. Кликай по OnConnectionFailed. Пиши:


procedure TForm1.NMFTP1ConnectionFailed(Sender: TObject);  
begin  
Label3.Caption := 'Ошибка!!! Не могу соединиться!' ;  
end;  

Ну и так и далее, там все прозрачно, пофантазируй немного.

Все, обработка фтп закончена.

Кодим скачку по http

После скачки по фтп это совсем легко, посему отдыхай. Код пиши вместо строки:

// качаем по хттп .


А код следующий:


Edit1.Enabled := false ;  
Edit2.Enabled := false ; // вырубаем эдиты  
Button1.Enabled := false ; // выключаем кнопку "Закачать" 

Обрабатываем кнопки и эдиты, как и при скачке по фтп.


Label3.Caption := 'Подключаемся ... ' ;  
Delete(addr, 1, 7) ;  

В переменной addr у нас адрес, помнишь? Так вот, удаляем из него кусок http:// - это ОЧЕНЬ важно. Почему? Потом объясню. Теперь, собственно, скачка:


NMHTTP1.InputFileMode := true ;  
NMHTTP1.Body := Edit2.Text ;  
NMHTTP1.Get( addr ) ; // качаем  

Опять обрабатываем кнопки:


Button1.Enabled := true ; // включаем кнопку "Закачать"  
Edit1.Enabled := true ;  
Edit2.Enabled := true ; // врубаем эдиты  
Edit1.Clear ;  
Edit2.Clear ; // очищаем эдиты  

С кодом все, теперь осталось добавить несколько событий, как и при скачке по фтп. Кликай на компоненте NMHTTP1 на форме, и в Object Inspector'е выбирай закладку Events. А дальше фантазируй: напиши что-нибудь при OnConnect, OnConnectionFailed, OnDisconnect. А вообще, корректная обработка ошибок (типа OnConnectionFailed) - это гимор, поэтому особо не забивай себе голову.

Последние штрихи

Чего-то не хватает... Правильно! Не хватает прогресс-бара - этой синей полоски с процентами! Но это не проблема, сейчас сделаем. Открывай закладку Win32 палитры компонентов, хватай компонент ProgressBar и лепи его на форму (чтобы посмотреть, как его прилепил я, позырь на скриншот готовой проги). Теперь выделяй компонент NMFTP1 и открывай в Object Inspector'е закладку Events. Ищи OnPacketRcvd. Создавай обработчик (клик по OnPacketRecvd, два по пустому месту справа от него). Там пиши следующий код:


procedure TForm1.NMFTP1PacketRecvd(Sender: TObject);  
begin  
ProgressBar1.Position := Round(NMFTP1.BytesRecvd*100/NMFTP1.BytesTotal) ;  
Label3.Caption := 'Получено ' + IntToStr(NMFTP1.BytesRecvd) + ' байт из ' + IntToStr (  
NMFTP1.BytesTotal ) ;  
end;  

Строки разрывать не надо, просто они длинные и не помещаются.

Теперь, когда прога будет что-то качать, синяя строчка будет ползти, а в метке будет написано, сколько байт уже получено. Все эти действия необходимо повторить и для NMHTTP1, только NMFTP1 в коде замени на NMHTTP1. После завершения загрузки очистим ProgressBar - для этого в конце фтп и хттп фрагментов - там, где мы открывали эдиты и очищали их - допиши следующую строку:


ProgressBar1.Position := 0 ; // очищаем прогресс-бар   

Теперь сохраняй все и компилируй (дави на F9) - получишь вполне работоспособную прогу-качалку, пользуйся.

Последнее слово

Так вот, чтобы написать эту прогу, мне пришлось основательно помучаться: я не знал, ни как закачать файлы по фтп, ни как закачать его по хттп. Но если с фтп, перерыв борландовский хелп и примеры, я разобрался, то хттп постоянно глючил, выдавал битый файл. Я написал в десяток ньюсгрупп - как русских, так и забугорных, задавал вопросы на бордах крутых дельфовских сайтов, я писал программерам, я написал в службу поддержки разработчика компонента NMHTTP ... И что ты думаешь, кто-нибудь посоветовал что-нибудь путевое? Фиг!

Я дошел сам. А дело было в мелочи: я делал все правильно, но писал


NMHTTP1.Get('http://www.host.com/file.zip') ;  

А нужно было


NMHTTP1.Get('www.host.com/file.zip') ;  

Вот почему нужно выбрасывать часть http://. Но ты теперь можешь не мучаться и кодить спокойно, а если что не так - мыль мне, я постараюсь ответить на твои вопросы. Удачи!

Добавлено: 31 Июля 2018 07:11:48 Добавил: Андрей Ковальчук

Серверное программирование в Delphi

Просматривая содержимое какого-нибудь сайта, ты загружаешь в свой компьютер статические и динамические HTML-страницы. Если с первыми все понятно, то с последними дело обстоит несколько иначе. Динамические веб-сервера могут быть построены на разных технологиях: CGI (Perl, C/C++), PHP, ISAPI, ASP. Сейчас самые распространенные - это CGI и PHP (хотя PHP-скрипты могут работать и через CGI-интерфейс). И так уж сложилось, что CGI ассоциируется именно с перлом, ведь на этом языке написано бесчисленное множество супернавороченных скриптов. Но на самом деле это может быть практически любой язык, в том числе C/C++ и даже Delphi. О последнем как раз и пойдет разговор в нашей сегодняшней статье.

Протоколы программирования

CGI (Common Gateway Interface) - это стандартный протокол связи между браузером и веб-сервером. Подробнее о нем ты можешь узнать из предыдущих номеров Х.

Вместо CGI можно воспользоваться совершенно другим подходом (путем обращения к интерфейсам API сервера), таким как ISAPI (Internet Server API), разработанным Microsoft, и NSAPI (Netscape Server API). ISAPI позволяет писать DLL, загружаемые в адресное пространство сервера и сохраняемые там какое-то время. Загрузив DLL, сервер получает возможность использовать для обработки каждого запроса отдельный поток. То есть сервер получает запрос, грузит DLL (если она еще не загружена!) и выполняет соответствующий код, результат которого и отсылается клиенту. А так как служебный обмен данными на сервере осуществляется через память, такие приложения работают на порядок быстрее, чем аналогичные CGI. Apache сервер тоже позволяет расширить свои возможности при помощи CGI, но он и поддерживает специальные библиотеки расширения (модули).

Как это в Delphi

Существует множество языков программирования, поддерживающих разработку серверных приложений. И вовсе не обязательно изучать новый язык только из-за того, что, по мнению некоторых, он имеет преимущество перед тем, к которому ты уже привык, и на котором писать код тебе значительно проще. Так что, если ты предпочел остальным языкам программирования именно Delphi, этот материал для тебя.

Я не зря перечислил все виды серверного программирования - все они поддерживаются Delphi, и их разработка в его среде приятна и несложна. Давай рассмотрим подробнее.

Конечно, разрабатывать CGI и другие типы серверных приложений в Дельфи можно по-разному, но я предлагаю использовать технологию WebBroker, поддерживающую иерархию классов VCL и CLX, что сильно упрощает разработку веб-приложений, а также специальный тип модулей данных (WebModule). Самое главное, что компонент WebModule является общим для всех типов приложений! Т.е. никаких изменений в коде, будь то ISAPI-, или CGI-проект, ты не найдешь! Единственное НО: если разрабатываешь проекты ISAPI, то код в разных версиях Дельфи будет отличаться, а если решил писать модули Apache, то их придется установить на сервер. Для этого помести свой модуль в папку modules сервера и отредактируй файл httpd.conf - читай комментарии внутри файла.

А теперь давай напишем простенькое веб-приложение.

Графический счетчик обращений

Почему я выбрал такой простой пример? Цель моей статьи - показать возможности Delphi, а дальше уже на твое усмотрение.

Итак, запускай Delphi и лезь в New -> Other -> Web Server Application. Далее появится диалоговое окно, в котором ты сможешь выбрать тип приложения (выбирай CGI). Перед тобой появилось окошко компонента WebModule1. Для построения приложения тебе придется редактировать Actions этого компонента. Добавь новый Action, нажав на кнопку Add new в диалоговом окне Editing Webmodule1.Actions. После этого создай обработчик события OnAction и напиши туда код из "Листинга счетчика".

Поясню, мы создаем не многофункциональное приложение, а простой счетчик посещаемости, поэтому никакие другие свойства компонента редактировать не надо; а вот для более сложных приложений понадобится редактирование поля PathInfo для каждого нового Action’а. Думаю, ты понимаешь, зачем это нужно.

А теперь давай разберемся с программой. Только сначала отредактируй раздел uses:


uses Windows, Graphics, Jpeg, ExtCtrls, SysUtils, Classes, HTTPApp;
Сам код объяснять не буду - там все очень просто. Расскажу лишь, как это работает. Существует два метода написания графических счетчиков: первый заключается в рисовании картинок с цифрами от 0 до 9 и последующей комбинации из них нужного числа. Второй (мы будем использовать его) - рисование в памяти растровой картинки с выводом числа прямо на ней. Как мы этого добьемся? Для начала создаем растровое изображение (компонент Image), а для рисования на нем используем стандартные методы класса TCanvas. Далее мы подключаем это изображение к объекту класса TJpegImage, чтобы получить изображение в формате jpeg. После этого можно сохранить полученные данные в поток и вернуть их клиенту.

Механизм же реализации счетчика еще проще: при обращении клиента к программе осуществляется проверка на наличие лог-файла с числом посещений. Если файл существует, то программа просто добавляет единицу и перезаписывает файл; если же его нет, то в файл пишется единица (так как мы все же обратились к странице один раз). А дальше осуществляется подготовка изображения и отправка его клиенту.

За пересылку изображения клиенту отвечают три выражения:


Response.ContentType := 'image/jpeg'; // задает тип содержимого ответа;  
Response.ContentStream := Stream; // присваивает поток свойству ContentStream;  
Response.SendResponse; // отправляет ответ клиенту.  

Теперь для проверки работоспособности приложения ты можешь обратиться к нему так: www.somehost.ru/cgi-bin/Project1, или вставить в нужную тебе страницу тег <img src="путь_к_скрипту">.

Заключение

И последнее. При написании серверных приложений каждый сталкивался с проблемой их отладки. Но у тех, кто использует Delphi таких проблем больше не будет: начиная с 6 версии появился встроенный отладчик Web App Debugger. Подробно рассказывать о нем не буду, но если возникнут вопросы, смело присылай их мне.

Вот и все. Теперь у тебя есть начальные знания, необходимые для написания серверного приложения на Delphi.

Удачи!

Листинг счетчика


procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;  
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);  
var  
Bitmap: TBitmap;  
Hits: Integer;  
LogFile: Text;  
FileName: string;  
Jpeg1: TJpegImage;  
Stream: TMemoryStream;  
begin  
FileName :='Count.log'; //задаем название лог-файла  
System.Assign(LogFile, FileName);  
try  
if FileExists(FileName) then //если файл существует, то добавляем 1 к общему числу посещений  
begin  
Reset(LogFile);  
Readln(LogFile, Hits);  
Inc(Hits);  
end  
else //если нет, то пишем в файл 1  
Hits := 1;  
Rewrite(LogFile);  
Writeln(LogFile, Hits);  
finally  
Close(LogFile);  
end;  
Bitmap := TBitmap.Create; //создаем изображение  
try  
Bitmap.Width := 120;  
Bitmap.Height := 25;  
Bitmap.Canvas.Font.Name := 'Arial';  
Bitmap.Canvas.Font.Size := 14;  
Bitmap.Canvas.Font.Color := RGB (255, 127, 0);  
Bitmap.Canvas.Font.Style := [fsBold];  
Bitmap.Canvas.TextOut (1, 1, 'Hits: ' + FormatFloat ('###,###,###', Int(Hits))); //выводим количество посещений  
Jpeg1 := TJpegImage.Create;  
try  
Jpeg1.CompressionQuality := 50;  
Jpeg1.Assign(Bitmap);  
Stream := TMemoryStream.Create; //создаем поток  
Jpeg1.SaveToStream (Stream); //помещаем в него изображение  
Stream.Position := 0;  
Response.ContentType := 'image/jpeg';  
Response.ContentStream := Stream;  
Response.SendResponse; //отсылаем результат  
finally  
Jpeg1.Free;  
end;  
finally  
Bitmap.Free;  
end;  
end;  

Добавлено: 31 Июля 2018 07:09:49 Добавил: Андрей Ковальчук

Своя кнопка в Internet Explorer

ButtonText = Всплывающая подсказка к кнопке
MenuText = Текст, который будет использован для пункта в меню "Сервис"
MenuStatusbar = *Ignore*
CLSID = Ваш уникальный classID. Для создания нового CLSID (для каждой кнопки) можно использовать GUIDTOSTRING.
Default Visible := Показать ей.
Exec := Путь к Вашей программе.
Hoticon := иконка из shell32.dll когда мышка находится над кнопкой
Icon := иконка из shell32.dll

procedure CreateExplorerButton;  
const  
  TagID = '\{10954C80-4F0F-11d3-B17C-00C0DFE39736}\';  
var  
  Reg: TRegistry;  
  ProgramPath: string;  
  RegKeyPath: string;  
begin  
  ProgramPath := 'c:\folder\exename.exe';  
  Reg := TRegistry.Create;  
  try  
    with Reg do  
    begin  
      RootKey := HKEY_LOCAL_MACHINE;  
      RegKeyPath := 'Software\Microsoft\Internet Explorer\Extensions';  
      OpenKey(RegKeyPath + TagID, True);  
      WriteString('ButtonText', 'Your program Button text');  
      WriteString('MenuText', 'Your program Menu text');  
      WriteString('MenuStatusBar', 'Run Script');  
      WriteString('ClSid', '{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}');  
      WriteString('Default Visible', 'Yes');  
      WriteString('Exec', ProgramPath);  
      WriteString('HotIcon', ',4');  
      WriteString('Icon', ',4');  
    end  
  finally  
    Reg.CloseKey;  
    Reg.Free;  
  end;  
end;  

После выполнения этого кода достаточно просто запустить IE.

Добавлено: 31 Июля 2018 07:09:01 Добавил: Андрей Ковальчук

Свои настройки Proxy в программе с TWebBrowser.Изменение в ходе выполнения

Сразу скажу, что эта статья - маленькая рекомендация тем, кто хочет реализовать возможность работы TWebBrowser в своей программе с настройками Proxy , которые отличаются от стандартных.

В один прекрасный день мне понадобилось в программе периодически менять Proxy и при этом пользоваться всем, что предоставляет IE. Лучший и единственный выбор - TwebBrowser. При близком знакомстве с ним стало понятно, что через Proxy он работать не может (вернее может, но берет настройки из "Свойств обозревателя"). Перспектива постоянно менять настройки реестра меня не прельщала. И как назло ни в одной крупной конференции не было даже упоминания о возможности настройки Proxy в ходе выполнения программы кроме изменения реестра (может плохо искал). Перерыв Fido-архивы и конференции Инета накаткнулся на win-функцию UrlMkSetSessionOption. Вот к чему привели мои изыскания :


....  
uses ... urlmon, wininet ...  
....  
var PIInfo : PInternetProxyInfo;  
...       
New (PIInfo) ;  
PIInfo^.dwAccessType := INTERNET_OPEN_TYPE_PROXY ;    
//  Изменение  настроек ПРОКСИ   
PIInfo^.lpszProxy := PChar('some.proxy:someport');    
//  указать  прокси  напр.  
195.43.67.33:8080   
PIInfo^.lpszProxyBypass := PChar('');    
//  Список адресов, доступ к которым возможен минуя Proxy сервер  
  
UrlMkSetSessionOption(INTERNET_OPTION_PROXY, piinfo,   
SizeOf(Internet_Proxy_Info), 0);    
....   
Dispose (PIInfo) ;   
....   

Вызывать функцию UrlMkSetSessionOption можно из любого места программы, причем любое количество раз и с разными настройками.

После вызова функции TWebBrowser будет работать через указанный прокси. Еще раз повторюсь настройки касаются только текущей сессии (программы на момент выполнения ), общие настройки Windows не изменяются.

Добавлено: 31 Июля 2018 07:08:23 Добавил: Андрей Ковальчук

Реализация ping с помощью ICMP API

При работе в сети Интернет в некоторых случаях необходимо знать текущее состояние как локального, так и удаленных хостов (имеет ли локальный хост в данный момент возможность выхода в сеть Интеренет, доступен ли некий удаленный хост и т.д.)

Общеизвестно, что для указанных целей используется утилита ping. В данной статье описана реализация на Delphi возможностей этой программы с помощью ICMP API.

Принцип работы ping-а основан на использовании протокола ICMP - Internet Control Message Protocol (протокол управляющих, или контрольных, сообщений).
С помощью ICMP хосты в сети обмениваются различной служебной информацией (информацией о смене маршрута, уменьшении скорости передачи, недоступности какого-либо адреса и т.д.) В основе протокола ICMP лежит понятие сообщений. Сообщения ICMP протокола, как правило, оповещают об ошибках, возникающих при обработке датаграмм. ICMP использует основные свойства протокола IP, как если бы он являлся протоколом более высокого уровня. На самом же деле ICMP является составной частью IP.

Одним из типов сообщений протокола является т.н. "эхо-запрос". Получив "эхо-запрос" хост обязан ответить пославшему "эхо-ответом". По сути, "эхо-запрос" и "эхо-ответ" отличаются лишь адресами отправителя и получателя и кодом типа сообщения (тип 8 - "эхо-запрос", тип 0 - "эхо-ответ").
Более подробно протокол ICMP описан в RFC792.

Реализации утилиты ping на различных платформах существенно отличаются. Так, в ОС UNIX используются RAW sockets (необработанные, "сырые" сокеты), а в ОС Windows всех версий - т.н. ICMP API. Использование ICMP API оправдано только в ОС Windows NT и 2000, т.к. для использования RAW sockets в этих ОС необходимы права Администратора.

Из документации Microsoft:
"Icmp.dll предоставляет возможность разрабатывать приложения, использующие ICMP (ping, traceroute) без использования Winsock. Если разработчик пишет под платформу, поддерживающую Winsock, предпочтительнее использовать механизм Winsock RAW sockets.
Однако, при написании программ под Windows NT и Windows 2000 следует учитывать, что механизм RAW sockets доступен для использования только членам группы Administrators.Все функции в icmp.dll не рассматриваются как часть Win 32 API и не будут поддерживаться в следующих реализациях".

На практике, во всех 32-разрядных версиях Windows есть и используется библиотека icmp.dll. Так что, на данный момент, можно безбоязненно ее использовать.

ICMP API

Определения типов и прототипы функций для Microsoft icmp.dll:


type  
    ip_option_information = packed record  // Информация заголовка IP (Наполнение   
                    // этой структуры и формат полей описан в RFC791.  
        Ttl : byte;         // Время жизни (используется traceroute-ом)   
        Tos : byte;         // Тип обслуживания, обычно 0  
        Flags : byte;       // Флаги заголовка IP, обычно 0  
        OptionsSize : byte;     // Размер данных в заголовке, обычно 0, максимум 40  
        OptionsData : Pointer;  // Указатель на данные   
    end;  
  
   icmp_echo_reply = packed record  
        Address : u_long;            // Адрес отвечающего  
        Status : u_long;             // IP_STATUS (см. ниже)  
        RTTime : u_long;             // Время между эхо-запросом и эхо-ответом   
                         // в миллисекундах  
        DataSize : u_short;              // Размер возвращенных данных  
        Reserved : u_short;              // Зарезервировано  
        Data : Pointer;          // Указатель на возвращенные данные  
        Options : ip_option_information; // Информация из заголовка IP  
    end;  
  
    PIPINFO = ^ip_option_information;  
    PVOID = Pointer;  
  
        function IcmpCreateFile() : THandle; stdcall; external 'ICMP.DLL' name 'IcmpCreateFile';  
        function IcmpCloseHandle(IcmpHandle : THandle) : BOOL; stdcall; external 'ICMP.DLL'  name 'IcmpCloseHandle';  
        function IcmpSendEcho(  
                          IcmpHandle : THandle;    // handle, возвращенный IcmpCreateFile()  
                          DestAddress : u_long;    // Адрес получателя (в сетевом порядке)  
                          RequestData : PVOID;     // Указатель на посылаемые данные  
                          RequestSize : Word;      // Размер посылаемых данных  
                          RequestOptns : PIPINFO;  // Указатель на посылаемую структуру   
                                               // ip_option_information (может быть nil)  
                          ReplyBuffer : PVOID;     // Указатель на буфер, содержащий ответы.  
                          ReplySize : DWORD;       // Размер буфера ответов   
                          Timeout : DWORD          // Время ожидания ответа в миллисекундах  
                         ) : DWORD; stdcall; external 'ICMP.DLL' name 'IcmpSendEcho'; 

Функция IcmpSendEcho() посылает ICMP эхо-запрос по заданному IP адресу и помещает все ответы, полученные за время заданного таймаута, в буфер ответов (ReplyBuffer). Перед использованием функций Icmp.dll необходимо вызвать функцию WSAStartup() из Winsock.
Если этого не сделать, то первый же вызов функции IcmpSendEcho() приведет к ошибке 10091 (WSASYSNOTREADY).

Следует заметить, что ответы, помещенные в буфер, необязательно будут сообщениями "эхо-ответ". Возможно, что в ответ на "эхо-запрос" придут сообщения ICMP о возникших ошибках. Естественно, эти сообщения так же будут помещены в ReplyBuffer.
Размер буфера ответов должен быть достаточным для приема хотя бы одного ответа, будь то "эхо-ответ" или сообщение об ошибке. Отсюда, указанный размер складывается из размера самой сруктуры icmp_echo_reply плюс Max(RequestSize, 8), т.к. сообщение ICMP об ошибке составляет 8 байт.
Функция IcmpSendEcho() возвращает количество ответов (или структур icmp_echo_reply) в массиве ReplyBuffer. Если функция возвратила 0, то для более детальной диагностики возникшей ошибки используется функция GetLastError().

Возможные значения поля Status структуры icmp_echo_reply (IP_STATUS):

IP_STATUS_BASE 11000
IP_SUCCESS 0
IP_BUF_TOO_SMALL 11001
IP_DEST_NET_UNREACHABLE 11002
IP_DEST_HOST_UNREACHABLE 11003
IP_DEST_PROT_UNREACHABLE 11004
IP_DEST_PORT_UNREACHABLE 11005
IP_NO_RESOURCES 11006
IP_BAD_OPTION 11007
IP_HW_ERROR 11008
IP_PACKET_TOO_BIG 11009
IP_REQ_TIMED_OUT 11010
IP_BAD_REQ 11011
IP_BAD_ROUTE 11012
IP_TTL_EXPIRED_TRANSIT 11013
IP_TTL_EXPIRED_REASSEM 11014
IP_PARAM_PROBLEM 11015
IP_SOURCE_QUENCH 11016
IP_OPTION_TOO_BIG 11017
IP_BAD_DESTINATION 11018
IP_ADDR_DELETED 11019
IP_SPEC_MTU_CHANGE 11020
IP_MTU_CHANGE 11021
IP_UNLOAD 11022
IP_GENERAL_FAILURE 11050
MAX_IP_STATUS IP_GENERAL_FAILURE
IP_PENDING 11255

Ниже приведен базовый фрагмент кода, необходимый для однократного посыла эхо-запроса:


procedure TForm1.Button1Click(Sender: TObject);  
var  
    hIP : THandle;  
    pingBuffer : array [0..31] of Char;  
    pIpe : ^icmp_echo_reply;  
    pHostEn : PHostEnt;  
    wVersionRequested : WORD;  
    lwsaData : WSAData;  
    error : DWORD;  
    destAddress : In_Addr;  
begin  
      
    // Создаем handle  
    hIP := IcmpCreateFile();   
      
    GetMem( pIpe,  
            sizeof(icmp_echo_reply) + sizeof(pingBuffer));  
    pIpe.Data := @pingBuffer;  
    pIpe.DataSize := sizeof(pingBuffer);  
  
    wVersionRequested := MakeWord(1,1);  
    error := WSAStartup(wVersionRequested,lwsaData);  
    if (error <> 0) then  
    begin  
         Memo1.SetTextBuf('Error in call to '+  
                          'WSAStartup().');  
         Memo1.Lines.Add('Error code: '+IntToStr(error));  
         Exit;  
    end;  
      
    pHostEn := gethostbyname('delphi.mastak.com');  
    error := GetLastError();  
    if (error <> 0) then  
    begin  
         Memo1.SetTextBuf('Error in call to'+  
                          'gethostbyname().');  
         Memo1.Lines.Add('Error code: '+IntToStr(error));  
         Exit;  
    end;  
       
     destAddress := PInAddr(pHostEn^.h_addr_list^)^;  
  
      // Посылаем ping-пакет  
    Memo1.Lines.Add('Pinging ' +  
                    pHostEn^.h_name+' ['+  
                    inet_ntoa(destAddress)+'] '+  
                    ' with '+  
                    IntToStr(sizeof(pingBuffer)) +  
                    ' bytes of data:');  
  
    IcmpSendEcho(hIP,  
                 destAddress.S_addr,  
                 @pingBuffer,  
                 sizeof(pingBuffer),  
                 Nil,  
                 pIpe,  
                 sizeof(icmp_echo_reply) + sizeof(pingBuffer),  
                 5000);  
  
    error := GetLastError();  
    if (error <> 0) then  
    begin  
         Memo1.SetTextBuf('Error in call to '+  
                          'IcmpSendEcho()');  
         Memo1.Lines.Add('Error code: '+IntToStr(error));  
         Exit;  
    end;  
  
     // Смотрим некоторые из вернувшихся данных  
    Memo1.Lines.Add('Reply from '+  
                IntToStr(LoByte(LoWord(pIpe^.Address)))+'.'+  
                IntToStr(HiByte(LoWord(pIpe^.Address)))+'.'+  
                IntToStr(LoByte(HiWord(pIpe^.Address)))+'.'+  
                IntToStr(HiByte(HiWord(pIpe^.Address))));  
    Memo1.Lines.Add('Reply time: '+IntToStr(pIpe.RTTime)+' ms');  
  
    IcmpCloseHandle(hIP);  
    WSACleanup();  
    FreeMem(pIpe);  
end;  

Разумеется, это только "скелет". Многие возможные ошибки не обрабатываются.Для работы этого фрагмента надо:
создать форму TForm1;
включить в раздел Uses юнит WinSock;
поместить на форму компонент Memo1:TMemo и кнопку Button1:TButton;
в разделе Implementation поместить описания типов и функций Icmp.dll (см. выше);
сопоставить событию OnClick кнопки приведенную функцию.

Добавлено: 31 Июля 2018 06:59:43 Добавил: Андрей Ковальчук

Разработка CGI приложений на Дельфи

В последнее время в связи с растущей популярностью сети Интернет все чаше становится необходимость разработки приложений, которые бы могли работать непосредственно в www среде. Т.е. такие, которые бы полностью бы интегрировались в уже привычные нам веб-странички. По сути дела работа с таким приложением происходит полностью через любимый браузер пользователя и ничем не отличается от серфинга по страничкам. Ввод данных равно как и выдача обработанных результатов происходит через html-формы веб-страничек. Обработка же данных происходит на веб-сервере. Таким образом, мы получим самое что ни есть клиент-серверное приложение в его самом классическом понимании.

Необходимо отметить, что CGI-приложения разрабатываемые в средах разработки ориентированных на Win32 системы, в том числе и в Дельфи, а вернее серверная часть такого приложения может работать только под Win32 сервером, например IIS из NT или Personal Web Server из Windows98. Что касается клиентской части, то здесь никаких проблем совместимости не должно быть в принципе, т.к. клиентская часть представляет собой сгенерированный HTML код, который поддерживается любым браузером, не важно какую платформу использует пользователь, будь то Win32, OS/2, Unix и др.

Таким образом, программисту пишушему CGI-приложения придется столкнуться с двумя основными задачами - это разработка веб-интерфейса и разработка непосредственно математической части приложения.

Что касается веб-интерфейсов, то здесь желательно знать хотя бы основы языка HTML. Здесь мы не будем уделять этому особое внимание, хотя знание HTML для программиста CGI-приложений очень желательно. Сейчас же для нас будет вполне достаточным знание таких основопологающих тэгов как <HTML>,<BODY> и конструкции <FORM>.

Ну а теперь будем разбираться непосредственно с телом CGI-приложения. Во-первых, что такое CGI-приложение разрабатываемое в Win32 среде разработки? Это приложение типа Win32 CONSOLE, т.е. консольное приложение Win32. Только вот для такого приложения в отличии от классической Win32 консоли стандартным устройством ввода является либо поля ввода HTML формы либо строка адреса браузера, а в качестве стандартного устройства вывода используется окно браузера. Активизация приложения происходит непосредственно из какой-либо HTML странички, например так <A HREF="http://myhost/myapp.exe">My Application</A> Как мы уже выяснили такое CGI-приложение будет представлять собой исполняемую Win32 программу (exe), таким веб-приложениям принято давать расширение CGI, хотя это и непринципиально.

Для начала рассмотрим пример самой простой CGI-программки выдающей в окно пользовательского браузера текст "HELLO WORLD".


===cut===  
program MyApp  
{$APPTYPE CONSOLE}  // тип приложения Win32 консоль  
{$E cgi}        // Расширение приложения cgi  
begin  
  WriteLn('Content-Type: text/html');  
  WriteLn;  
  WriteLn;  
  WriteLn('<HTML>');  
  WriteLn('<HEAD>');  
  WriteLn('<TITLE>Простейшее CGI приложение</TITLE>');  
  WriteLn('<META http-equiv="Content-Type" content="text/html;' +  
                                ' charset=windows-1251">');  
  WriteLn('</HEAD>');  
  WriteLn('<BODY>');  
  WrОтiteLn('<H1>HELLO WORLD</H1>');  
  WriteLn('</BODY>');  
  WriteLn('</HTML>');  
end.  
===cut===  

Откомпиллируйте этот исходный код в среде Дельфи, поместите скомпонованный исполняемый код в ваш CGI-BIN каталог ( в каталог, где разрешено исполнение скриптов), напишите небольшую веб страничку для активизации нашего CGI-приложения, например, такую:

===cut==
<HTML>
<HEAD>
<TITLE>Форма для активизации CGI-приложения</TITLE>
</HEAD>
<BODY>
<A HREF="http://localhost/cgi-bin/myapp.cgi">
Нажми сюда для запуска приложения</A>
</BODY>
</HTML>
===cut==

Теперь откройте нашу веб-страничку и перейдите по ссылке "Нажми сюда для запуска приложения". Если вы все сделали правильно, то на экране в окне вашего любимого браузера появиться текст "HELLO WORLD".

Как видите все достаточно просто! Однако, для полноценной работы приложения, оно должно уметь не только выводить некие данные, но получать данные от пользователя, т.е. обеспечивать ввод информации. Ввод данных в случае CGI-приложения, как мы уже говорили, будет осуществляться по средствам интерфейса организованного веб-формой. Такая форма может передавать данные двумя способами, в зависимости от значения атрибута "METHOD".

В случае <FORM METHOD="GET" ...>... данные передаются через строку адреса браузера и записываются в переменную системного окружения QUERY_STRING, а размер строки данных в переменную CONTENT_LENGTH.

В случае <FORM METHOD="POST" ...>... передаваемые данные в строке адреса не отображаются, передаются через стандартный поток ввода консольной программы.

Таким образом задача получения данных CGI-приложением сводится к чтению определенной переменной окружения. Надо отметить, что передаваемые веб-формой данные имеют следующий формат: <имя_атрибута1>=<значение_атрибута1>&<имя_атрибута2>=<значение_атрибута2>...

Задача программиста сводится к извлечению значений нужных атрибутов из полученной от браузера строки и преобразования этих значений из вида URLencoded в обычные текстовые данные. Суть URLencoded формата заключается в том, что некоторые символы, содержащиеся в значении поля, заменяются на % и следующим за ним шестнадцатиричным кодом символа, а пробел заменяется на +.

А сейчас давайте рассмотрим пример CGI приложения, которое бы производило подобие некоторой идентификации пользователя системы.

===cut==
<!-- HTML форма ввода пароля -->
<HTML>
<HEAD>
<TITLE>Авторизация доступа</TITLE>
</HEAD>
<BODY>
<FORM method="POST" action="http://localhost/cgi-bin/chkpaswd.cgi">
  Введите пароль: 
  <input type="text" name="paswd" size=20>
  <input type="submit" value="Найти">
  <input type="reset" value="Очистить">
</FORM>
</BODY>
</HTML>
===cut==

Далее идет пример непосредственно CGI приложения. Следует отметить, что приведенный в этом примере способ получения данных от веб формы (непосредственное чтение устройства стандартного ввода STD_INPUT) является наиболее наглядным, но не самым удобным, в Дельфи предусмотренны более удобные механизмы, которых мы каснемся позже.


===cut==  
{Файл проекта CGIApp2}  
program CGIApp2;  
  
{$APPTYPE CONSOLE}  
  
uses   
  MainUn in 'MAinUn.pas';  
  
{$E cgi}  
  
begin  
  Main;  
end.  
  
program MainUn;  
interface  
uses  
  SysUtils, Windows, Classes;  
  
implementation  
  
// Функция перевода шестнадцетиричного символа в число  
function HexToInt(CH : char): integer;  
begin  
  Result:=0;  
  case CH of  
    '0'..'9': Result:=Ord(CH)-Ord('0');  
    'A'..'F': Result:=Ord(CH)-Ord('A')+10;  
    'a'..'f': Result:=Ord(CH)-Ord('a')+10;  
  end;  
end;  
  
// Преобразует символы, записанные в виде URLencoded  
function Decode(Value: string): string;  
var  
  i, L: integer;  
begin  
  Result:='';  
  L:=0;  
  for i := 1 to Length(Value) do  
  begin  
    if(Value[i] <> '%') and (Value[i] <>'+') and (L<1) then  
    begin  
      Result := Result + Value[i];  
    end  
    else  
    begin  
      if(Value[i] = '+') then  
        Result := Result + ' '  
      else if(Value[i] = '%') then  
      begin  
        L := 2;  
        if(i < Length(Value) - 1) then  
        begin  
          Result := Result + Chr(HexToInt(Value[i+1]) * 16 +   
                    HexToInt(Value[i+2]));  
        end;  
      end  
      else  
        Dec(L);  
    end;  
  end;  
end;  
  
// Фнкция возвращает значение атрибута заданного   
//в качестве параметра функции из строки данных   
//считанной из устройства стандартого ввода.  
  
function ParamByName(Name: string): string;  
var  
  SS, ST : string;  
  K : integer;  
begin  
  
  Result := '';  
  SS := InParams;  
  
  while Length(SS) <> 0 do  
   begin  
    K := Pos('&',SS);  
    if (K <> 0) then  
    begin  
      ST := Copy(SS,1,K-1);  
      SS := Copy(SS,K+1,10000);  
    end  
     else  
     begin  
      ST :=SS;  
      SS:='';  
    end;  
    K := Pos('=',ST);  
    if(K <> 0) then  
    begin  
      if(Name = Copy(ST,1,K-1)) then  
      begin  
        Result := Decode(Copy(ST,K+1,6000));  
      end;  
    end;  
  end;  
end;  
  
procedure Main;  
var  
  STR : string;  
  StdIn, Size, Actual : cardinal;   
  InParams : string;  
const  
  UserPassword : String = 'MyPass';  
begin  
  StdIn := GetStdHandle(STD_INPUT_HANDLE);  
  Size := SetFilePointer(StdIn, 0, nil, FILE_END);  
  SetFilePointer(StdIn, 0, nil, FILE_BEGIN);  
  SetLength(STR,Size+1);  
  if (Size <= 0) then  
    Exit;  
  // Читаем данные из стандартного устройства ввода  
  ReadFile(StdIn, STR[1], Size, Actual, nil);   
  STR[Size+1] := #0;  
  InParams := PChar(@STR[1]);  
    
  APasswd := ParamByName('paswd');  
  
  WriteLn('Content-Type: text/html');  
  WriteLn;  
  WriteLn;  
  WriteLn('<HTML>');  
  WriteLn('<HEAD>');  
  WriteLn('<TITLE>Идентификация пользователя</TITLE>');  
  WriteLn('<META http-equiv="Content-Type" content="text/html;'+  
                    ' charset=windows-1251">');  
  WriteLn('</HEAD>');  
  WriteLn('<BODY>');  
  if APasswd = UserPassword then  
    WriteLn('<H1>Успешная идентификация!</H1>')  
  else  
    WriteLn('<H1>Пароль введен неверно!</H1>')  
  WriteLn('</BODY>');  
  WriteLn('</HTML>');  
    
end;  
===cut==

Ну, вот пока что все. Но это совсем не значит, что мы полностью охватили всё, что касается разработки CGI приложений на Дельфи. В самое ближайшее время я обязательно продолжу это повествование, ведь впереди еще столько интересной и полезной информации. Так что, до скорого!

Автор: Сергей Васильченко

Добавлено: 31 Июля 2018 06:58:48 Добавил: Андрей Ковальчук