В этой статье.
Небольшая схема работы веб сервисов
Практическая реализация простого приложения
Передача файла с progress bar'ом
Сервисы - это модульные приложения, которые могут быть вызваны и опубликованы в инете.
Работа с ними ведется с помощью протокола SOAP - стандарт легковесного протокола обмена информацией (simple object protocol). Это протокол использует XML для кодирования удаленных вызовов процедур и использует коммуникационный протокол HTTP.
Приложения Web-служб публикуют информацию о том, через какой интерфейс они доступны и как осуществить их вызов с помощью документов WSDL.
Посмотреть на WSDL можно http://МойСайт/Приложение/wsdl
http://127.0.0.1/MyApp.dll/wsdl
или так
http://127.0.0.1:81/SCRIPT/soado.exe/wsdl
WSDL включает
В разделе implementation содержится описание функций с именем GET<имя службы>, которая возвращает вызываемый интерфейс на клиенте, а при импорте WSDL на клиенте автоматически генерируется глобальная функция, которая возвращает интерфейс
Общий механизм работы можно описать так:
на сервере описан интерфейс с именем mSiteBSoap
mSiteBSoap = inteface(IInvokable) // вызываемый интерфейс (Invokable)
при импорте wsdl генерируется прототип:
function GetmSiteBSoap(useWSDL: boolean; // если true, то местоположение сервера определяется с помощью WSDL
Addr: string; // иначе клиент должен предоставить адрес URL
HTTPRIO)
: mSiteBSoap; // возвращает ссылку на вызываемый интерфейс
Обращение клиента
var
Port: mSiteBSoap
begin
try
Port := mSiteBSoap; // получаем ссылку на интерфейс класса
Port.Login; // вызываем метод этого класса
finally
Port := nil; // корректно удаляем ссылку
end;
end;
При использовании вызываемого интерфейса автоматически поддерживаются скалярные типы данных: boolean, char, byte, integer, widestring...
При необходимости работать с другими типами данных необходимо выполнить их регистрацию с помощью реестра типов.
Модули сервера
THTTPSoapDispatcher - диспетчер, получает SOAP пакеты и передает их компоненту, определенному в его свойстве Dispatcher
(обычно THTTPSoapPascalInvoker)
THTTPSoapPascalInvoker - получает SOAP запрос, находит в реестре (Invocation Registry) вызываемый метод, и выполняет его вызов (invokes)
формирует ответ и передает его обратно диспетчеру THTTPSoapDispatcher
Дополнительно
SOAPAttachment, позволяет обмениваться бинарными данными (binary data)
TSOAPAttachment = class(TRemotable) записывает attachment во временный файл (permanent file) или поток (stream)
Header. Заголовки
сами данные SOAP находятся в теге <SOAP:body>
SOAP:Header тэг позволяет посылать дополнительную информацию, например, куки
Создаем сервер. New - SOAP Server Application - Service Name даем ему имя MulValues
Доп.
свойства WSDLHTMLPublish
PublishOption PoDefault = true AdminEnable = true
В этом случае при запросе wsdl документа (http://127.0.0.1:81/SCRIPT/mulval.exe/wsdl) будет доступна кнопка "Administrator". С помощью встроенного в web модуль администрирования можно создавать несколько серверов физических (указать адрес - порт), которые будут предоставлять услугу. Т.о. можно распределить нагрузку между несколькими серверами.
I. Описание интерфейса. обычно файлы именуются * intf.pas
1. Пишем unit с интерфейсом IMulValues, который должен быть наследником от базового интерфейса IInvokable.
Добавить модуль данных. File - New - SoapServerDataModule
!!! Обратить внимание на
1. stateless - т.е. не хранит информацию, связанную с пользователем
2. при добавлении TDataModule из dpr убрать авто создание модуля, создавать в коде.
Клиентская часть. Обычное приложение.
1. В uses добавляем InvokeRegistry, Rio, SOAPHTTPClient.
2. Добавляем RIO: THTTPRIO; - создает сообщение формата http для вызова удаленного сервиса, для связи с удаленным сервером используется свойство url или WSDLLocation.
прим. RIO - Remote Invokable Object, т.е удаленный вызываемый объект
3. На клиенте импортируем WSDL, при этом будет сгенерирован pas файл с интерфейсами
File - New - Other вкладка WebServices WSDLImporter
Дальше надо указать url (http://127.0.0.1:81/SCRIPT/mulval.exe/wsdl/IMulValues) или WSDL файл.
Механизм доступа к интерфейсу port := RIO as Указываем интерфейс, где port наш интерфейс
Дополнительно
класс TlinkedRIO используется для отладки, методы сервера он вызывает напрямую
Небольшая схема работы веб сервисов
Практическая реализация простого приложения
Передача файла с progress bar'ом
Сервисы - это модульные приложения, которые могут быть вызваны и опубликованы в инете.
Работа с ними ведется с помощью протокола SOAP - стандарт легковесного протокола обмена информацией (simple object protocol). Это протокол использует XML для кодирования удаленных вызовов процедур и использует коммуникационный протокол HTTP.
Приложения Web-служб публикуют информацию о том, через какой интерфейс они доступны и как осуществить их вызов с помощью документов WSDL.
Посмотреть на WSDL можно http://МойСайт/Приложение/wsdl
http://127.0.0.1/MyApp.dll/wsdl
или так
http://127.0.0.1:81/SCRIPT/soado.exe/wsdl
WSDL включает
- объявления классов, прототипы функций
- объявления вызывающего интерфейса
В разделе implementation содержится описание функций с именем GET<имя службы>, которая возвращает вызываемый интерфейс на клиенте, а при импорте WSDL на клиенте автоматически генерируется глобальная функция, которая возвращает интерфейс
Общий механизм работы можно описать так:
на сервере описан интерфейс с именем mSiteBSoap
mSiteBSoap = inteface(IInvokable) // вызываемый интерфейс (Invokable)
при импорте wsdl генерируется прототип:
function GetmSiteBSoap(useWSDL: boolean; // если true, то местоположение сервера определяется с помощью WSDL
Addr: string; // иначе клиент должен предоставить адрес URL
HTTPRIO)
: mSiteBSoap; // возвращает ссылку на вызываемый интерфейс
Обращение клиента
var
Port: mSiteBSoap
begin
try
Port := mSiteBSoap; // получаем ссылку на интерфейс класса
Port.Login; // вызываем метод этого класса
finally
Port := nil; // корректно удаляем ссылку
end;
end;
При использовании вызываемого интерфейса автоматически поддерживаются скалярные типы данных: boolean, char, byte, integer, widestring...
При необходимости работать с другими типами данных необходимо выполнить их регистрацию с помощью реестра типов.
initialization
{ Invokable interfaces must be registered }
InvRegistry.RegisterInterface(TypeInfo(IMulValues));
{ Invokable interfaces must be registered }
InvRegistry.RegisterInterface(TypeInfo(IMulValues));
InvRegistry.RegisterInterface(Мой класс);
Схема
Модули сервера
THTTPSoapDispatcher - диспетчер, получает SOAP пакеты и передает их компоненту, определенному в его свойстве Dispatcher
(обычно THTTPSoapPascalInvoker)
THTTPSoapPascalInvoker - получает SOAP запрос, находит в реестре (Invocation Registry) вызываемый метод, и выполняет его вызов (invokes)
формирует ответ и передает его обратно диспетчеру THTTPSoapDispatcher
Дополнительно
SOAPAttachment, позволяет обмениваться бинарными данными (binary data)
TSOAPAttachment = class(TRemotable) записывает attachment во временный файл (permanent file) или поток (stream)
Header. Заголовки
сами данные SOAP находятся в теге <SOAP:body>
SOAP:Header тэг позволяет посылать дополнительную информацию, например, куки
Создаем сервер. New - SOAP Server Application - Service Name даем ему имя MulValues
Доп.
свойства WSDLHTMLPublish
PublishOption PoDefault = true AdminEnable = true
В этом случае при запросе wsdl документа (http://127.0.0.1:81/SCRIPT/mulval.exe/wsdl) будет доступна кнопка "Administrator". С помощью встроенного в web модуль администрирования можно создавать несколько серверов физических (указать адрес - порт), которые будут предоставлять услугу. Т.о. можно распределить нагрузку между несколькими серверами.
I. Описание интерфейса. обычно файлы именуются * intf.pas
1. Пишем unit с интерфейсом IMulValues, который должен быть наследником от базового интерфейса IInvokable.
{ Invokable interfaces must derive from IInvokable }
IMulValues = interface(IInvokable)
['{53ADBA92-C6BA-40E7-A075-D5981FCB92C1}'] // это уникальный GUID в D его можно сгенерировать ctrl+Shift+G
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
// эти методы нам любезно сгенерировала Дельфи
function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
function echoDouble(const Value: Double): Double; stdcall;
// а эти методы мы добавим ручками
function echoString(const Value: WideString): WideString; stdcall; // получить-вернуть строчку
function GetAttachmentFile(const Fname: WideString): TSoapAttachment; stdcall; // передать файл
end;
implementation
initialization
{ Invokable interfaces must be registered }
// а вот эта строка регистрирует наш интерфейс в реестре (Invocation Registry)
// это позволит нам идентифицировать его и обращаться к его методам
// т.е. теперь клиент может обращаться к интерфейсу IMulValues
InvRegistry.RegisterInterface(TypeInfo(IMulValues));
IMulValues = interface(IInvokable)
['{53ADBA92-C6BA-40E7-A075-D5981FCB92C1}'] // это уникальный GUID в D его можно сгенерировать ctrl+Shift+G
{ Methods of Invokable interface must not use the default }
{ calling convention; stdcall is recommended }
// эти методы нам любезно сгенерировала Дельфи
function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
function echoDouble(const Value: Double): Double; stdcall;
// а эти методы мы добавим ручками
function echoString(const Value: WideString): WideString; stdcall; // получить-вернуть строчку
function GetAttachmentFile(const Fname: WideString): TSoapAttachment; stdcall; // передать файл
end;
implementation
initialization
{ Invokable interfaces must be registered }
// а вот эта строка регистрирует наш интерфейс в реестре (Invocation Registry)
// это позволит нам идентифицировать его и обращаться к его методам
// т.е. теперь клиент может обращаться к интерфейсу IMulValues
InvRegistry.RegisterInterface(TypeInfo(IMulValues));
Доп. Директива компилятора {$M+} - указать в начале модуля, необходима для резервирования памяти для стека, требуемого для обращения к удаленным процедурам. (Delphi 7 Бобровский стр.582). Не пробовала.
II. Теперь приступаем к реализации (Implementation), модули именуются * impl.pas
{ TMulValues }
TMulValues = class(TInvokableClass, IMulValues)
public
function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
function echoDouble(const Value: Double): Double; stdcall;
function echoString(const Value: WideString): WideString; stdcall; // вернуть строчку
function GetAttachmentFile(const Fname: WideString): TSoapAttachment; stdcall;
end;
...
function TMulValues.echoString(const Value: WideString): WideString;
begin
Result := Value; // возвращаем строчку
end;
// возвращаем файл
function TMulValues.GetAttachmentFile(const Fname: WideString): TSoapAttachment;
var
memStr: TMemoryStream;
begin
{ Загрузить файл}
Result := TSoapAttachment.Create;
memStr := TMemoryStream.Create;
if FileExists(Fname) then
begin
memStr.LoadFromFile(Fname);
Result.SetSourceStream(memStr, soReference);
end
else
Result := nil;
end;
{ TMulValues }
TMulValues = class(TInvokableClass, IMulValues)
public
function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
function echoDouble(const Value: Double): Double; stdcall;
function echoString(const Value: WideString): WideString; stdcall; // вернуть строчку
function GetAttachmentFile(const Fname: WideString): TSoapAttachment; stdcall;
end;
...
function TMulValues.echoString(const Value: WideString): WideString;
begin
Result := Value; // возвращаем строчку
end;
// возвращаем файл
function TMulValues.GetAttachmentFile(const Fname: WideString): TSoapAttachment;
var
memStr: TMemoryStream;
begin
{ Загрузить файл}
Result := TSoapAttachment.Create;
memStr := TMemoryStream.Create;
if FileExists(Fname) then
begin
memStr.LoadFromFile(Fname);
Result.SetSourceStream(memStr, soReference);
end
else
Result := nil;
end;
Добавить модуль данных. File - New - SoapServerDataModule
!!! Обратить внимание на
1. stateless - т.е. не хранит информацию, связанную с пользователем
2. при добавлении TDataModule из dpr убрать авто создание модуля, создавать в коде.
Клиентская часть. Обычное приложение.
1. В uses добавляем InvokeRegistry, Rio, SOAPHTTPClient.
2. Добавляем RIO: THTTPRIO; - создает сообщение формата http для вызова удаленного сервиса, для связи с удаленным сервером используется свойство url или WSDLLocation.
прим. RIO - Remote Invokable Object, т.е удаленный вызываемый объект
3. На клиенте импортируем WSDL, при этом будет сгенерирован pas файл с интерфейсами
File - New - Other вкладка WebServices WSDLImporter
Дальше надо указать url (http://127.0.0.1:81/SCRIPT/mulval.exe/wsdl/IMulValues) или WSDL файл.
Механизм доступа к интерфейсу port := RIO as Указываем интерфейс, где port наш интерфейс
procedure TForm2.EchoStringClick(Sender: TObject);
var
port: IMulValues;
SendStr, ResStr: WideString;
begin
nRead := 0; // это для Progress Bar'а счетчик
// указываем url
RIO.URL := 'http://127.0.0.1:81/SCRIPT/MulVal.exe/soap/IMulValues';
port := RIO as IMulValues; // доступ к интерфейсу
SendStr := Memo1.Lines.Text; // строка, которую собираемся передать
ResStr := port.echoString(SendStr); // передаем строчку, получаем ответ
Memo2.Lines.Text := ResStr; // пишем результат в memo
end;
// можно файл, можно поток
procedure TForm2.Button2Click(Sender: TObject);
var
port: IMulValues;
sAtt: TSoapAttachment;
memStr: TMemoryStream;
AttFileName: String;
AttFileNameWithServerPath: WideString;
begin
nRead := 0;
RIO.URL := 'http://127.0.0.1:81/SCRIPT/MulVal.exe/soap/IMulValues';
port := RIO as IMulValues;
AttFileName := trim(edFileName.Text); // простой Edit в к-м задаем имя файла
AttFileNameWithServerPath := 'C:\Inetpub\TEST81\SCRIPT\' + AttFileName; //
sAtt := port.GetAttachmentFile(AttFileNameWithServerPath);
// пишем что получили в поток
try
memStr := TMemoryStream.Create;
try
sAtt.SaveToStream(memStr);
...
if memStr.Size > 0 then
begin
memStr.Position := 0;
memStr.SaveToFile(AttFileName); // пишем на диск
end
else
ShowMessage('Файл не найден');
finally
memStr.Free;
end;
finally
DeleteFile(sAtt.CacheFile);
sAtt.Free;
end;
end;
var
port: IMulValues;
SendStr, ResStr: WideString;
begin
nRead := 0; // это для Progress Bar'а счетчик
// указываем url
RIO.URL := 'http://127.0.0.1:81/SCRIPT/MulVal.exe/soap/IMulValues';
port := RIO as IMulValues; // доступ к интерфейсу
SendStr := Memo1.Lines.Text; // строка, которую собираемся передать
ResStr := port.echoString(SendStr); // передаем строчку, получаем ответ
Memo2.Lines.Text := ResStr; // пишем результат в memo
end;
// можно файл, можно поток
procedure TForm2.Button2Click(Sender: TObject);
var
port: IMulValues;
sAtt: TSoapAttachment;
memStr: TMemoryStream;
AttFileName: String;
AttFileNameWithServerPath: WideString;
begin
nRead := 0;
RIO.URL := 'http://127.0.0.1:81/SCRIPT/MulVal.exe/soap/IMulValues';
port := RIO as IMulValues;
AttFileName := trim(edFileName.Text); // простой Edit в к-м задаем имя файла
AttFileNameWithServerPath := 'C:\Inetpub\TEST81\SCRIPT\' + AttFileName; //
sAtt := port.GetAttachmentFile(AttFileNameWithServerPath);
// пишем что получили в поток
try
memStr := TMemoryStream.Create;
try
sAtt.SaveToStream(memStr);
...
if memStr.Size > 0 then
begin
memStr.Position := 0;
memStr.SaveToFile(AttFileName); // пишем на диск
end
else
ShowMessage('Файл не найден');
finally
memStr.Free;
end;
finally
DeleteFile(sAtt.CacheFile);
sAtt.Free;
end;
end;
...
//****************************************************************************
//******** события RIOHTTP **************************************************
procedure TForm2.RIOAfterExecute(const MethodName: string;
SOAPResponse: TStream);
begin
ShowMessage('Готово '); // после загрузки сообщаем, что все готово )
end;
procedure TForm2.RIOHTTPWebNode1ReceivingData(Read, Total: Integer);
var
CurrPos: integer;
begin
// показываем процесс загрузки
inc(nRead, Read);
StatusBar1.Panels[0].Text := Format('Total %d', [Total]);
ProgressBar1.Position := Trunc((Read/Total)*100);// прогресс загрузки
Application.ProcessMessages;
end;
//****************************************************************************
//******** события RIOHTTP **************************************************
procedure TForm2.RIOAfterExecute(const MethodName: string;
SOAPResponse: TStream);
begin
ShowMessage('Готово '); // после загрузки сообщаем, что все готово )
end;
procedure TForm2.RIOHTTPWebNode1ReceivingData(Read, Total: Integer);
var
CurrPos: integer;
begin
// показываем процесс загрузки
inc(nRead, Read);
StatusBar1.Panels[0].Text := Format('Total %d', [Total]);
ProgressBar1.Position := Trunc((Read/Total)*100);// прогресс загрузки
Application.ProcessMessages;
end;
Дополнительно
класс TlinkedRIO используется для отладки, методы сервера он вызывает напрямую