© 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;Без этого исправления, вы не сможете заставить работать свой сервис через HTTPS соединение, на нестандартном (отличном от 443) порту.
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
Создание ВебСервиса в Delphi6/7 наглядный пример того, что RAD это не обязательно кликанье мышкой ;-)

Большая часть работы сделана ;-). Сохраните модули с описанием интерфейса, реализации интерфейса и веб-модулем в одной папке, а проект в другой (можно дать ей многозначительное название CGI).
На текущий момент мы имеет свой собственный ВебСервис, который реализует пустой интерфейс и представляет из себя CGI приложение.
CGI это пионер Интернет технологий, помимо того огромного плюса, что его поддерживают практически все ВебСервера, у него есть огромный минус. Для каждого запроса он загружается в оперативную память и отработав выгружается. Поскольку размер "exe" созданных Delphi оставляет желать лучшего, CGI следует использовать только для редко используемых сервисов или если ваш сервер не поддерживает ничего другого.
Гораздо эффективнее использовать ISAPI DLL, которая загружается всего один раз при первом запросе и дальнейшие запросы идут уже к этому экземпляру.
Чтобы за пару кликов мышки превратить наш ВебСервис в ISAPI DLL нужно сделать следующее:
К сожалению созданный мастером файл проекта (и в D6 и в D7) содержит ошибки. Долгое копание в конференциях дало следующее решение.
Следует привести DPR файл к следующему виду:
library <название вашего проекта для ISAPI DLL>;Осталось только сохранить этот DPR файл в отдельной папке и ВебСервис в виде 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.
Если у вас нет Веб сервера, а также для целей удобного тестирования можно создать автономный ВебСервис, то есть созданное вами приложение будет выступать в роли Веб Сервера. Для этого мы используем бесплатную библиотеку Интернет-компонент 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.
Кроме того придется внести небольшие изменения в Веб-модуль:
constructor TWebMain.Create(AOwner: TComponent);
Begin
inherited Create(AOwner);
if GUseSSL then begin
WSDLHTMLPublish.PublishOptions := WSDLHTMLPublish.PublishOptions + [poPublishLocationAsSecure];
end else begin
WSDLHTMLPublish.PublishOptions := WSDLHTMLPublish.PublishOptions - [poPublishLocationAsSecure];
end; //if
End; //
procedure TWebMain.WebModule1DefaultHandlerAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Begin
ActiveX.CoInitialize(NIL); //иначе некорректно работают методы использующие COM
try
WSDLHTMLPublish.ServiceInfo(Sender, Request, Response, Handled); //это создал мастер
finally
ActiveX.CoUninitialize;
end; //
End; //
Теперь вы можете сосредоточится на функциональности вашего сервиса, не беспокоясь о мелких бытовых неурядицах. Запускайте наслаждайтесь! ;-)
Только не забудьте указать порт в пути и префикс https, если вы используете опцию -SSL.
Если необходимо обратится к ВебСервису из языка не поддерживающего данную технологию, но поддерживающий работу с COM объектами, то в некоторых случаях можно сделать следующее.
Если это язык группы C/C++ или Delphi/PASCAL, то можно воспользоваться утилитой Delphi\Bin\WSDLImp.exe для генерации описания интерфейсов по WSDL описанию. В противном случае придется писать этот код вручную.
В D6/7 создается "COM-прокладка" между ВебСервисом и клиентом. Для этого:


…
type
TSetupProxy = class(TTypedComObject, ISetupProxy, IIPayment{интерфес поддерживаемый ВебСервисом})
protected
FPath: WideString;
FWebService: IIPayment; {интерфес поддерживаемый ВебСервисом}
procedure SetPath(const APath: WideString); stdcall;
public
property WebService: IIPayment read FWebService implements IIPayment;
//^^^ наш COM объект имплементирует интерфейс ВебСервиса
End;
implementation
uses
ComServ, SoapHTTPClient {THTTPRIO};
{ TSetupProxy }
procedure TSetupProxy.SetPath(const APath: WideString);
var
RIO: THTTPRIO;
Begin
FPath := APath;
RIO := THTTPRIO.Create(NIL);
try
RIO.URL := APath; //путь к сервису
FWebService := (RIO as IIPayment); //получаем удаленный интерфейс
finally
if (FWebService = NIL) then RIO.FREE;
end; //tryf
End; //SetPath
Теперь этот COM объект можно создать в любом языке и взять у него интерфейс ВебСервиса.
Исходники и SSL-сертификаты (18К).
PS: Надиюсь фам панравилазь. Извинити за токой руский зяыг.
Copyright© 2002 Андрей Финк Специально для Delphi Plus