Soap - це просто

SOAP - це просто

Як не дивно, але SOAP (S imple O bject A ccess P rotocol, крос-платформна, крос-мовна технологія запуску об'єктів) - це дійсно просто, хоча коли я тільки починав з ним працювати на Delphi, ніяк не міг зрозуміти з якого боку до нього підступитися. Насправді, при проектуванні SOAP додатків необхідно виконувати зовсім небагато умов, після чого все буде прекрасно працювати, і ці прості умови я і постараюся тут розглянути.







Основна умова при програмуванні SOAP: сервер повинен бути stateless. тобто результат виконання запиту не повинен залежати від попередніх команд, отриманих сервером. Це означає, що всі параметри сесії повинні зберігатися на клієнті і передаватися сервера в складі запиту (якщо необхідно). Цим забезпечується висока стійкість і масштабованість системи, хоча ряд смакоти звичайної двухзвенкі стає недоступним:
  • не можна явно управляти транзакціями з клієнта (цим займається сервер),
  • відповідно не можна заблокувати запис на час редагування (хіба що зробити спеціальні поля-прапори в БД),
  • не можна однією командою передати параметри, а інший вважати результат - все повинно відбуватися в рамках однієї команди
  • не можна працювати з класичною зв'язкою майстер-деталь (проте TClientDataset надає для цього чудовий засіб: nested datasets - вкладені таблиці),
  • не можна використовувати властивість ClientDataSet.PacketRecords> 0, тому що сервер "не пам'ятає", що він уже передав на клієнта, а що - ні, подібну функціональність доводиться реалізовувати за допомогою додаткових параметрів запиту,
  • . якщо чого забув - допишу пізніше

Просте SOAP-додаток

Подібні приклади розглянуті в будь-якій літературі, присвяченій розробці SOAP на Delphi.
Запустіть Delphi і виберіть в меню File | New | Other. . перейдіть на закладку WebServices і виберіть Soap Server Application.

Вам буде запропоновано на вибір 5 варіантів:
  • ISAPI / NSAPI Dinamic Link Libarry - бібліотека для серверів IIS / Netscape, кожен запит передається як структура і обробляється окремим тред,
  • CGI Stand-alone Executable - консольний додаток, отримує запит на стандартний вхід, повертає відповідь на стандартний вихід, кожен запит обробляється окремим екземпляром програми,
  • Win-CGI Stand-alone Executable - додаток Windows, обмін даними відбувається через INI-файл (не рекомендується до використання, як застаріле),
  • Apache Shared Module (DLL) - бібліотека для сервера Apache, кожен запит передається як структура і обробляється окремим тред,
  • WebAppDebugger Executable - бібліотека для налагоджувального сервера, що поставляється в складі Delphi, оскільки WebAppDebugger є також COM сервером, необхідно вказати (довільне) CoClass Name для COM об'єкта, за допомогою якого буде викликатися ваш веб-модуль.

Виберіть CGI Stand-alone Executable, як найбільш простий для налагодження формат, потім додаток можна буде легко перетворити в будь-який інший. Вся хитрість в тому, що якщо вся логіка додатка зосереджена в написаних Вами модулях, то Ви просто створюєте новий додаток потрібного типу, підключаєте до німу свої модулі, і воно працює!

Після того, як Ви натиснете ОК, буде згенеровано новий додаток, що містить WebModule з трьома компонентами:

  • THTTPSoapDispatcher - отримує входять SOAP пакети і передає їх компоненту, визначеному його Dispatcher property (зазвичай THTTPSoapPascalInvoker),
  • THTTPSoapPascalInvoker - отримує вхідний SOAP запит, знаходить в Invocation Registry викликається метод, виконує (invokes) його, формує відповідь і передає його обрабно THTTPSoapDispatcher,
  • TWSDLHTMLPublish - формує WSDL (W eb S ervices D escription L anguage), опис даних і інтерфейсів, що підтримуються Вашим модулем.

Збережіть створене додаток, це буде скелет нашого сервера.

Займемося наповненням його логікою. Оскільки і серверу та клієнту потрібні опису структур даних, що передаються і інтерфейсів, то краще їх винести в окремий модуль, а всю серверну реалізацію - в інший. Для цього створіть два модуля (File | New | Unit) і збережіть один з них під ім'ям CentimeterInchIntf.pas. а інший - CentimeterInchImpl.pas. Усередині CentimeterInchIntf.pas наберіть наступне:

Таким чином ми визначили інтерфейс ICmInch, що надає дві функції: перетворення сантиметрів у дюйми і дюймів в сантиметри, і зареєстрували його в InvokeRegistry.

Розберемося з реалізацією. У CentimeterInchImpl.pas визначимо нащадка TInvokableClass, що реалізує наш інтерфейс ICmInch:







Як бачите, в TCmInch ми реалізували обидві функції інтерфейсу ICmInch, і також зареєстрували наш новий invokable class в InvokeRegistry (взагалі, все що буде передаватися по мережі треба в ньому реєструвати, за винятком скалярних типів).

WebService Listing

Якщо натиснути на посилання WSDL for ICmInch. то ми отримаємо повне WSDL опис нашого інтерфейсу.

Створіть новий додаток (звичайного типу), вкажіть в секції uses наш інтерфейсний модуль CentimeterInchIntf. помістіть на головну форму дві кнопки, два поля введення і компонент THTTPRIO з палітри WebServices.

Q: А що робити, якщо SOAP сервер написаний кимось іншим і у нас немає інтерфейсного модуля?
A: Тоді треба скористатися Web Service Importer, який знаходиться в меню File | New | Other. . на закладці WebServices. Цей майстер згенерує інтерфейсний модуль по WSDL сервісу.

Передача складних типів

Наш компонент THTTPSOAPPascalInvoker вже знає як пересилати скалярні типи і динамічні масиви (останні треба попередньо зареєструвати в InvokeRegistry, див. Нижче), однак для пересилання складних типів, таких як static array, interface, record, set або class, необхідно спочатку описати їх як нащадків класу TRemotable. володіє RunTime Type Information (RTTI). Наприклад, якщо ми хочемо оголосити клас, який повертає курс валюти і її найменування, то наш інтерфейсний модуль буде виглядати так:

Тут ми додатково оголосили динамічний масив TCurrencyArray, на випадок якщо захочемо його передавати (зверніть увагу на відмінність в командах реєстрації класу і масиву).
Насправді повний синтаксис команди реєстрації класу дещо ширше, бажаючі можуть прочитати про нього в документації до Delphi:

Зауваження: якщо є тип, який в WSDL документі є скалярним, але не має прямого відповідності в Object Pascal (наприклад, DateTime) то в якості базового класу слід використовувати TRemotableXS, який оголошує два методу XSToNative і NativeToXS для перетворення строкового подання в Object Pascal і назад (ці методи треба, природно, реалізувати).
У складі Delphi поставляється модуль XSBuiltIns, в якому вже реалізовано багато корисних функцій (проте у версії 6.0 там були помилки в обробці дати, якщо національні настройки в системі були англійські).

Виникає цікаве питання зі створенням-знищенням об'єктів, переданих в якості параметрів. Ось що про це йдеться в документації до TRemotable:
"З боку сервера нащадки TRemotable, що є вхідними параметрами, автоматично створюються при розпакуванні (unmarshal) виклику методу, і автоматично знищуються після упаковки (marshal) вихідних параметрів для передачі клієнту.
Нащадки TRemotable, створені всередині методу, викликаного через invokable interface, автоматично знищуються після того як їх значення упаковано (marshal) для передачі клієнту.
Клієнт, що викликає invokable interface, відповідає за створення об'єктів, які використовуються як вхідні параметри і за знищення всіх нащадків TRemotable, які він створив, а також отриманих в результаті виклику методу. "

Передача Dataset-а

Тут все зовсім просто. Перебуваючи в проекті Soap Server Application, виберіть у меню File | New | Other. . перейдіть на закладку WebServices і виберіть Soap Server Data Module. Подальше не відрізняється від розробки звичайного MIDAS додатки, з двома особливостями: сервер зобов'язаний бути stateless - отримав запит, відповів і забув (наприклад, CGI модуль в буквальному сенсі завершується після кожного виклику), і мати не більше одного SoapDataModule.
Помістіть на отриманий модуль компоненти доступу до даних (наприклад, TClientDataset), встановіть у них всі необхідні для роботи властивості. Помістіть TDataSetProvider, з'єднайте його з компонентом доступу до даних.

Скомпілліруйте додаток і покладіть його туди, де воно може бути запущено Web-сервером (чомусь мені не вдалося запустити його під WebAppDebugger).

У клієнтському додатку помістіть на форму TSoapConnection і TClientDataset, в SoapConnection.URL вкажіть шлях до інтерфейсу вашого сервера: можна використовувати конкретний інтерфейс SoapDataModule, а можна і більше загальний - IAppServer. У TClientDataset.RemoteServer вкажіть на TSoapConnection. Тепер, поставивши TClientDataset.Active:=true, отримаємо наші дані на клієнта.

Якщо для відриття датасета на сервері йому потрібні якісь параметри, то зручно буде замість установки Active: = true використовувати запит DataRequest. Це виглядає так.
На клієнті: На сервері:

Якщо ви змінили дані на клієнта і хочете їх зберегти на сервер - викличте у TClientDataset метод ApplyUpdates, тільки не встановлюйте у TDataSetProvider.ResolveToDataSet = true. Запити поновлення нехай формує сам TDataSetProvider, а контроль за формуванням цих запитів можна здійснювати за допомогою властивостей TField.ProviderFlags.

Для цікавих: формат переданого пакета даних описаний на community.borland.com. проте в реальності він передається у вигляді бінарного (base64Binary) пакета в тому ж форматі, що і файл (* .cds), опису цього формату мені знайти не вдалося.
Щоб подивитися, як реально виглядають пакети, що передаються по мережі, можна скористатися програмою tcpTrace. або балкою WebAppDebugger-а.

Робота з майстер-деталь

Тут теж все просто, але - дещо незвично, оскільки деталь повинна передаватися як вкладений в майстер-таблицю датасета, оскільки звичайна майстер-деталь зв'язка для TClientDataSet неможлива. Робиться це так.

У Soap Server Data Module поміщаються датасета для майстер таблиці і для деталі і, як зазвичай, зв'язуються через TDataSource. Туди ж можна відслідковувати один TDatasetProvider, який зв'язується з майстер-таблицею. Сервер компілюється і кладеться туди, де може бути запущений Web-сервером.

У другого TClientDataSet (це буде наша деталь) встановіть єдине властивість: DataSetField (виберіть зі списку назву для TDataSetField першого датасета). Тепер, якщо з'єднати наші TClientDataSet-и з грід, то ми побачимо наші дані - окремо майстер таблицю і деталь.

Є альтернативний варіант: майстер і деталь передаються окремими датасета, при цьому деталь завантажується цілком. а для вибору набору записів деталі, що відповідають конкретній рядку майстра, використовується фільтрація на клієнті, однак тут будуть проблеми з синхронним оновленням майстра і деталі.

В якості додаткової літератури раджу подивитися:
  1. BizSnap chapter of Kylix Developer's Guide (особливо частини 4-6)
  2. InterBase in a Multi-tier World
  3. Проектування ISAPI додатків для роботи з базами даних
  4. . і звичайно ж - RTFM. правда з пошуком в цих розділах чомусь великі проблеми, але інформація в Хелп є і досить докладна,
  5. а також - ті демо-додатки, які йдуть з Delphi

Побажання з приводу розвитку даної статті вітаються на: [email protected]







Схожі статті