Як не дивно, але SOAP (S imple O bject A ccess P rotocol. Крос-платформна. Крос-мовна технологія запуску об'єктів) - це дійсно просто, хоча коли я тільки починав з ним працювати на Delphi. ніяк не міг зрозуміти з якого боку до нього підступитися. Насправді, при проектуванні SOAP додатків необхідно виконувати зовсім небагато умов, після чого все буде прекрасно працювати, і ці прості умови я і постараюся тут розглянути.
Перш за все, а навіщо потрібен цей самий SOAP? Основних плюса два: SOAP є public стандартом межпрограммного взаємодії, і клієнтові не треба нічого знати про сервер - ні про його мовою, ні про платформу; SOAP інтерфейси є самодокументірующіміся, тобто сервер зобов'язаний надати клієнту докладний опис інтерфейсу, його функцій, вхідних і вихідних параметрів.
Основна умова при програмуванні SOAP: сервер повинен бути stateless. тобто результат виконання запиту не повинен залежати від попередніх команд, отриманих сервером. Це означає, що всі параметри сесії повинні зберігатися на клієнті і передаватися сервера в складі запиту (якщо необхідно). Цим забезпечується висока стійкість і масштабованість системи (клієнт може бути переключено на інший сервер, навіть не підозрюючи про це), хоча ряд смакоти звичайної двухзвенкі стає недоступним:
- не можна явно управляти транзакціями з клієнта (цим займається сервер),
- відповідно не можна заблокувати запис на час редагування (хіба що зробити спеціальні поля-прапори в БД),
- не можна однією командою передати параметри, а інший вважати результат - все повинно відбуватися в рамках однієї команди
- не можна працювати з класичною зв'язкою майстер-деталь (проте TClientDataset надає для цього чудовий засіб: nested datasets - вкладені таблиці),
- не можна використовувати властивість ClientDataSet.PacketRecords> 0, тому що сервер "не пам'ятає", що він уже передав на клієнта, а що - ні, подібну функціональність доводиться реалізовувати за допомогою додаткових параметрів запиту,
- . якщо чого забув - допишу пізніше
Просте SOAP-додаток
Подібні приклади розглянуті в будь-якій літературі, присвяченій розробці SOAP на Delphi.
Запустіть Delphi і виберіть в меню File | New | Other. . перейдіть на закладку WebServices і виберіть SoapServer 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), опис даних і інтерфейсів, що підтримуються Вашим модулем.
Збережіть створене додаток, це буде скелет нашого сервера.
Дивина: пізніше я помітив, що WebModule. створюваний для WebAppDebugger. трохи відрізняється від інших варіантів додатків рядками
в чому тут справа я не розбирався, але без них пріложніе під WebAppDebugger-му не працює.
Займемося наповненням його логікою. Оскільки і серверу та клієнту потрібні опису структур даних, що передаються і інтерфейсів, то краще їх винести в окремий модуль, а всю серверну реалізацію - в інший. Для цього створіть два модуля (File | New | Unit) і збережіть один з них під ім'ям CentimeterInchIntf.pas. а інший - CentimeterInchImpl.pas. Усередині CentimeterInchIntf.pas наберіть наступне:
Таким чином ми визначили інтерфейс ICmInch. надає дві функції: перетворення сантиметрів у дюйми і дюймів в сантиметри, і зареєстрували його в InvokeRegistry.
Розберемося з реалізацією. У CentimeterInchImpl.pas визначимо нащадка TInvokableClass. реалізує наш інтерфейс ICmInch.
Як бачите, в TCmInch ми реалізували обидві функції інтерфейсу ICmInch. і також зареєстрували наш новий invokable class в InvokeRegistry (взагалі, все що буде передаватися по мережі треба в ньому реєструвати, за винятком скалярних типів).
Створіть новий додаток (звичайного типу), вкажіть в секції 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 і виберіть SoapServer Data Module. Подальше не відрізняється від розробки звичайного MIDAS додатки, з двома особливостями: сервер зобов'язаний бути stateless - отримав запит, відповів і забув (наприклад, CGI модуль в буквальному сенсі завершується після кожного виклику), і мати не більше одного SoapDataModule.
Помістіть на отриманий модуль компоненти доступу до даних (наприклад, TClientDataset), встановіть у них всі необхідні для роботи властивості. Помістіть TDataSetProvider. з'єднайте його з компонентом доступу до даних.
Скомпілліруйте додаток і покладіть його туди, де воно може бути запущено Web-сервером (чомусь мені не вдалося запустити його під WebAppDebugger. Ймовірно я використав не той WebModule. См. Зауваження вище).
У клієнтському додатку помістіть на форму TSoapConnection і TClientDataset. в SoapConnection.URL вкажіть шлях до інтерфейсу вашого сервера:
можна використовувати конкретний інтерфейс SoapDataModule. а можна і більше загальний - IAppServer. У TClientDataset.RemoteServer вкажіть на TSoapConnection. Тепер, поставивши TClientDataset.Active:=true. отримаємо наші дані на клієнта.
Якщо для відриття датасета на сервері йому потрібні якісь параметри, то зручно буде замість установки Active: = true використовувати запит DataRequest (нагадаю, що для SOAP додатків всі параметри повинні передаватися в складі єдиного запиту, тобто не можна спочатку встановити параметри, а потім зробити запит, тому що з великою ймовірністю другий запит піде до іншого екземпляра сервера). Це виглядає так.
На клієнті.
тобто сервера можна передати будь-які дані (передається тип - OleVariant) і отримати назад дані знову ж будь-якого типу, наприклад datapacket. як у наведеному випадку.
Якщо ви змінили дані на клієнта і хочете їх зберегти на сервер, то є кілька способів це зробити. Найпростіший - встановити TDataSetProvider.ResolveToDataSet: = false і викликати у TClientDataset метод ApplyUpdates. Запити поновлення нехай формує сам TDataSetProvider. а контроль (досить слабкий, проте) за формуванням цих запитів можна здійснювати за допомогою властивостей TField.ProviderFlags.
Інший спосіб: встановити TDataSetProvider.ResolveToDataSet: = true. проте в цьому випадку в подію TDataSetProvider.OnBeforeApplyUpdates доведеться відкрити пов'язаний датасета. так щоб в нього була завантажена та запис, яку збираєтеся змінювати. Зате тепер можна задіяти методи датасета BeforeInsert-BeforePost.
І останній варіант: скористатися своїм власним методом, доданим до інтерфейсу, як це раніше було зроблено з Cm2Inch, і передавати йому ClientDataset.Delta. або набір інструкцій для поновлення, або те що підказує Ваша фантазія розробника. Наприклад.
В даному прикладі метод SaveChanges приймає датапакет типу Delta і повертає таблицю помилок, що виникли при оновленні.
Для цікавих: формат переданого пакета даних описаний на community.borland.com. проте в реальності він передається у вигляді бінарного (base64Binary) пакета в тому ж форматі, що і файл (*. cds), опису цього формату мені знайти не вдалося.
Щоб подивитися, як реально виглядають пакети, що передаються по мережі, можна скористатися програмою tcpTrace. або балкою WebAppDebugger-а.
Робота з майстер-деталь
Тут теж все просто, але - дещо незвично, оскільки деталь повинна передаватися як вкладений в майстер-таблицю датасета. оскільки звичайна майстер-деталь зв'язка для TClientDataSet неможлива. Робиться це так.
У Soap Server Data Module поміщаються датасета для майстер таблиці і для деталі і, як зазвичай, зв'язуються через TDataSource. Туди ж можна відслідковувати один TDatasetProvider. який зв'язується з майстер-таблицею. Сервер компілюється і кладеться туди, де може бути запущений Web-сервером.
У другого TClientDataSet (це буде наша деталь) встановіть єдине властивість: DataSetField (виберіть зі списку назву для TDataSetField першого датасета). Тепер, якщо з'єднати наші TClientDataSet-и з грід. то ми побачимо наші дані - окремо майстер таблицю і деталь.
Є альтернативний варіант: майстер і деталь передаються окремими датасета. при цьому деталь завантажується цілком. а для вибору набору записів деталі, що відповідають конкретній рядку майстра, використовується фільтрація на клієнті, однак тут будуть проблеми з синхронним оновленням майстра і деталі.
Перехід до іншого типу додатків
Якщо ви вже награлися з WebAppDebugger-му і CGI, то можете замахнутися на "так би мовити, Шекспіра", модуль ISAPI / NSAPI.
Взагалі, перейти від одного виду додатки до іншого (скажімо, від WebAppDebugger до CGI) вкрай просто. Створюємо новий додаток потрібного нам типу, додаємо в нього всі наші модулі і новий додаток працює! Якщо ви звичайно не поміщали ніякого коду в автоматично генеруються модулі, чого особисто я не рекомендую робити.
Ще слід пам'ятати про згадуваних раніше відмінностях WebModule для WebAppDebugger і CGI, а також про різницю в роботі CGI і ISAPI додатків: в першому випадку створюється по екземпляру програми на коннект (тобто з кожним додатком працює один користувач), у другому - додаток одне, але на кожен коннект всередині програми створюється окремий потік, що вимагає обережного поводження з загальними даними, а в іншому - ніяких проблем.
Примітка: SOAP сервера, скомпільовані на Delphi 7 (і, ймовірно, вище) чутливих до Locale сервера, на якому вони працюють - саме спираючись на неї вони перетворять українські літери з Win1251 в UTF8. Тобто в національних налаштуваннях операційної системи сервера необхідно виставити країну "Росія", інакше замість російських букв отримаєте крякозюбри.
або додайте в код ініціалізації будь-якого модуля рядок
що змусить ваш модуль використовувати саме російську кодування за замовчуванням.
В якості додаткової літератури раджу подивитися:
- BizSnap chapter of Kylix Developer's Guide (особливо частини 4-6)
- InterBase in a Multi-tier World
- Проектування ISAPI додатків для роботи з базами даних
- . і звичайно ж - RTFM. правда з пошуком в цих розділах чомусь великі проблеми, але інформація в Хелп є і досить докладна,
- а також - ті демо-додатки. які йдуть з Delphi
Побажання з приводу розвитку даної статті вітаються на. [email protected]