WebServices: от книжной теории к реальной практике

© 2002 Андрей Финк

В сети можно найти огромное количество материалов, в том числе и на русском языке посвященных созданию ВебСервисов. Поэтому мы не будем углубляется в теорию их создания, функционировании и историю появления. Для практического применения достаточно представить себе ВебСервис как некий "объект", который может располагаться в открытом Интернете, позволять работать с собой по протоколу HTTP (таким образом, беспрепятственно работать через HTTP Proxy и не встречать возражения Админов) и реализовывать некоторые интерфейсы. Причем в отличие от COM объекта, он не зависит от фантазий Билла Гейтса, и клиенты его использующие могут работать под управлением любой операционной системы.

В данной статье я хочу показать, на примере простейшего ВебСервиса, как с минимальными усилиями сделать его CGI, ISAPI и standalone версии (с поддержкой SSL), а также как использовать его из языка не поддерживающего данную технологию.

К сожалению, как и все новые технологии, реализация ВебСервисов в Delphi содержит некоторое количество ошибок. Я нашел одну, и чтобы вам не повторять мои поиски, рекомендую исправить метод: function TWSDLHTMLPublish.GetHostScriptBaseURL(Request: TWebRequest): String; расположенный в файле Source\Internet\WSDLPub.pas следующим образом:

function TWSDLHTMLPublish.GetHostScriptBaseURL(Request: TWebRequest): String;
const
  SHttps = 'https://';
  SHttp = 'http://';
begin
  { Here we automatically detect the default HTTPS port [sure hope no one dares to use 443 for non SSL purposes] or if user configured publisher to publish service location as SSL }
  if Request.ServerPort = 443 then
    Result := SHttps + Request.Host + Request.InternalScriptName { do not localize }
  else if poPublishLocationAsSecure in PublishOptions then
    Result := SHttps + Request.Host + ':' + IntToStr(Request.ServerPort) + Request.InternalScriptName { do not localize }
  else if (Request.ServerPort <> 80) and (Pos(':', Request.Host) = 0) then
    Result := SHttp + Request.Host + ':' + IntToStr(Request.ServerPort) + Request.InternalScriptName { do not localize }
  else
    Result := SHttp + Request.Host + Request.InternalScriptName; { do not localize }
end; //GetHostScriptBaseURL
Без этого исправления, вы не сможете заставить работать свой сервис через HTTPS соединение, на нестандартном (отличном от 443) порту.

Создание ВебСервиса в Delphi6/7 наглядный пример того, что RAD это не обязательно кликанье мышкой ;-)

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

На текущий момент мы имеет свой собственный ВебСервис, который реализует пустой интерфейс и представляет из себя CGI приложение.

CGI это пионер Интернет технологий, помимо того огромного плюса, что его поддерживают практически все ВебСервера, у него есть огромный минус. Для каждого запроса он загружается в оперативную память и отработав выгружается. Поскольку размер "exe" созданных Delphi оставляет желать лучшего, CGI следует использовать только для редко используемых сервисов или если ваш сервер не поддерживает ничего другого.

Гораздо эффективнее использовать ISAPI DLL, которая загружается всего один раз при первом запросе и дальнейшие запросы идут уже к этому экземпляру.

Чтобы за пару кликов мышки превратить наш ВебСервис в ISAPI DLL нужно сделать следующее:

К сожалению созданный мастером файл проекта (и в D6 и в D7) содержит ошибки. Долгое копание в конференциях дало следующее решение.

Следует привести DPR файл к следующему виду:

library <название вашего проекта для ISAPI DLL>;

uses
  ActiveX,
  ComObj,
  WebBroker,
  ISAPIThreadPool,
{<== здесь идет список ваших модулей (веб-модуль описание интерфейса, реализация интерфейса)}   WMain in '..\WMain.pas' {WebMain: TWebModule},
  IPaymentIntf in '..\IPaymentIntf.pas',
  IPaymentImpl in '..\IPaymentImpl.pas'
{===>};

{$R *.res}

exports
  GetExtensionVersion,
  HttpExtensionProc,
  TerminateExtension;

begin
  CoInitFlags := COINIT_MULTITHREADED;
  Application.Initialize;
  Application.CreateForm(TWebMain, WebMain);
  Application.Run;
end.
Осталось только сохранить этот DPR файл в отдельной папке и ВебСервис в виде ISAPI DLL готов.

Если у вас нет Веб сервера, а также для целей удобного тестирования можно создать автономный ВебСервис, то есть созданное вами приложение будет выступать в роли Веб Сервера. Для этого мы используем бесплатную библиотеку Интернет-компонент Indy версии 9, которые поставляются в составе Delphi7, пользователи остальных версий Delphi/Kylix/BCB могут скачать её с сайта http://www.nevrona.com/indy, для поддержки SSL в данном случае потребуется бесплатная библиотека OpenSSL для Indy, которую можно взять с сайта http://www.intelicom.si, для создания сервера с поддержкой SSL также потребуются SSL сертификаты, как их создавать это тема отдельной статьи, поэтому мы будем пользоваться сертификатами из демок к Indy (поскольку их не так то просто найти - они прилагаются к этой статье ;-)

Для создания самостоятельного сервиса выбираем File|New|Other|Console Application

{$APPTYPE CONSOLE}
program FlashPayment;
uses
  SysUtils,
  WebBroker,
  WMain in '..\WMain.pas' {WebMain: TWebModule}, //здесь должен быть ваш Веб модуль
  IPaymentIntf in '..\IPaymentIntf.pas', //описание интерфейсов
  IPaymentImpl in '..\IPaymentImpl.pas', //реализация
  IdHTTPWebBrokerBridge in '..\..\..\..\..\..\D7\PAS\Indy\IdHTTPWebBrokerBridge.pas',
{ ^^^ этот модуль не устанавливается вместе с Indy, он лежит в папке с исходниками Indy }
  IdSSLOpenSSL,
  IdGlobal,
  WSDLPub in '..\..\..\..\..\..\d7\delphi\source\Internet\WSDLPub.pas';
{ ^^^ чтобы использовался исправленный нами WSDLPub вместо lib\WSDLPub.dcu }

{$R *.res}
const
  PortNumber = 8881; //порт на котором будет слушать сервис. 80 и 443 у меня уже заняты IISом
  //опции командной строки ;-)
  SUseSSL = 'SSL';
  SLogSSL = 'LOGSSL';

var
  GWebBrokerBridge: TIdHTTPWebBrokerBridge; //собственно сервер, наследник IdHTTPServer
  GSrvIoHandlerSSL: TIdServerIOHandlerSSL; //поддержка SSL

//информационное событие SSL IoHandler'а - интересно наблюдать как работает SSL ;-)
procedure objInfoCallback(SELF: Pointer; const AMsg: String);
Begin
  WriteLn(AMsg);
End; //

//приватный ключ сертификата обычно шифруется. это событие вызывается чтобы мы указали пароль
procedure objGetKeyPassword(SELF: Pointer; var VPassword: String);
Begin
{ this is a password for unlocking the server key. If you have your own key, then it would probably be different}
  VPassword := 'aaaa';
End; //

//создать все объекты и запустить сервер
procedure StartSoapServer(ALogSSL: Boolean = FALSE);
var
  LAppPath: String;
Begin
  GWebBrokerBridge := TIdHTTPWebBrokerBridge.Create(NIL);
  with GWebBrokerBridge do begin
    DefaultPort := PortNumber; //на каком порту слушать
    RegisterWebModuleClass(TWebMain); //какая форма будет создаваться при обращении пользователя
    Active := TRUE; //запуск!
  end; //with

  if GUseSSL then begin
    GSrvIoHandlerSSL := TIdServerIOHandlerSSL.Create(GWebBrokerBridge);
    with GSrvIoHandlerSSL.SSLOptions do begin
      Method := sslvSSLv23;
      LAppPath := ExtractFilePath(ParamStr(0)); //будем искать сертификаты в папке EXE\Cert
      RootCertFile := LAppPath + 'cert\CAcert.pem'; //сертификат выданный нам центром сертификации
      CertFile := LAppPath + 'cert\WSScert.pem'; //наш публичный ключ
      KeyFile := LAppPath + 'cert\WSSkey.pem'; //наш приватный ключ
    end; //with
    //далее идет "хакерский" трюк. так как у нас нет формы, роль событий выполняют обычные процедуры
    GSrvIoHandlerSSL.OnGetPassword := TPasswordEvent(MakeMethod(NIL, @objGetKeyPassword));
    GWebBrokerBridge.IOHandler := GSrvIoHandlerSSL;
    if ALogSSL then begin
      GSrvIoHandlerSSL.OnStatusInfo := TCallbackEvent(MakeMethod(NIL, @objInfoCallback));
    end; //if LogSSL
  end; //if UseSSL
End; //StartSoapServer

procedure StopSoapServer;
Begin
  GWebBrokerBridge.Active := FALSE; //стой раз-два
  FreeAndNil(GWebBrokerBridge);
End; //StopSoapServer

Begin
  WriteLn('Standalone WebService Server v0.9 Copyright (c) 2002 Andrew Fink');
  WriteLn('Use -SSL and -LOGSSL switches for SSL support and log');
  WriteLn('Starting WebService. Port: ',PortNumber);
  GUseSSL := FindCmdLineSwitch(SUseSSL);
  if GUseSSL then begin
    WriteLn('SSL Support Enabled');
  end; //if
  StartSoapServer(FindCmdLineSwitch(SLogSSL));
  WriteLn('Press Enter to Halt');
  ReadLn;
  StopSoapServer;
END.

Кроме того придется внести небольшие изменения в Веб-модуль:

Теперь вы можете сосредоточится на функциональности вашего сервиса, не беспокоясь о мелких бытовых неурядицах. Запускайте наслаждайтесь! ;-)

Только не забудьте указать порт в пути и префикс https, если вы используете опцию -SSL.

Работа с ВебСервисами из языков не поддерживающих данную технологию.

Если необходимо обратится к ВебСервису из языка не поддерживающего данную технологию, но поддерживающий работу с COM объектами, то в некоторых случаях можно сделать следующее.

Если это язык группы C/C++ или Delphi/PASCAL, то можно воспользоваться утилитой Delphi\Bin\WSDLImp.exe для генерации описания интерфейсов по WSDL описанию. В противном случае придется писать этот код вручную.

В D6/7 создается "COM-прокладка" между ВебСервисом и клиентом. Для этого:

Теперь этот COM объект можно создать в любом языке и взять у него интерфейс ВебСервиса.

Исходники и SSL-сертификаты (18К).

PS: Надиюсь фам панравилазь. Извинити за токой руский зяыг.


Copyright© 2002 Андрей Финк  Специально для Delphi Plus