10. Веб-сервіси
10.1. Вступ
У попередньому розділі ми розглянули кілька клієнт-серверних додатків на базі TCP/IP. Оскільки клієнти та сервер обмінюються текстовими рядками, їх можна писати будь-якою мовою програмування. Клієнт повинен лише знати протокол взаємодії, якого очікує сервер. Веб-сервіси — це серверні додатки на базі TCP/IP, що мають такі характеристики:
- Вони розміщуються на веб-серверах, а протоколом обміну даними між клієнтом і сервером є HTTP (HyperText Transport Protocol) — протокол, що працює на рівні вище TCP-IP.
- Веб-сервіс має стандартний протокол взаємодії незалежно від типу наданої послуги. Веб-сервіс пропонує різні послуги: S1, S2, …, Sn. Кожна з них очікує на параметри, надані клієнтом, і повертає йому результат. Для кожної послуги клієнт повинен знати:
- точну назву сервісу, якщо
- перелік параметрів, які потрібно надати, та їхній тип
- тип результату, що повертається сервісом
Як тільки ці елементи відомі, діалог «клієнт-сервер» відбувається за однаковим форматом незалежно від того, до якої веб-служби звертаються. Таким чином, написання клієнтських програм стандартизується.
- З міркувань безпеки щодо атак з Інтернету багато організацій мають приватні мережі та відкривають в Інтернеті лише певні порти своїх серверів: переважно порт 80 веб-сервісу. Усі інші порти заблоковані. Тому клієнтсько-серверні додатки, представлені в попередньому розділі, створюються в межах приватної мережі (інтранету) і, як правило, недоступні ззовні. Розміщення сервісу на веб-сервері робить його доступним для всієї інтернет-спільноти.
- Веб-сервіс можна моделювати як віддалений об’єкт. Тоді надавані послуги стають методами цього об’єкта. Клієнт може отримати доступ до цього віддаленого об’єкта так, ніби він є локальним. Це приховує всю частину мережевої комунікації та дозволяє створити клієнт, незалежний від цього рівня. Якщо цей рівень зміниться, клієнт не потребуватиме модифікації. Це є величезною перевагою і, ймовірно, головним плюсом веб-сервісів.
- Як і у випадку з клієнт-серверними додатками TCP/IP, розглянутими в попередньому розділі, клієнт і сервер можуть бути написані на будь-якій мові програмування. Вони обмінюються рядками тексту. Ці рядки складаються з двох частин:
- заголовки, необхідні для протоколу HTTP
- тіло повідомлення. У разі відповіді сервера клієнту воно має формат XML (eXtensible Markup Language). У разі запиту клієнта до сервера тіло повідомлення може мати кілька форм, зокрема XML. Запит клієнта XML може мати особливий формат, який називається SOAP (Simple Object Access Protocol). У цьому випадку відповідь сервера також відповідає формату SOAP.
10.2. Веб-браузери та XML
Веб-сервіси надсилають XML своїм клієнтам. Веб-браузери можуть по-різному реагувати на отримання цього потоку XML. Internet Explorer має попередньо визначений стильовий шаблон, який дозволяє його відобразити. Netscape Communicator не має такого стильового шаблону і не відображає отриманий код XML. Щоб отримати доступ до XML, потрібно переглянути вихідний код отриманої сторінки. Ось приклад для наступного коду XML:
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="st.istia.univ-angers.fr">bonjour de nouveau !</string>
Internet Explorer відобразить таку сторінку:

тоді як Netscape Navigator відобразить:

Якщо переглянути вихідний код сторінки, отриманої в Netscape, отримаємо:

Netscape дійсно отримав те саме, що й Internet Explorer, але відобразив це по-іншому. Далі ми будемо використовувати Internet Explorer для створення знімків екрана.
10.3. Перший веб-сервіс
Ми ознайомимося з веб-сервісами на прикладі надзвичайно простого сервісу, який має три варіанти.
10.3.1. Версія 1
Для цієї першої версії ми будемо використовувати VS.NET, який має ту перевагу, що дозволяє створити каркас веб-сервісу, готовий до негайного використання. Зрозумівши цю архітектуру, ми зможемо почати діяти самостійно. Це буде предметом наступних версій.
За допомогою VS.NET створимо новий проєкт з опцією [Fichier/Nouveau/Projet]:

Зверніть увагу на наступні моменти:
- тип проєкту — Visual Basic (ліва панель)
- шаблон проекту — «Веб-сервіс» ASP.NET (права панель)
- розташування можна вибрати довільно. У даному випадку веб-сервіс розміщуватиметься на локальному веб-сервері IIS. Отже, його URL буде http://localhost/[chemin], де [chemin] потрібно визначити. Тут ми обираємо шлях http://localhost/polyvbnet/demo. VS.NET створить папку для цього проєкту. Де саме? Сервер IIS має кореневий каталог для дерева веб-документів, які він надає. Назвемо цей кореневий каталог <IISroot>. Він відповідає URL http://localhost. Звідси випливає, що URL http://localhost/polyvbnet/demo буде пов’язана з папкою <IISroot>/polyvbnet/demo. <IISroot> зазвичай є папкою \inetpub\wwwroot на диску, де було встановлено IIS. У нашому прикладі це диск E. Отже, папка, створена VS.NET, — це папка e:\inetpub\wwwroot\polyvbnet\demo:

Як завжди, створено надто багато папок. Не всі вони є корисними. Ми пояснимо лише ті, які нам потрібні в даний момент. VS.NET створив проект:

Ми бачимо деякі файли, що містяться у фізичній папці проекту. Найцікавішим для нас є файл із розширенням asmx. Це розширення веб-сервісів. Веб-сервіс керується VS.NET як додаток Windows, c.a.d — додаток, що має графічний інтерфейс та код для його керування. Саме тому ми маємо вікно проектування:

Зазвичай веб-сервіс не має графічного інтерфейсу. Він являє собою об’єкт, який можна викликати віддалено. Він має методи, і програми викликають їх. Отже, ми розглядатимемо його як класичний об’єкт із тією особливістю, що його можна інстанціювати віддалено через мережу. Тому ми не використовуватимемо вікно проектування, представлене VS.NET. Замість цього звернемо увагу на код сервісу, скориставшись опцією «Вигляд/Код»:

Слід звернути увагу на кілька моментів:
- файл називається Service1.asmx.vb, а не Service1.asmx. До змісту файлу Service1.asmx ми повернемося трохи пізніше.
- ми бачимо вікно з кодом, аналогічне тому, яке було під час створення додатків для Windows за допомогою VS.NET
Код, згенерований VS.NET, має такий вигляд:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
#Регіон «Код, згенерований Конструктором веб-сервісів»
Public Sub New()
MyBase.New()
'Цей виклик є обов’язковим для Конструктора веб-сервісів.
InitializeComponent()
'Додайте свій код ініціалізації після виклику InitializeComponent()
End Sub
'Необхідний для Конструктора веб-сервісів
Private components As System.ComponentModel.IContainer
'REMARQUE: наступна процедура є обов’язковою для Конструктора веб-сервісів
'Її можна змінити за допомогою Конструктора веб-сервісів.
'Не змінюйте її за допомогою редактора коду.
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
components = New System.ComponentModel.Container()
End Sub
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
'CODEGEN: ця процедура є обов’язковою для Конструктора веб-сервісів
'Не змінюйте її за допомогою редактора коду.
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub
#Кінець регіону
' EXEMPLE DE SERVICE WEB
' Приклад служби HelloWorld() повертає рядок «Hello World».
' Щоб виконати генерацію, не коментуйте наступні рядки, а потім збережіть і згенеруйте проект.
' Щоб протестувати цей веб-сервіс, переконайтеся, що файл .asmx є стартовою сторінкою
' та натисніть F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' Кінець функції
End Class
Перш за все, звернімо увагу, що перед нами є клас Service1, який походить від класу WebService:
Це змушує нас імпортувати простір імен System.Web.Services:
Imports System.Web.Services
Перед оголошенням класу стоїть атрибут компіляції:
<System.Web.Services.WebService(Namespace := "http://tempuri.org/demo/Service1")> _
Public Class Service1
Inherits System.Web.Services.WebService
Атрибут System.Web.Services.WebService() вказує, що наступний клас є веб-сервісом. Цей атрибут підтримує різні параметри, серед яких є параметр під назвою NameSpace. Він слугує для розміщення веб-сервісу в просторі імен. Адже можна уявити, що у світі існує кілька веб-сервісів під назвою meteo. Нам потрібен спосіб їх розрізнити. Саме простір імен дозволяє це зробити. Один із них може називатися [espacenom1].meteo, а інший — [espacenom2].meteo. Тут ми бачимо концепцію, аналогічну просторам імен класів. VS.NET автоматично згенерував код, який розмістив у певній області вихідного коду:
Якщо придивитися до цього коду, то можна побачити той самий код, який генерував конструктор під час створення додатків для Windows. Цей код можна просто видалити, якщо немає графічного інтерфейсу, що й буде у нашому випадку для веб-сервісів.
Завершує урок приклад того, як може виглядати веб-сервіс:
#Кінець регіону
' EXEMPLE DE SERVICE WEB
' Приклад служби HelloWorld() повертає рядок «Hello World».
' Щоб виконати генерацію, не коментуйте наступні рядки, а потім збережіть і згенеруйте проект.
' Щоб протестувати цей веб-сервіс, переконайтеся, що файл .asmx є стартовою сторінкою
' та натисніть F5.
'
'<WebMethod()> Public Function HelloWorld() As String
' HelloWorld = "Hello World"
' End Function
З огляду на вищесказане, ми очищаємо код, щоб він виглядав наступним чином:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour
Inherits System.Web.Services.WebService
<WebMethod()> Public Function Bonjour() As String
Return "bonjour !"
End Function
End Class
Тепер нам стало трохи зрозуміліше.
- веб-сервіс — це клас, що походить від класу WebService
- клас кваліфікується атрибутом <System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>. Отже, ми розміщуємо наш сервіс у просторі імен st.istia.univ-angers.fr.
- Методи класу кваліфікуються атрибутом <WebMethod()>, що вказує на те, що ми маємо справу з методом, який можна викликати віддалено через мережу
Отже, клас, що реалізує наш веб-сервіс, називається Bonjour і має єдиний метод, який також називається Bonjour і повертає рядок символів. Ми готові до першого тесту.
- Запустимо веб-сервер IIS, якщо це ще не зроблено
- скористаємося опцією «Налагодження/Виконати без налагодження». VS.NET
VS.NET після цього скомпілює весь додаток, запустить браузер (зазвичай Internet Explorer, якщо він встановлений) і відобразить URL-адресу http://localhost/polyvbnet/demo/Service1.asmx:

Чому саме URL-адреса http://localhost/polyvbnet/demo/Service1.asmx? Тому що це був єдиний файл .asmx у проєкті:

Якби було кілька файлів .asmx, нам довелося б вказати, який із них слід виконати першим. Для цього потрібно клацнути правою кнопкою миші на відповідному файлі .asmx і вибрати опцію [Définir comme page de démarrage].

Може бути цікаво дізнатися, що міститься у файлі service1.asmx. Адже за допомогою VS.NET ми працювали з файлом service1.asmx.vb, а не з файлом service1.asmx. Цей файл знаходиться в папці проекту:

Відкриємо його за допомогою текстового редактора (Notepad або іншого). Отримаємо такий вміст:
Файл містить просту директиву для сервера IIS, яка вказує:
- що ми маємо справу з веб-сервісом (ключове слово WebService)
- що мовою класу цього сервісу є Visual Basic (Language="vb")
- що вихідний код цього класу міститься у файлі Service1.asmx.vb (Codebehind="Service1.asmx.vb")
- що клас, який реалізує службу, називається demo.Bonjour (Class="demo.Bonjour"). Зауважимо, що VS.NET розмістив клас Bonjour у просторі імен demo, який також є назвою проєкту.
Повернемося до сторінки, яку можна переглянути за адресою http://localhost/polyvbnet/demo/Service1.asmx:

Хто написав код HTML для наведеної вище сторінки? Ми — ні, це ми знаємо. Це IIS, який представляє веб-сервіси у стандартному форматі. Ця сторінка пропонує нам два посилання. Перейдемо за першим — [Description du service]:

Ой... це досить незрозумілий XML. Проте звернімо увагу на URL
http://localhost/polyvbnet/demo/Service1.asmx?WSDL. Відкрийте браузер і введіть цю URL-адресу безпосередньо. Ви отримаєте те саме, що й раніше. Отже, запам’ятаємо, що URL-адреса http://serviceweb?WSDL надає доступ до опису веб-сервісу XML. Повернімося на початкову сторінку та перейдімо за посиланням [Bonjour]. Нагадаємо, що Bonjour — це метод веб-сервісу. Якби було кілька методів, усі вони були б представлені тут. Ми отримуємо таку нову сторінку:

Ми навмисно скоротили отриману сторінку, щоб не перевантажувати нашу демонстрацію. Знову звернімо увагу на отриманий URL-адресу:
Якщо ми введемо цей URL-адресу безпосередньо в браузері, отримаємо те саме, що й вище. Нам пропонується скористатися кнопкою [Appeler]. Спробуємо. Ми отримуємо нову сторінку:

Це знову XML. Тут ми бачимо дві інформації, які були присутні в нашому веб-сервісі:
- простір імен st.istia.univ-angers.fr нашого сервісу
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")>
- значення, повернене методом Bonjour:
Return "bonjour !"
Що ми дізналися?
- як написати веб-сервіс S
- як його викликати
Тепер ми розглянемо, як написати веб-сервіс без використання VS.NET.
10.3.2. Версія 2
У попередньому прикладі VS.NET самостійно виконав багато завдань. Чи можна створити веб-сервіс без цього інструменту? Відповідь — так, і зараз ми це продемонструємо. За допомогою текстового редактора ми створимо такий веб-сервіс:
Imports System.Web.Services
<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour2
Inherits System.Web.Services.WebService
<WebMethod()> Public Function getBonjour() As String
Return "bonjour de nouveau !"
End Function
End Class
Клас називається Bonjour2 і має метод із назвою getBonjour. Він розміщений у файлі demo2.vb, який, у свою чергу, знаходиться в дереві каталогів сервера IIS у папці E:\Inetpub\wwwroot\polyvbnet\demo2. Це класичний клас VB.NET, який можна скомпілювати:
dos>vbc /out:demo2 /t:library /r:system.dll /r:system.web.services.dll demo2.vb
dos>dir
02/03/2004 18:04 286 demo2.vb
02/03/2004 18:10 77 demo2.asmx
02/03/2004 18:12 3 072 demo2.dll
Ми розміщуємо збірку demo2.dll у папці bin (ця назва є обов’язковою):
Тепер створюємо файл demo2.asmx. Саме його викликатимуть веб-клієнти. Його вміст такий:
Ми вже зустрічали цю директиву. Вона вказує, що:
- клас веб-служби називається Bonjour2 і знаходиться в збірці demo2.dll. IIS шукатиме цю збірку в різних місцях, зокрема в папці bin веб-служби. Саме тому ми розмістили там збірку demo2.dll.
Тепер ми можемо провести різні тести. Перевіряємо, чи IIS активний, і за допомогою браузера звертаємося за URL-адресою http://localhost/polyvbnet/demo2/demo2.asmx:

Потім URL-адресу http://localhost/polyvbnet/demo2/demo2.asmx?WSDL

Потім URL http://localhost/polyvbnet/demo2/demo2.asmx?op=getBonjour, де getBonjour — це назва єдиного методу нашого веб-сервісу:

Ми використовуємо кнопку [Appeler], наведену вище:

Ми отримуємо результат виклику методу getBonjour веб-сервісу. Тепер ми знаємо, як створити веб-сервіс без vs.net. Надалі ми не будемо зважати на те, як побудований веб-сервіс, а зосередимося лише на основних файлах.
10.3.3. Версія 3
Дві попередні версії веб-сервісу [Bonjour] використовували два файли:
- файл .asmx, точка входу веб-сервісу
- файл .vb — вихідний код веб-сервісу
Тут ми покажемо, що можна обійтися лише одним файлом .asmx. Код сервісу demo3.asmx такий:
Ми бачимо, що вихідний код сервісу тепер міститься безпосередньо у вихідному файлі demo3.asmx. Директива
більше не посилається на клас у зовнішньому збірнику, а на клас, що міститься в тому самому вихідному файлі. Помістимо його в папку <IISroot>\polyvbnet\demo3:

Запустимо IIS і введемо URL-адресу http://localhost/polyvbnet/demo3/demo3.asmx:

Ми помічаємо суттєву відмінність від попередньої версії: нам не довелося компілювати код VB сервісу. IIS здійснив цю компіляцію самостійно за допомогою компілятора VB.NET, встановленого на тій самій машині. Потім він вивів сторінку. Якщо трапиться помилка компіляції, про неї повідомить IIS:

10.3.4. Версія 4
Тут нас цікавить конфігурація сервера IIS. До цього моменту ми завжди розміщували наші веб-сервіси в кореневій структурі <IISroot> сервера IIS, у даному випадку — [e:\inetpub\wwwroot]. Тут ми покажемо, що веб-сервіс можна розмістити будь-де. Це робиться за допомогою віртуальних папок IIS. Розмістимо наш сервіс у такій папці:

Папка [D:\data\devel\vbnet\poly\chap9\demo3] відсутня у дереві папок сервера IIS. Потрібно вказати це серверу, створивши віртуальну папку IIS. Запустимо IIS і виберемо опцію [Avancé], наведену нижче:

На екрані з’явився список віртуальних каталогів. Ми не будемо на ньому зупинятися. Створюємо новий віртуальний каталог за допомогою кнопки [Ajouter], показаної вище:

За допомогою кнопки [Parcourir] ми вказуємо фізичну папку, що містить веб-сервіс, у даному випадку папку [D:\data\devel\vbnet\poly\chap9\demo3]. Ми надаємо цій папці логічне (віртуальне) ім’я: [virdemo3]. Це означає, що документи, які містяться у фізичній папці [D:\data\devel\vbnet\poly\chap9\demo3], будуть доступні в мережі за URL-адресою [http://<machine>/virdemo3]. У наведеному вище діалоговому вікні є й інші параметри, які ми залишаємо без змін. Ми підтверджуємо налаштування. Нова віртуальна папка з’являється у списку віртуальних папок IIS:

Тепер відкриваємо браузер і вводимо URL-адресу [http://localhost/virdemo3/demo3.asmx]. Отримуємо те саме, що й раніше:

10.3.5. Висновок
Ми продемонстрували кілька способів створення веб-сервісу. Надалі ми будемо використовувати метод із варіанту 3 для створення сервісу та метод із варіанту 4 для його локалізації. Таким чином, нам не знадобиться VS.NET. Проте варто відзначити корисність використання VS.NET, оскільки він допомагає у налагодженні. Існують безкоштовні інструменти для розробки веб-додатків, зокрема продукт WebMatrix, спонсором якого є Microsoft, і який можна знайти на URL [http://www.asp.net/webmatrix]. Це чудовий інструмент для початку веб-програмування без попередніх інвестицій.
10.4. Веб-сервіс операцій
Розглянемо веб-сервіс, який пропонує п’ять функцій:
- додати(a,b), що поверне a+b
- віднімати(a,b), що поверне a-b
- multiplier(a,b), що поверне a*b
- diviser(a,b), що поверне a/b
- «все»(a,b), що поверне масив [a+b, a-b, a*b, a/b]
Код VB.NET цієї служби такий:
<%@ WebService language="VB" class=operations %>
imports system.web.services
<WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits WebService
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function
<WebMethod> _
Function soustraire(a As Double, b As Double) As Double
Return a - b
End Function
<WebMethod> _
Function multiplier(a As Double, b As Double) As Double
Return a * b
End Function
<WebMethod> _
Function diviser(a As Double, b As Double) As Double
Return a / b
End Function
<WebMethod> _
Function toutfaire(a As Double, b As Double) As Double()
Return New Double() {a + b, a - b, a * b, a / b}
End Function
End Class
Тут ми повторюємо деякі пояснення, які вже надавалися, але які варто нагадати або доповнити. Клас operations схожий на клас VB.NET, однак слід звернути увагу на кілька моментів:
- перед методами стоїть атрибут <WebMethod()>, який вказує компілятору, які методи мають бути «опубліковані» (c.a.d) та надані клієнту. Метод, якому не передує цей атрибут, буде невидимим для віддалених клієнтів. Це може бути внутрішній метод, який використовується іншими методами, але не призначений для оприлюднення.
- клас походить від класу WebService, визначеного в просторі імен System.Web.Services. Таке успадкування не завжди є обов’язковим. Зокрема, у цьому прикладі можна було б обійтися без нього.
- Перед самим класом стоїть атрибут <WebService(Namespace="st.istia.univ-angers.fr")>, призначений для надання веб-сервісу простору імен. Постачальник класів присвоює своїм класам простір імен, щоб надати їм унікальну назву та уникнути конфліктів із класами інших постачальників, які можуть мати таку саму назву. Для веб-сервісів ситуація така сама. Кожен веб-сервіс має бути ідентифікований за допомогою унікальної назви, у даному випадку — st.istia.univ-angers.fr.
- Ми не визначили конструктор. Тому неявним чином буде використано конструктор батьківського класу.
Наведений вище вихідний код призначений не безпосередньо для компілятора VB.NET, а для веб-сервера IIS. Він повинен мати розширення .asmx і бути збереженим у каталозі веб-сервера. Тут ми зберігаємо його під назвою operations.asmx у папці <IISroot>\polyvbnet\operations:

До цієї фізичної папки ми прив’язуємо віртуальну папку IIS [operations]:
![]() | ![]() |
Зайдемо на сервіс за допомогою браузера. URl, який потрібно запросити, — це [http://localhost/operations/operations.asmx]:

Ми отримуємо веб-документ із посиланням на кожен із методів, визначених у веб-сервісі operations. Перейдемо за посиланням ajouter:

На отриманій сторінці нам пропонується протестувати метод ajouter, передавши йому два необхідні аргументи: a та b. Нагадаємо визначення методу ajouter:
Зауважимо, що сторінка перейняла імена аргументів a та b, використані у визначенні методу. Натискаємо кнопку Appeler і отримуємо таку відповідь у окремому вікні браузера:

Якщо замість цього ввести [Affichage/Source], отримаємо такий код:

Повторімо операцію для методу [toutfaire]:

Ми отримуємо таку сторінку:

Скористаємося кнопкою [Appeler], що знаходиться вище:

У будь-якому випадку відповідь сервера має такий вигляд:
- відповідь має формат XML
- рядок 1 є стандартним і завжди присутній у відповіді
- наступні рядки залежать від типу результату (double, ArrayOfDouble), кількості результатів та простору імен веб-сервісу (тут — st.istia.univ-angers.fr).
Існує кілька способів звернення до веб-сервісу та отримання відповіді. Повернемося до URL цього сервісу:

і перейдемо за посиланням [ajouter]. На сторінці, що відкрилася, наведено два способи запиту до функції [ajouter] веб-сервісу:



Ці два методи доступу до функцій веб-сервісу називаються відповідно: HTTP-POST та SOAP. Тепер розглянемо їх по черзі.
Примітка: у перших версіях VS.NET існував третій метод під назвою HTTP-GET. На момент написання цього документа (березень 2004 року) цей метод, схоже, більше не доступний. Це означає, що веб-сервіс, згенерований за допомогою VS.NET, не приймає запитів GET. Це не означає, що неможливо створити веб-сервіси, які приймають запити GET, зокрема за допомогою інших інструментів, окрім VS.NET, або просто вручну.
10.5. Клієнт HTTP-POST
Ми дотримуємося методу, запропонованого веб-сервісом:

Прокоментуємо те, що написано. Насамперед веб-клієнт повинен надіслати такі заголовки HTTP:
Веб-клієнт надсилає запит POST на URL /operations/operations.asmx/ajouter згідно з протоколом HTTP версії 1.1 | |
Вказується цільова машина запиту. У даному випадку — localhost. Цей заголовок став обов’язковим у версії 1.1 протоколу HTTP | |
Тут вказується, що після заголовків HTTP будуть надіслані додаткові параметри у форматі urlencoded. Цей формат замінює певні символи на їхні шістнадцяткові коди. | |
Це кількість символів у рядку параметрів, який буде надіслано після заголовків HTTP. |
За заголовками HTTP йде порожній рядок, а потім — рядок параметрів POST довжиною [Content-Length] символів у вигляді a=XX&b=YY, де XX та YY — це «url-кодовані» рядки значень параметрів a та b. Ми знаємо достатньо, щоб відтворити наведене вище за допомогою нашого загального клієнта TCP, який вже використовувався в розділі про програмування TCP/IP:
- запускаємо IIS
- сервіс доступний за URL-адресою [http://localhost/operations/operations.asmx]
- використовуємо загальний TCP-клієнт у вікні DOS
dos>clttcpgenerique localhost 80
Commandes :
POST /operations/operations.asmx/ajouter HTTP/1.1
HOST: localhost
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-length: 7
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
a=2&b=3
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Спочатку зауважимо, що ми додали заголовок [Connection: close], щоб попросити сервер закрити з'єднання після надсилання відповіді. Це тут необхідно. Якщо цього не вказати, за замовчуванням сервер залишить з'єднання відкритим. Однак його відповідь — це послідовність текстових рядків, останній з яких не закінчується символом кінця рядка. Виявляється, що наш універсальний клієнт TCP зчитує текстові рядки, що закінчуються символом кінця рядка, за допомогою методу ReadLine. Якщо сервер не закриває з’єднання після надсилання останнього рядка, клієнт блокується, оскільки чекає на символ кінця рядка, який не надходить. Якщо сервер закриває з’єднання, метод ReadLine клієнта завершується, і клієнт не блокується.
Одразу після отримання порожнього рядка, що сигналізує про закінчення заголовків HTTP, сервер IIS надсилає першу відповідь:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:17 GMT
<-- X-Powered-By: ASP.NET
<--
Ця відповідь, що складається виключно з заголовків HTTP, повідомляє клієнту, що він може надіслати ті 7 символів, які, за його словами, він хотів надіслати. Що ми робимо:
Тут слід звернути увагу, що наш TCP-клієнт надсилає більше ніж 7 символів, оскільки він надсилає їх із позначкою кінця рядка (WriteLine). Це не заважає серверу, який із отриманих символів візьме лише перші 7, а потім з’єднання закривається (Connection: close). Ці зайві символи стали б перешкодою, якби з’єднання залишилося відкритим, оскільки тоді їх сприйняли б як частину наступної команди клієнта. Отримавши параметри, сервер надсилає свою відповідь:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 13:55:26 GMT
<-- X-Powered-By: ASP.NET
<-- Connection: close
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 90
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">5</double>
Тепер ми маємо все необхідне для написання клієнтської програми для нашого веб-сервісу. Це буде консольний клієнт під назвою httpPost2, який використовується наступним чином:
dos>httpPost2 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
soustraire 8 9
--> POST /operations/operations.asmx/soustraire HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=8&b=9
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">-1</double>
[résultat=-1]
fin
dos>
Клієнт викликається шляхом передачі йому параметра URL веб-сервісу:
Потім клієнт зчитує команди, введені з клавіатури, та виконує їх. Вони мають такий формат:
де «функція» — це функція веб-сервісу, що викликається (додавання, віднімання, множення, ділення), а a та b — значення, над якими ця функція буде працювати. Наприклад:
Після цього клієнт надсилає необхідний запит HTTP до веб-сервера та отримує відповідь. Обмін даними між клієнтом і сервером відображається на екрані для кращого розуміння процесу:
ajouter 6 7
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<--
--> a=6&b=7
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
[résultat=13]
Вище наведено обмін даними, який ми вже розглядали у випадку з загальним клієнтом TCP, з однією відмінністю: заголовок HTTP Connection: Keep-Alive вимагає від сервера не закривати з’єднання. Таким чином, з’єднання залишається відкритим для наступної операції клієнта, якому, отже, не потрібно повторно підключатися до сервера. Однак це змушує його використовувати інший метод, ніж ReadLine(), для зчитування відповіді сервера, оскільки відомо, що вона складається з низки рядків, останній з яких не закінчується символом кінця рядка. Отримавши всю відповідь сервера, клієнт аналізує її, щоб знайти результат запитуваної операції та вивести його на екран:
Розглянемо код нашого клієнта:
' простори імен
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Imports System.Web
' клієнт веб-сервісу operations
Public Module clientPOST
Public Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg URI"
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' кількість аргументів
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' зазначаємо запитуване URI
Dim URIstring As String = args(0)
' встановлюється з'єднання з сервером
Dim uri As Uri = Nothing ' l'URI du service web
Dim client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Try
' підключення до сервера
uri = New Uri(URIstring)
client = New TcpClient(uri.Host, uri.Port)
' створюються потоки вводу-виводу клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
Catch ex As Exception
' URI неправильний або інша проблема
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' створення словника функцій веб-сервісу
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' запити користувача вводяться з клавіатури
' у формі «функція a b»
' вони закінчуються командою «fin»
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
Dim fonction As String = Nothing ' nom d'une fonction du service web
Dim a, b As String ' les arguments des fonctions du service web
' запрошення користувачеві
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b")
' обробка помилок
Dim erreurCommande As Boolean
Try
' цикл обробки команд, введених з клавіатури
While True
' на початку немає помилок
erreurCommande = False
' зчитування команди
commande = Console.In.ReadLine().Trim().ToLower()
' завершено?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' розбиття команди на поля
champs = Regex.Split(commande, "\s+")
Try
' потрібно три поля
If champs.Length <> 3 Then
Throw New Exception
End If
' поле 0 має бути визнаною функцією
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' поле 1 має бути дійсним числом
a = champs(1)
Double.Parse(a)
' поле 2 має бути дійсним числом
b = champs(2)
Double.Parse(b)
Catch
' неправильний запит
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' запит надсилається до веб-сервісу
If Not erreurCommande Then executeFonction([IN], OUT, uri, fonction, a, b)
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' завершення з'єднання клієнт-сервер
Try
[IN].Close()
OUT.Close()
client.Close()
Catch
End Try
End Sub
...........
' відображення помилок
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' припинення роботи з помилкою
Environment.Exit(exitCode)
End Sub
End Module
Тут ми бачимо елементи, з якими вже неодноразово стикалися і які не потребують особливих коментарів. Тепер розглянемо код методу executeFonction, де містяться нові елементи:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' виконує функцію(a,b) на веб-сервісі з URI URI
' обмін даними між клієнтом і сервером відбувається через потоки IN та OUT
' результат функції міститься у рядку
' <double xmlns="st.istia.univ-angers.fr">double</double>
' надіслано сервером
' побудова рядка запиту
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' формування масиву заголовків HTTP для відправлення
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' заголовки HTTP надсилаються на сервер
Dim i As Integer
For i = 0 To entetes.Length - 1
' відправлення на сервер
OUT.WriteLine(entetes(i))
' відображення на екрані
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
' зчитується перша відповідь веб-сервера HTTP/1.1 100
Dim ligne As String = Nothing
' один рядок потоку читання
ligne = [IN].ReadLine()
While ligne <> ""
'відлуння
Console.Out.WriteLine(("<-- " + ligne))
' наступний рядок
ligne = [IN].ReadLine()
End While
'відлуння останнього рядка
Console.Out.WriteLine(("<-- " + ligne))
' відправлення параметрів запиту
OUT.Write(requête)
' відлуння
Console.Out.WriteLine(("--> " + requête))
' побудова регулярного виразу для визначення розміру відповіді XML
' у потоці відповіді веб-сервера
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' читання другого відповіді веб-сервера після відправлення запиту
' зберігаємо значення рядка Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' відображення на екрані
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' наступний рядок
ligne = [IN].ReadLine()
End While
' виведення останнього рядка на екран
Console.Out.WriteLine("<--")
' побудова регулярного виразу для отримання результату
' у потоці відповіді веб-сервера
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' зчитуємо решту відповіді веб-сервера
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' розбиття відповіді на рядки тексту
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' проглядаємо рядки тексту у пошуках результату
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' подальша обробка
Console.Out.WriteLine(("<-- " + lignes(i)))
' порівняння поточного рядка з шаблоном
MatchRésultat = ModèleRésultat.Match(lignes(i))
' чи знайдено?
If MatchRésultat.Success Then
' записуємо результат
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' виводимо результат
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
По-перше, клієнт HTTP-POST надсилає свій запит у форматі POST:
' побудова рядка запиту
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
' побудова масиву заголовків HTTP для відправки
Dim entetes(5) As String
entetes(0) = "POST " + uri.AbsolutePath + "/" + fonction + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: application/x-www-form-urlencoded"
entetes(3) = "Content-Length: " & nbChars
entetes(4) = "Connection: Keep-Alive"
entetes(5) = ""
' надсилання заголовків HTTP на сервер
Dim i As Integer
For i = 0 To entetes.Length - 1
' відправлення на сервер
OUT.WriteLine(entetes(i))
' відображення на екрані
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
У заголовку
необхідно вказати розмір параметрів, які клієнт надішле за заголовками HTTP:
Для цього використовується такий код:
' формування рядка запиту
Dim requête As String = "a=" + HttpUtility.UrlEncode(a) + "&b=" + HttpUtility.UrlEncode(b)
Dim nbChars As Integer = requête.Length
Метод HttpUtility.UrlEncode(string chaine) перетворює деякі символи з chaîne у %n1n2, де n1n2 — це код ASCII перетвореного символу. Символи, на які поширюється ця трансформація, — це всі символи, що мають особливе значення в запиті POST (пробіл, знак =, знак & тощо). У даному випадку метод HttpUtility.UrlEncode зазвичай не потрібен, оскільки a та b — це числа, які не містять жодного з цих особливих символів. Він наведено тут лише як приклад. Для нього потрібен простір імен System.Web. Після того, як клієнт надіслав свої заголовки HTTP:
--> POST /operations/operations.asmx/ajouter HTTP/1.1
--> Host: localhost:80
--> Content-Type: application/x-www-form-urlencoded
--> Content-Length: 7
--> Connection: Keep-Alive
-->
сервер відповідає заголовком HTTP 100 Continue:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:47 GMT
<-- X-Powered-By: ASP.NET
<--
Код просто зчитує та виводить на екран цю першу відповідь:
' зчитується перша відповідь веб-сервера HTTP/1.1 100
Dim ligne As String = Nothing
' один рядок потоку читання
ligne = [IN].ReadLine()
While ligne <> ""
'відлуння
Console.Out.WriteLine(("<-- " + ligne))
' наступний рядок
ligne = [IN].ReadLine()
End While
'відлуння останнього рядка
Console.Out.WriteLine(("<-- " + ligne))
Після ознайомлення з цією першою відповіддю клієнт повинен надіслати свої параметри:
Для цього він використовує такий код:
' відправлення параметрів запиту
OUT.Write(requête)
' відлуння
Console.Out.WriteLine(("--> " + requête))
Після цього сервер надішле свою відповідь. Вона складається з двох частин:
- заголовків HTTP, що закінчуються порожнім рядком
- відповідь у форматі XML
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Wed, 03 Mar 2004 14:56:38 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 91
<--
<-- <?xml version="1.0" encoding="utf-8"?>
<-- <double xmlns="st.istia.univ-angers.fr">13</double>
Спочатку клієнт зчитує заголовки HTTP, щоб знайти в них рядок Content-Length і отримати розмір відповіді XML (у даному випадку 90). Цей розмір отримується за допомогою регулярного виразу. Можна було б зробити це інакше, і, безсумнівно, ефективніше.
' побудова регулярного виразу для визначення розміру відповіді XML
' у потоці відповіді веб-сервера
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' читання другого відповіді веб-сервера після відправлення запиту
' зберігаємо значення рядка Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' відображення на екрані
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' наступний рядок
ligne = [IN].ReadLine()
End While
' виведення останнього рядка на екран
Console.Out.WriteLine("<--")
Отримавши довжину N відповіді XML, залишається лише прочитати N символів із потоку IN відповіді сервера. Цей рядок із N символів розбивається на рядки тексту для потреб відстеження екрану. Серед цих рядків шукаємо рядок із результатом:
— знову ж таки за допомогою регулярного виразу. Після знаходження результату його виводиться на екран.
Кінець коду клієнта виглядає так:
' побудова регулярного виразу для отримання результату
' у потоці відповіді веб-сервера
Dim modèle As String = "<double xmlns=""st.istia.univ-angers.fr"">(.+?)</double>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' зчитуємо решту відповіді веб-сервера
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' розбиття відповіді на рядки тексту
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' проглядаємо рядки тексту у пошуках результату
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' подальша обробка
Console.Out.WriteLine(("<-- " + lignes(i)))
' порівняння поточного рядка з шаблоном
MatchRésultat = ModèleRésultat.Match(lignes(i))
' чи знайдено?
If MatchRésultat.Success Then
' результат записується
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' виводимо результат
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.6. Клієнт SOAP
Тут ми розглядаємо другого клієнта, який використовуватиме діалог «клієнт-сервер» типу SOAP (Simple Object Access Protocol). Наведено приклад діалогу для функції ajouter:


Запит клієнта — це запит POST. Отже, ми побачимо деякі механізми, що вже використовувалися попереднім клієнтом. Головна відмінність полягає в тому, що тоді як клієнт HTTP-POST надсилав параметри a та b у формі
, клієнт SOAP надсилає їх у більш складному форматі XML:
POST /operations/operations.asmx HTTP/1.1
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "st.istia.univ-angers.fr/ajouter"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>double</a>
<b>double</b>
</ajouter>
</soap:Body>
</soap:Envelope>
У відповідь він отримує код XML, який також є складнішим за розглянуті раніше:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouterResponse xmlns="st.istia.univ-angers.fr">
<ajouterResult>double</ajouterResult>
</ajouterResponse>
</soap:Body>
</soap:Envelope>
Хоча запит і відповідь є складнішими, це той самий механізм HTTP, що й для клієнта HTTP-POST. Таким чином, код клієнта SOAP можна скопіювати з коду клієнта HTTP-POST. Ось приклад виконання:
dos>clientsoap1 http://localhost/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
[résultat=7]
Змінюється лише метод executeFonction. Клієнт SOAP надсилає заголовки HTTP свого запиту. Вони просто трохи складніші, ніж у HTTP-POST:
ajouter 3 4
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
Код, що їх генерує:
' executeFonction
Public Sub executeFonction(ByVal [IN] As StreamReader, ByVal OUT As StreamWriter, ByVal uri As Uri, ByVal fonction As String, ByVal a As String, ByVal b As String)
' виконує функцію(a,b) на веб-сервісі з URI URI
' обмін даними між клієнтом і сервером відбувається через потоки IN та OUT
' результат функції міститься у рядку
' <double xmlns="st.istia.univ-angers.fr">double</double>
' надіслано сервером
' побудова рядка запиту SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' формування масиву заголовків HTTP для відправлення
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " & uri.Host & ":" & uri.Port
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " & nbCharsSOAP
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' заголовки HTTP надсилаються на сервер
Dim i As Integer
For i = 0 To entetes.Length - 1
' відправлення на сервер
OUT.WriteLine(entetes(i))
' відображення на екрані
Console.Out.WriteLine(("--> " + entetes(i)))
Next i
Отримавши цей запит, сервер надсилає свою першу відповідь, яку клієнт відображає:
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:29 GMT
<-- X-Powered-By: ASP.NET
<--
Код для зчитування цієї першої відповіді такий:
' зчитується перша відповідь веб-сервера HTTP/1.1 100
Dim ligne As String = Nothing
' один рядок потоку читання
ligne = [IN].ReadLine()
While ligne <> ""
'відбиття
Console.Out.WriteLine(("<-- " + ligne))
' наступний рядок
ligne = [IN].ReadLine()
End While 'while
'відлуння останнього рядка
Console.Out.WriteLine(("<-- " + ligne))
Тепер клієнт надішле свої параметри у форматі XML у так званому конверті SOAP:
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>3</a>
<b>4</b>
</ajouter>
</soap:Body>
</soap:Envelope>
Код:
Після цього сервер надішле остаточну відповідь:
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 07:28:33 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 345
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>7</ajouterResult></ajouterResponse
></soap:Body></soap:Envelope>
Клієнт відображає на екрані отримані заголовки HTTP, шукаючи серед них рядок Content-Length:
' побудова регулярного виразу, що дозволяє визначити розмір відповіді XML
' у потоці відповіді веб-сервера
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' зчитування другої відповіді веб-сервера після відправлення запиту
' зберігаємо значення рядка Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' відображення на екрані
Console.Out.WriteLine(("<-- " + ligne))
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' наступний рядок
ligne = [IN].ReadLine()
End While 'while
' виведення останнього рядка на екран
Console.Out.WriteLine("<--")
Як тільки стає відомим розмір N відповіді XML, клієнт зчитує N символів із потоку відповіді сервера, розбиває отриманий рядок на текстові рядки для відображення їх на екрані та шукає в них тег XML у результаті: <ajouterResult>7</ajouterResult> і відображає його:
' побудова регулярного виразу для отримання результату
' у потоці відповіді веб-сервера
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' зчитується решта відповіді веб-сервера
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' розбиття відповіді на рядки тексту
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' проглядаємо рядки тексту у пошуках результату
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' подальша обробка
Console.Out.WriteLine(("<-- " + lignes(i)))
' порівняння поточного рядка з шаблоном
MatchRésultat = ModèleRésultat.Match(lignes(i))
' чи знайдено?
If MatchRésultat.Success Then
' результат записується
strRésultat = MatchRésultat.Groups(1).Value
End If
'наступний рядок
Next i
' виводимо результат
Console.Out.WriteLine(("[résultat=" + strRésultat + "]" + ControlChars.Lf))
End Sub
10.7. Інкапсуляція обміну даними між клієнтом і сервером
Уявімо, що наш веб-сервіс operations використовується різними додатками. Було б цікаво надати їм клас, який би виконував роль інтерфейсу між клієнтським додатком і веб-сервісом та приховував би більшу частину мережевого обміну даними, що для більшості розробників є нетривіальним завданням. Таким чином, ми отримаємо таку схему:
![]() |
Клієнтський додаток звертатиметься до клієнтсько-серверного інтерфейсу, щоб надсилати запити до веб-сервісу. Цей інтерфейс здійснюватиме весь необхідний обмін даними з сервером і повертатиме отриманий результат клієнтському додатку. Клієнтський додаток більше не муситиме займатися обміном даними з сервером, що значно спростить його розробку.
10.7.1. Клас інкапсуляції
З огляду на те, що було розглянуто в попередніх параграфах, ми тепер добре розуміємо мережевий обмін даними між клієнтом і сервером. Ми навіть розглянули три методи. Ми вирішили інкапсулювати метод SOAP. Клас має такий вигляд:
' простори імен
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports System.Web
Imports Microsoft.VisualBasic
' clientSOAP веб-сервісу operations
Public Class clientSOAP
' змінні екземпляра
Private uri As uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' словник функцій
Private dicoFonctions As New Hashtable
' список функцій
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' детальний вивід
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
' конструктор
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' режим детального виведення
Me.verbose = verbose
' підключення до сервера
uri = New Uri(uriString)
client = New TcpClient(uri.Host, uri.Port)
' створення потоків вводу-виводу клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' створення словника функцій веб-сервісу
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
End Sub
' закриття з'єднання з сервером
Public Sub Close()
' завершення зв’язку між клієнтом і сервером
[IN].Close()
OUT.Close()
client.Close()
End Sub
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' виконання функції(a,b) на веб-сервісі з URI URI
' обмін даними між клієнтом і сервером відбувається через потоки IN та OUT
' результат функції міститься у рядку
' <double xmlns="st.istia.univ-angers.fr">double</double>
' надіслано сервером
' функція дійсна?
fonction = fonction.Trim().ToLower()
If Not dicoFonctions.ContainsKey(fonction) Then
Return "[fonction [" + fonction + "] indisponible : (ajouter, soustraire,multiplier,diviser)]"
End If
' аргументи a та b дійсні?
Dim doubleA As Double = 0
Try
doubleA = Double.Parse(a)
Catch
Return "[argument [" + a + "] incorrect (double)]"
End Try
Dim doubleB As Double = 0
Try
doubleB = Double.Parse(b)
Catch
Return "[argument [" + b + "] incorrect (double)]"
End Try
' ділення на нуль?
If fonction = "diviser" And doubleB = 0 Then
Return "[division par zéro]"
End If
' побудова рядка запиту SOAP
Dim requêteSOAP As String = "<?xml version=" + """1.0"" encoding=""utf-8""?>" + ControlChars.Lf
requêteSOAP += "<soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/"">" + ControlChars.Lf
requêteSOAP += "<soap:Body>" + ControlChars.Lf
requêteSOAP += "<" + fonction + " xmlns=""st.istia.univ-angers.fr"">" + ControlChars.Lf
requêteSOAP += "<a>" + a + "</a>" + ControlChars.Lf
requêteSOAP += "<b>" + b + "</b>" + ControlChars.Lf
requêteSOAP += "</" + fonction + ">" + ControlChars.Lf
requêteSOAP += "</soap:Body>" + ControlChars.Lf
requêteSOAP += "</soap:Envelope>"
Dim nbCharsSOAP As Integer = requêteSOAP.Length
' формування масиву заголовків HTTP для відправки
Dim entetes(6) As String
entetes(0) = "POST " + uri.AbsolutePath + " HTTP/1.1"
entetes(1) = "Host: " + uri.Host + ":" + uri.Port.ToString
entetes(2) = "Content-Type: text/xml; charset=utf-8"
entetes(3) = "Content-Length: " + nbCharsSOAP.ToString
entetes(4) = "Connection: Keep-Alive"
entetes(5) = "SOAPAction: ""st.istia.univ-angers.fr/" + fonction + """"
entetes(6) = ""
' заголовки HTTP надсилаються на сервер
Dim i As Integer
For i = 0 To entetes.Length - 1
' відправлення на сервер
OUT.WriteLine(entetes(i))
' відображення на екрані
If verbose Then
Console.Out.WriteLine(("--> " + entetes(i)))
End If
Next i
' зчитується перша відповідь веб-сервера HTTP/1.1 100
Dim ligne As String = Nothing
' один рядок потоку читання
ligne = [IN].ReadLine()
While ligne <> ""
'відбиття
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' наступний рядок
ligne = [IN].ReadLine()
End While
'відлуння останнього рядка
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' відправлення параметрів запиту
OUT.Write(requêteSOAP)
' відлуння
If verbose Then
Console.Out.WriteLine(("--> " + requêteSOAP))
End If
' побудова регулярного виразу для визначення розміру відповіді XML
' у потоці відповіді веб-сервера
Dim modèleLength As String = "^Content-Length: (.+?)\s*$"
Dim RegexLength As New Regex(modèleLength) '
Dim MatchLength As Match = Nothing
Dim longueur As Integer = 0
' читання другого відповіді веб-сервера після відправлення запиту
' зберігаємо значення рядка Content-Length
ligne = [IN].ReadLine()
While ligne <> ""
' відображення на екрані
If verbose Then
Console.Out.WriteLine(("<-- " + ligne))
End If
' Content-Length?
MatchLength = RegexLength.Match(ligne)
If MatchLength.Success Then
longueur = Integer.Parse(MatchLength.Groups(1).Value)
End If
' наступний рядок
ligne = [IN].ReadLine()
End While
' виведення останнього рядка на екран
If verbose Then
Console.Out.WriteLine("<--")
End If
' побудова регулярного виразу для отримання результату
' у потоці відповіді веб-сервера
Dim modèle As String = "<" + fonction + "Result>(.+?)</" + fonction + "Result>"
Dim ModèleRésultat As New Regex(modèle)
Dim MatchRésultat As Match = Nothing
' зчитуємо решту відповіді веб-сервера
Dim chrRéponse(longueur) As Char
[IN].Read(chrRéponse, 0, longueur)
Dim strRéponse As String = New [String](chrRéponse)
' розбиття відповіді на рядки тексту
Dim lignes As String() = Regex.Split(strRéponse, ControlChars.Lf)
' проглядаємо рядки тексту у пошуках результату
Dim strRésultat As String = "?" ' résultat de la fonction
For i = 0 To lignes.Length - 1
' подальші дії
If verbose Then
Console.Out.WriteLine(("<-- " + lignes(i)))
End If ' comparaison ligne courante au modèle
MatchRésultat = ModèleRésultat.Match(lignes(i))
' чи знайдено?
If MatchRésultat.Success Then
' записуємо результат
strRésultat = MatchRésultat.Groups(1).Value
End If
Next i
' повертаємо результат
Return strRésultat
End Function
End Class
Тут немає нічого нового порівняно з тим, що ми вже розглядали. Ми просто взяли код клієнта SOAP, який ми вивчали, і дещо переробили його, щоб перетворити на клас. Цей клас має конструктор і два методи:
' конструктор
Public Sub New(ByVal uriString As String, ByVal verbose As Boolean)
' executeFonction
Public Function executeFonction(ByVal fonction As String, ByVal a As String, ByVal b As String) As String
' закриття з'єднання з сервером
Public Sub Close()
та має такі атрибути:
' змінні екземпляра
Private uri As Uri = Nothing ' l'URI du service web
Private client As TcpClient = Nothing ' la liaison tcp du client avec le serveur
Private [IN] As StreamReader = Nothing ' le flux de lecture du client
Private OUT As StreamWriter = Nothing ' le flux d'écriture du client
' словник функцій
Private dicoFonctions As New Hashtable
' список функцій
Private fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser"}
' детальний вивід
Private verbose As Boolean = False ' à vrai, affiche à l'écran les échanges client-serveur
Конструктору передаються два параметри:
- URI веб-сервісу, до якого він повинен підключитися
- логічне значення verbose, яке, по суті, вимагає, щоб мережеві обміни відображалися на екрані; інакше вони не будуть відображатися.
Під час створення об’єкта формуються потоки IN для читання з мережі, OUT для запису в мережу, а також словник функцій, що підтримуються сервісом. Після створення об’єкта відкривається з’єднання «клієнт-сервер», і його потоки IN та OUT стають доступними для використання.
Метод Close дозволяє закрити з’єднання з сервером.
Метод ExecuteFonction — це той самий метод, який ми написали для розглянутого клієнта SOAP, за винятком кількох дрібних деталей:
- Параметри uri, IN та OUT, які раніше передавалися як параметри методу, більше не потрібно передавати, оскільки тепер вони є атрибутами екземпляра, доступними для всіх методів цього екземпляра
- Метод ExecuteFonction, який раніше повертав тип void і виводив результат функції на екран, тепер повертає цей результат, а отже, тип string.
Зазвичай клієнт використовуватиме клас clientSOAP наступним чином:
- створення об’єкта clientSOAP, який встановить з’єднання з веб-сервісом
- повторне використання методу executeFonction
- припинення з'єднання з веб-сервісом за допомогою методу Close.
Розглянемо перший клієнт.
10.7.2. Клієнт консолі
Тут ми повертаємося до клієнта SOAP, який ми розглядали тоді, коли клас clientSOAP ще не існував, і переробляємо його так, щоб він тепер використовував цей клас:
' простори імен
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
Public Module testClientSoap
' запит на URI від веб-сервісу operations
' інтерактивно виконує команди, введені з клавіатури
Public Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg URI [verbose]"
' кількість аргументів
If args.Length <> 1 And args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' детальний вивід?
Dim verbose As Boolean = False
If args.Length = 2 Then
verbose = args(1).ToLower() = "verbose"
End If
' встановлення з’єднання з веб-сервісом
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' помилка підключення
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
' запити користувача вводяться з клавіатури
' у вигляді функції a b — вони завершуються командою «end»
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' запрошення користувачеві
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b" + ControlChars.Lf)
' обробка помилок
Dim erreurCommande As Boolean
Try
' цикл обробки команд, введених з клавіатури
While True
' на початку немає помилок
erreurCommande = False
' зчитування команди
commande = Console.In.ReadLine().Trim().ToLower()
' завершено?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' розбиття команди на поля
champs = Regex.Split(commande, "\s+")
' потрібно три поля
If champs.Length <> 3 Then
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
' фіксуємо помилку
erreurCommande = True
End If
' надсилаємо запит до веб-сервісу
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
' наступний запит
End While
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
' завершення з'єднання клієнт-сервер
Try
client.Close()
Catch
End Try
End Sub
' відображення помилок
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' припинення роботи з помилкою
Environment.Exit(exitCode)
End Sub
End Module
Тепер клієнт став значно простішим, і в ньому немає жодних мережевих операцій. Клієнт приймає два параметри:
- URI веб-сервісу operations
- опціональний ключовий параметр verbose. Якщо він присутній, мережеві обміни будуть відображатися на екрані.
Ці два параметри використовуються для створення об’єкта clientSOAP, який забезпечуватиме обмін даними з веб-сервісом.
' встановлення з'єднання з веб-сервісом
Dim client As clientSOAP = Nothing
Try
client = New clientSOAP(args(0), verbose)
Catch ex As Exception
' помилка підключення
erreur("L'erreur suivante s'est produite lors de la connexion au service web : " + ex.Message, 2)
End Try
Після встановлення з'єднання з веб-сервісом клієнт може надсилати свої запити. Вони вводяться з клавіатури, аналізуються, а потім надсилаються на сервер шляхом виклику методу executeFonction об'єкта clientSOAP.
' надсилання запиту до веб-сервісу
If Not erreurCommande Then Console.Out.WriteLine(("résultat=" + client.executeFonction(champs(0).Trim().ToLower(), champs(1).Trim(), champs(2).Trim())))
Клас clientSOAP компілюється в «асемблер»:
dos>vbc /r:clientSOAP.dll testClientSOAP.vb
dos>dir
04/03/2004 08:46 6 913 clientSOAP.vb
04/03/2004 09:07 7 168 clientSOAP.dll
Потім клієнтський додаток testClientSoap компілюється за допомогою:
dos>vbc /r:clientSOAP.dll /r:system.dll testClientSOAP.vb
dos>dir
04/03/2004 09:08 2 711 testClientSOAP.vb
04/03/2004 09:08 4 608 testClientSOAP.exe
Ось приклад виконання без додаткових повідомлень:
dos>testclientsoap http://localhost/st/operations/operations.asmx
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 1 3
résultat=4
soustraire 6 7
résultat=-1
multiplier 4 5
résultat=20
diviser 1 2
résultat=0.5
x
syntaxe : [ajouter|soustraire|multiplier|diviser] a b
x 1 2
résultat=[fonction [x] indisponible : (ajouter, soustraire,multiplier,diviser)]
ajouter a b
résultat=[argument [a] incorrect (double)]
ajouter 1 b
résultat=[argument [b] incorrect (double)]
diviser 1 0
résultat=[division par zéro]
fin
Можна відстежувати мережеві обміни, запросивши «детальний» режим виконання:
dos>testClientSOAP http://localhost/operations/operations.asmx verbose
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser] a b
ajouter 4 8
--> POST /operations/operations.asmx HTTP/1.1
--> Host: localhost:80
--> Content-Type: text/xml; charset=utf-8
--> Content-Length: 321
--> Connection: Keep-Alive
--> SOAPAction: "st.istia.univ-angers.fr/ajouter"
-->
<-- HTTP/1.1 100 Continue
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<--
--> <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ajouter xmlns="st.istia.univ-angers.fr">
<a>4</a>
<b>8</b>
</ajouter>
</soap:Body>
</soap:Envelope>
<-- HTTP/1.1 200 OK
<-- Server: Microsoft-IIS/5.0
<-- Date: Thu, 04 Mar 2004 08:15:25 GMT
<-- X-Powered-By: ASP.NET
<-- X-AspNet-Version: 1.1.4322
<-- Cache-Control: private, max-age=0
<-- Content-Type: text/xml; charset=utf-8
<-- Content-Length: 346
<--
<-- <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-inst
ance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><ajouterResponse xmlns="st.istia.univ-angers.fr"><ajouterResult>12</ajouterResult></ajouterResponse></soap:Body></soap:Envelope>
résultat=12
fin
Тепер створимо графічний клієнт.
10.7.3. Графічний клієнт для Windows
Тепер ми будемо звертатися до нашого веб-сервісу за допомогою графічного клієнта, який також використовуватиме клас clientSOAP. Графічний інтерфейс матиме такий вигляд:
![]() |
Елементи управління такі:
№ | тип | назва | роль |
TextBox | txtURI | URI служби веб-операцій | |
Кнопка | btnOuvrir | відкриває з'єднання з веб-сервісом | |
Кнопка | btnFermer | закриває з'єднання з веб-сервісом | |
ComboBox | cmbFonctions | перелік операцій (додавання, віднімання, множення, ділення) | |
TextBox | txtA | аргументи функцій | |
TextBox | txtB | аргумент b функцій | |
TextBox | txtRésultat | результат функції(a,b) | |
Кнопка | btnCalculer | запускає обчислення функції(a,b) | |
TextBox | txtErreur | виводить повідомлення про стан з'єднання |
Існують деякі обмеження щодо роботи:
- кнопка btnOuvrir активна лише тоді, коли поле txtURI не порожнє і з’єднання ще не відкрито
- кнопка btnFermer активна лише тоді, коли відкрито з’єднання з веб-сервісом
- кнопка btnCalculer активна лише тоді, коли з’єднання відкрито, а поля txtA та txtB не порожні
- поля txtRésultat та txtErreur мають атрибут ReadOnly із значенням «true»
Клієнт спочатку встановлює з’єднання з веб-сервісом за допомогою кнопки [Ouvrir]:

Далі користувач може вибрати функцію та значення для a та b:





Далі наведено код програми. Ми опустили код форми, який тут не має значення.
'простори імен
Imports System
Imports System.Windows.Forms
' клас форми
Public Class FormClientSOAP
Inherits System.Windows.Forms.Form
' атрибути екземпляра
Dim client As clientSOAP ' client SOAP du service web operations
#Регіон «Код, згенерований Windows Form Designer»
Public Sub New()
MyBase.New()
'Цей виклик необхідний для Конструктора Windows Form.
InitializeComponent()
' інші ініціалізації
myInit()
End Sub
'Замінений метод Dispose форми для очищення списку компонентів.
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
....
End Sub
...
Private Sub InitializeComponent()
....
End Sub
#Кінець регіону
Private Sub myInit()
' ініціалізація форми
cmbFonctions.SelectedIndex = 0
btnOuvrir.Enabled = False
btnFermer.Enabled = True
btnCalculer.Enabled = False
End Sub
Private Sub txtURI_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtURI.TextChanged
' вміст поля введення змінився — встановлюємо стан кнопки «Відкрити»
btnOuvrir.Enabled = txtURI.Text.Trim <> ""
End Sub
Private Sub btnOuvrir_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnOuvrir.Click
' запит на встановлення з'єднання з веб-сервісом
Try
' створення об’єкта типу [clientSOAP]
client = New clientSOAP(txtURI.Text.Trim, False)
' стани кнопок
btnOuvrir.Enabled = False
btnFermer.Enabled = True
' URI більше не можна редагувати
txtURI.ReadOnly = True
' статус клієнта
txtErreur.Text = "Liaison au service web ouverte"
Catch ex As Exception
' сталася помилка — її відображають
txtErreur.Text = ex.Message
End Try
End Sub
Private Sub btnFermer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnFermer.Click
' закриття з'єднання з веб-сервісом
client.Close()
' стани кнопок
btnOuvrir.Enabled = True
btnFermer.Enabled = False
' URI
txtURI.ReadOnly = False
' стан клієнта
txtErreur.Text = "Liaison au service web fermée"
End Sub
Private Sub btnCalculer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalculer.Click
' обчислення функції f(a,b)
' очищення попереднього результату
txtRésultat.Text = ""
Try
txtRésultat.Text = client.executeFonction(cmbFonctions.Text, txtA.Text.Trim, txtB.Text.Trim)
Catch ex As Exception
' сталася мережева помилка
txtErreur.Text = ex.Message
' закриття з'єднання
btnFermer_Click(Nothing, Nothing)
End Try
End Sub
Private Sub txtA_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtA.TextChanged
' зміна значення A
btnCalculer.Enabled = txtA.Text.Trim <> "" And txtB.Text.Trim <> ""
End Sub
Private Sub txtB_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtB.TextChanged
' зміна значення B
txtA_TextChanged(Nothing, Nothing)
End Sub
' головний метод
Public Shared Sub main()
Application.Run(New FormClientSOAP)
End Sub
End Class
І тут клас clientSOAP приховує всі мережеві аспекти програми. Програма побудована таким чином:
- асемблерний файл clientSOAP.dll, що містить клас clientSOAP, було розміщено в папці проєкту
- графічний інтерфейс clientsoapgui.vb було створено за допомогою VS.NET, а потім скомпільовано у вікні DOS:
dos>vbc /r:system.dll /r:system.windows.forms.dll /r:system.drawing.dll /r:clientSOAP.dll clientsoapgui.vb
dos>dir
04/03/2004 09:13 7 168 clientSOAP.dll
04/03/2004 16:44 9 866 clientsoapgui.vb
04/03/2004 16:44 11 264 clientsoapgui.exe
Потім графічний інтерфейс було запущено за допомогою команди:
10.8. Проксі-клієнт
Нагадаємо, що ми щойно зробили. Ми створили проміжний клас, який інкапсулює мережевий обмін даними між клієнтом і веб-сервісом згідно зі схемою, наведеною нижче:
![]() |
Платформа .NET розвиває цю логіку далі. Як тільки стає відомо, до якого веб-сервісу потрібно звернутися, ми можемо автоматично згенерувати клас, який слугуватиме посередником для доступу до функцій веб-сервісу та приховає всю мережеву частину. Цей клас називають проксі для веб-сервісу, для якого він був згенерований.
Як згенерувати клас-проксі для веб-сервісу? Веб-сервіс завжди супроводжується описовим файлом у форматі XML. Якщо URI нашого веб-сервісу operations — це http://localhost/operations/operations.asmx, то його файл опису доступний за адресою http://localhost/operations/operations.asmx?wsdl, як показано на наступному знімку екрана:

Тут ми бачимо файл XML, який детально описує всі функції веб-сервісу, вказуючи для кожної з них тип і кількість параметрів, а також тип результату. Цей файл називають файлом WSDL сервісу, оскільки він використовує мову WSDL (Web Services Description Language). На основі цього файлу за допомогою інструменту wsdl можна згенерувати проксі-клас:
dos>wsdl http://localhost/operations/operations.asmx?wsdl /language=vb
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\devel\vbnet\poly\chap9\clientproxy\operations.vb'.
dos>dir
04/03/2004 17:17 6 663 operations.vb
Інструмент wsdl генерує вихідний файл VB.NET (опція /language=vb) з назвою класу, що реалізує веб-сервіс, у даному випадку operations. Розглянемо частину згенерованого коду:
'------------------------------------------------------------------------------
' <автоматично згенеровано>
' Цей код було згенеровано за допомогою інструменту.
' Версія середовища виконання: 1.1.4322.573
'
' Зміни в цьому файлі можуть спричинити некоректну роботу та будуть втрачені, якщо
' код буде згенеровано заново.
' </autogenerated>
'------------------------------------------------------------------------------
Option Strict Off
Option Explicit On
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
'
'Цей вихідний код був автоматично té gé né ré згенерований за допомогою wsdl, Версія=1.1.4322.573.
'
'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="operationsSoap", [Namespace]:="st.istia.univ-angers.fr")> _
Public Class operations
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol
'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/operations/operations.asmx"
End Sub
'<remarks/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute("st.istia.univ-angers.fr/ajouter", RequestNamespace:="st.istia.univ-angers.fr", ResponseNamespace:="st.istia.univ-angers.fr", Use:=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)> _
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
'<примітки/>
Public Function Beginajouter(ByVal a As Double, ByVal b As Double, ByVal callback As System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult
Return Me.BeginInvoke("ajouter", New Object() {a, b}, callback, asyncState)
End Function
'<примітки/>
Public Function Endajouter(ByVal asyncResult As System.IAsyncResult) As Double
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0),Double)
End Function
....
На перший погляд цей код здається дещо складним. Нам не потрібно розуміти його деталі, щоб ним користуватися. Спочатку розглянемо оголошення класу:
Клас має назву operations, що відповідає веб-сервісу, для якого він був створений. Він походить від класу SoapHttpClientProtocol:

Наш проксі-клас має єдиний конструктор:
Конструктор присвоює атрибуту url значення URL веб-сервісу, пов’язаного з проксі. Клас operations, наведений вище, сам не визначає атрибут url. Цей атрибут успадковується від класу, від якого походить проксі: System.Web.Services.Protocols.SoapHttpClientProtocol. Тепер розглянемо, що стосується методу ajouter:
Public Function ajouter(ByVal a As Double, ByVal b As Double) As Double
Dim results() As Object = Me.Invoke("ajouter", New Object() {a, b})
Return CType(results(0),Double)
End Function
Можна помітити, що він має той самий підпис, що й у веб-сервісі operations, де він був визначений так:
<WebMethod> _
Function ajouter(a As Double, b As Double) As Double
Return a + b
End Function 'ajouter
Спосіб взаємодії цього класу з веб-сервісом тут не показано. Ця взаємодія повністю забезпечується батьківським класом System.Web.Services.Protocols.SoapHttpClientProtocol. У проксі міститься лише те, що відрізняє його від інших проксі:
- URL відповідного веб-сервісу
- визначення методів відповідного сервісу.
Щоб використовувати методи веб-сервісу operations, клієнту потрібен лише раніше згенерований проксі-клас operations. Скомпілюємо цей клас у файл assembly:
Тепер напишемо консольний клієнт. Він викликається без параметрів і виконує запити, введені з клавіатури:
dos>testclientproxy
Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b
ajouter 4 5
résultat=9
soustraire 9 8
résultat=1
multiplier 10 4
résultat=40
diviser 6 7
résultat=0,857142857142857
toutfaire 10 20
résultats=[30,-10,200,0,5]
diviser 5 0
résultat=+Infini
fin
Код клієнта такий:
' простори імен
Imports System
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Public Module testClientProxy
' інтерактивно виконує команди, введені з клавіатури
' та надсилає їх до веб-сервісу operations
Public Sub Main()
' аргументів більше немає — URL веб-сервісу жорстко закодовано в проксі
' створення словника функцій веб-сервісу
Dim fonctions As String() = {"ajouter", "soustraire", "multiplier", "diviser", "toutfaire"}
Dim dicoFonctions As New Hashtable
Dim i As Integer
For i = 0 To fonctions.Length - 1
dicoFonctions.Add(fonctions(i), True)
Next i
' створюється об’єкт проксі-сервера operations
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' помилка підключення
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
' запити користувача вводяться з клавіатури
' у формі «function a b» — вони закінчуються командою «end»
Dim commande As String = Nothing ' commande tapée au clavier
Dim champs As String() = Nothing ' champs d'une ligne de commande
' запрошення користувачеві
Console.Out.WriteLine("Tapez vos commandes au format : [ajouter|soustraire|multiplier|diviser|toutfaire] a b" + ControlChars.Lf)
' деякі локальні дані
Dim erreurCommande As Boolean
Dim fonction As String
Dim a, b As Double
' цикл введення команд, набраних з клавіатури
While True
' спочатку немає помилок
erreurCommande = False
' зчитування команди
commande = Console.In.ReadLine().Trim().ToLower()
' закінчено?
If commande Is Nothing Or commande = "fin" Then
Exit While
End If
' розбиття команди на поля
champs = Regex.Split(commande, "\s+")
Try
' потрібно три поля
If champs.Length <> 3 Then
Throw New Exception
End If
' поле 0 має бути визнаною функцією
fonction = champs(0)
If Not dicoFonctions.ContainsKey(fonction) Then
Throw New Exception
End If
' поле 1 має бути дійсним числом
a = Double.Parse(champs(1))
' поле 2 має бути дійсним числом
b = Double.Parse(champs(2))
Catch
' неправильний запит
Console.Out.WriteLine("syntaxe : [ajouter|soustraire|multiplier|diviser] a b")
erreurCommande = True
End Try
' запит надсилається до веб-сервісу
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
End If
End While
End Sub
' відображення помилок
Public Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' припинення роботи з помилкою
Environment.Exit(exitCode)
End Sub
End Module
Ми розглядаємо лише код, пов'язаний із використанням проксі-класу. Спочатку створюється проксі-об'єкт operations:
' створено об’єкт проксі-операцій
Dim myOperations As operations = Nothing
Try
myOperations = New operations
Catch ex As Exception
' помилка підключення
erreur("L'erreur suivante s'est produite lors de la connexion au proxy dy service web : " + ex.Message, 2)
End Try
З клавіатури вводяться рядки функції a та b. На основі цієї інформації викликаються відповідні методи проксі:
' надсилання запиту до веб-сервісу
If Not erreurCommande Then
Try
Dim résultat As Double
Dim résultats() As Double
If fonction = "ajouter" Then
résultat = myOperations.ajouter(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "soustraire" Then
résultat = myOperations.soustraire(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "multiplier" Then
résultat = myOperations.multiplier(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "diviser" Then
résultat = myOperations.diviser(a, b)
Console.Out.WriteLine(("résultat=" + résultat.ToString))
End If
If fonction = "toutfaire" Then
résultats = myOperations.toutfaire(a, b)
Console.Out.WriteLine(("résultats=[" + résultats(0).ToString + "," + résultats(1).ToString + "," + _
résultats(2).ToString + "," + résultats(3).ToString + "]"))
End If
Catch e As Exception
Console.Out.WriteLine(("L'erreur suivante s'est produite : " + e.Message))
End Try
Тут вперше обробляється операція toutfaire, яка виконує ці чотири операції. До цього моменту її ігнорували, оскільки вона надсилає масив чисел, інкапсульований у конверт XML, який складніше обробляти, ніж прості відповіді XML інших функцій, що повертають лише один результат. Як бачимо, завдяки проксі-класу використання методу toutfaire не складніше, ніж використання інших методів. Додаток було скомпільовано у вікні командного рядка наступним чином:
dos>vbc /r:operations.dll /r:system.dll /r:system.web.services.dll testClientProxy.vb
dos>dir
04/03/2004 17:17 6 663 operations.vb
04/03/2004 17:24 7 680 operations.dll
04/03/2004 17:41 4 099 testClientProxy.vb
04/03/2004 17:41 5 632 testClientProxy.exe
10.9. Налаштування веб-служби
Веб-сервісу можуть знадобитися дані конфігурації для правильної ініціалізації. На сервері IIS ці дані можна розмістити у файлі з назвою web.config, який знаходиться в тій самій папці, що й веб-сервіс. Припустимо, що потрібно створити веб-сервіс, якому для ініціалізації потрібні два параметри: ім’я та вік. Ці два параметри можна розмістити у файлі web.config у такому вигляді:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Параметри ініціалізації розміщені в конверті XML:
Параметр ініціалізації з іменем P, що має значення V, буде оголошено за допомогою такого рядка:
<add key="P" value="V"/>
Як веб-сервіс отримує цю інформацію? Коли IIS завантажує веб-сервіс, він перевіряє, чи є в тій самій папці файл web.config. Якщо так, він його зчитує. Значення V параметра P отримується за допомогою інструкції:
де ConfigurationSettings — це клас у просторі імен System.Configuration.
Перевіримо цю техніку на прикладі наступного веб-сервісу:
<%@ WebService language="VB" class=personne %>
Imports System.Web.Services
imports System.Configuration
<WebService([Namespace] := "st.istia.univ-angers.fr")> _
Public Class personne
Inherits WebService
' атрибути
Private nom As String
Private age As Integer
' конструктор
Public Sub New()
' ініціалізація атрибутів
nom = ConfigurationSettings.AppSettings("nom")
age = Integer.Parse(ConfigurationSettings.AppSettings("age"))
End Sub
<WebMethod> _
Function id() As String
Return "[" + nom + "," + age.ToString + "]"
End Function
End Class
Веб-сервіс personne має два атрибути nom та age, які ініціалізуються в його конструкторі без параметрів на основі значень, зчитаних із конфігураційного файлу web.config сервісу personne. Цей файл має такий вигляд:
<configuration>
<appSettings>
<add key="nom" value="tintin"/>
<add key="age" value="27"/>
</appSettings>
</configuration>
Крім того, веб-сервіс має ідентифікатор <WebMethod> без параметрів, який просто повертає атрибути nom та age. Сервіс зареєстрований у вихідному файлі personne.asmx, який разом із файлом конфігурації розміщений у папці c:\inetpub\wwwroot\st\personne:
Прив’яжемо віртуальну папку IIS /config до попередньої фізичної папки. Запустимо IIS, а потім у браузері введемо URL-адресу http://localhost/config/personne.asmx служби personne:

Перейдемо за посиланням з унікальним ідентифікатором методу:

Метод id не має параметрів. Скористаємося кнопкою Appeler:

Ми успішно отримали інформацію, що міститься у файлі web.config сервісу.
10.10. Веб-сервіс IMPOTS
Повернемося до вже добре знайомої програми IMPOTS. Минулого разу, коли ми з нею працювали, ми перетворили її на віддалений сервер, до якого можна було звернутися через Інтернет. Тепер ми перетворимо її на веб-сервіс.
10.10.1. Веб-сервіс
Ми беремо за основу клас impôt, створений у розділі про бази даних, який формується на основі інформації, що міститься в базі даних ODBC:
' опції
Option Strict On
Option Explicit On
' простори імен
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Public Class impôt
' дані, необхідні для розрахунку податку
' походять із зовнішнього джерела
Private limites(), coeffR(), coeffN() As Decimal
' конструктор
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' перевіряється, чи всі 3 таблиці мають однаковий розмір
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' все гаразд
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' конструктор 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' ініціалізує три масиви меж: coeffR, coeffN на основі
' вмісту таблиці Timpots з бази даних ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN — це три стовпці цієї таблиці
' може викликати виняток
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' запит SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' таблиці для отримання даних
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' здійснюється спроба доступу до бази даних
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' створюється об’єкт команди
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' виконується запит
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Обробка отриманої таблиці
While myReader.Read()
' дані поточного рядка заносяться в масиви
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' звільнення ресурсів
myReader.Close()
impotsConn.Close()
' динамічні масиви записуються у статичні масиви
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
' розрахунок податку
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' розрахунок кількості часток
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' розрахунок оподатковуваного доходу та сімейного коефіцієнта
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' розрахунок податку
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' повернення результату
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
У веб-сервісі можна використовувати лише конструктор без параметрів. Тому конструктор класу матиме такий вигляд:
' конструктор
Public Sub New()
' ініціалізує три таблиці обмежень: coeffR, coeffN на основі
' вмісту таблиці Timpots у базі даних ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN — це три стовпці цієї таблиці
' може викликати виняток
'— тут отримуються параметри конфігурації служби
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
'— виконується обробка даних з бази даних
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
П’ять параметрів конструктора попереднього класу тепер зчитуються з файлу web.config веб-сервісу. Код вихідного файлу impots.asmx виглядає так. Він повторює більшу частину попереднього коду. Ми обмежилися виділенням фрагментів коду, що стосуються саме веб-сервісу:
<%@ WebService language="VB" class=impots %>
' створення веб-сервісу «impots»
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
Imports System.Configuration
Imports System.Web.Services
<WebService([Namespace]:="st.istia.univ-angers.fr")> _
Public Class impôt
Inherits WebService
' дані, необхідні для розрахунку податку
' походять із зовнішнього джерела
Private limites(), coeffR(), coeffN() As Decimal
Private OK As Boolean = False
Private errMessage As String = ""
' конструктор
Public Sub New()
' ініціалізує три масиви меж, coeffR, coeffN, на основі
' вмісту таблиці Timpots у базі даних ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN — це три стовпці цієї таблиці
' може викликати виняток
'— тут отримуються параметри конфігурації служби
Dim DSNimpots As String = ConfigurationSettings.AppSettings("DSN")
Dim Timpots As String = ConfigurationSettings.AppSettings("TABLE")
Dim colLimites As String = ConfigurationSettings.AppSettings("COL_LIMITES")
Dim colCoeffR As String = ConfigurationSettings.AppSettings("COL_COEFFR")
Dim colCoeffN As String = ConfigurationSettings.AppSettings("COL_COEFFN")
'— виконується обробка даних з бази даних
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
Dim myReader As OdbcDataReader ' lecteur de données Odbc
' запит SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' таблиці для отримання даних
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' спроба доступу до бази даних
Try
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' створюється об’єкт команди
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' виконується запит
myReader = sqlCommand.ExecuteReader()
' Обробка отриманої таблиці
While myReader.Read()
' дані поточного рядка заносяться в масиви
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' звільнення ресурсів
myReader.Close()
impotsConn.Close()
' динамічні масиви записуються у статичні масиви
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
' Все гаразд
OK = True
errMessage = ""
Catch ex As Exception
' помилка
OK = False
errMessage += "[" + ex.Message + "]"
End Try
End Sub
' розрахунок податку
<WebMethod()> _
Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' розрахунок кількості часток
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' розрахунок оподатковуваного доходу та сімейного коефіцієнта
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' розрахунок податку
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' повернення результату
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
' ідентифікатор
<WebMethod()> _
Function id() As String
' щоб перевірити, чи все гаразд OK
Return "[" + OK + "," + errMessage + "]"
End Function
End Class
Пояснимо кілька змін, внесених до класу impots, окрім тих, що були необхідні для перетворення його на веб-сервіс:
- читання бази даних у конструкторі може завершитися невдачею. Тому ми додали до нашого класу два атрибути та один метод:
- логічне значення OK дорівнює vrai, якщо базу даних вдалося прочитати, і faux — у протилежному випадку
- рядок errMessage містить повідомлення про помилку, якщо базу даних не вдалося прочитати.
- Метод id без параметрів дозволяє отримати значення цих двох атрибутів.
- Для обробки можливої помилки доступу до бази даних частина коду конструктора, що стосується цього доступу, була обведена блоком try-catch.
Файл конфігурації служби web.config має такий вигляд:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
</configuration>
Під час першої спроби завантаження служби impots компілятор повідомив, що не може знайти простір імен Microsoft.Data.Odbc, який використовується в директиві:
Після ознайомлення з документацією
- до web.config було додано директиву компіляції, щоб вказати, що слід використовувати збірку Microsoft.Data.odbc
- копія файлу microsoft.data.odbc.dll була розміщена в папці bin проекту. Ця папка систематично сканується компілятором веб-сервісу під час пошуку «асемблера».
Інші рішення здаються можливими, але тут вони не розглядалися. Отже, файл конфігурації став таким:
<configuration>
<appSettings>
<add key="DSN" value="mysql-impots" />
<add key="TABLE" value="timpots" />
<add key="COL_LIMITES" value="limites" />
<add key="COL_COEFFR" value="coeffr" />
<add key="COL_COEFFN" value="coeffn" />
</appSettings>
<system.web>
<compilation>
<assemblies>
<add assembly="Microsoft.Data.Odbc" />
</assemblies>
</compilation>
</system.web>
</configuration>
Вміст папки impots\bin:
Служба та її файл конфігурації були розміщені у impots:
Фізична папка веб-сервісу була пов’язана з віртуальною папкою /impots у IIS. Сторінка сервісу виглядає наступним чином:

Якщо перейти за посиланням id:

Якщо скористатися кнопкою Appeler:

У попередньому результаті відображаються значення атрибутів OK (true) та errMessage (""). У цьому прикладі база даних завантажилася правильно. Це не завжди було так, і саме тому ми додали метод id, щоб отримати доступ до повідомлення про помилку. Помилка полягала в тому, що ім’я бази даних DSN було визначено як DSN (користувацьке), тоді як його слід було визначити як DSN (системне). Це розрізнення здійснюється в 32-бітовому диспетчері джерел ODBC:
![]() |
Повернемося до сторінки сервісу:

Перейдемо за посиланням calculer:

Встановлюємо параметри виклику та виконуємо його:

Результат правильний.
10.10.2. Створити проксі-клас для сервісу impots
Тепер, коли ми маємо робочий веб-сервіс impots, можемо створити його проксі-клас. Нагадаємо, що він буде використовуватися клієнтськими додатками для прозорого доступу до веб-сервісу impots. Спочатку за допомогою утиліти wsdl генеруємо вихідний файл класу проксі, а потім компілюємо його в DLL-файл.
dos>wsdl /language=vb http://localhost/impots/impots.asmx
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.1.4322.573]
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Écriture du fichier 'D:\data\serge\devel\vbnet\poly\chap9\impots\impots.vb'.
D:\data\serge\devel\vbnet\poly\chap9\impots>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
dos>vbc /t:library /r:system.dll /r:system.web.services.dll /r:system.xml.dll impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4
pour Microsoft (R) .NET Framework version 1.1.4322.573
Copyright (C) Microsoft Corporation 1987-2002. Tous droits réservés.
dos>dir
09/03/2004 10:20 <REP> bin
09/03/2004 10:58 4 651 impots.asmx
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 10:19 431 web.config
10.10.3. Використання проксі-сервера з клієнтом
У розділі, присвяченому базам даних, ми створили консольний додаток для розрахунку податку:
dos>dir
27/02/2004 16:56 5 120 impots.dll
27/02/2004 17:12 3 586 impots.vb
27/02/2004 17:08 6 144 testimpots.exe
27/02/2004 17:18 3 328 testimpots.vb
dos>testimpots
pg DSNimpots tabImpots colLimites colCoeffR colCoeffN
dos>testimpots odbc-mysql-dbimpots impots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Програма testimpots тоді використовувала класичний клас impôt, що містився у файлі impots.dll. Код програми testimpots.vb був таким:
Option Explicit On
Option Strict On
' простори імен
Imports System
Imports Microsoft.VisualBasic
' тестова сторінка
Module testimpots
Sub Main(ByVal arguments() As String)
' інтерактивна програма для розрахунку податків
' користувач вводить три значення з клавіатури: одружений nbEnfants зарплата
' після чого програма відображає суму податку до сплати
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' перевірка параметрів програми
If arguments.Length <> 5 Then
' повідомлення про помилку
Console.Error.WriteLine(syntaxe1)
' кінець
Environment.Exit(1)
End If 'if
' отримуємо аргументи
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' створення об’єкта податку
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' нескінченний цикл
While True
' спочатку помилок немає
Dim erreur As Boolean = False
' запитуються параметри розрахунку податку
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' що робити?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' перевірка кількості аргументів у введеному рядку
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' перевірка правильності параметрів
' одружений
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' зарплата
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' параметри правильні — розраховується податок
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Ми повернемося до тієї самої програми, щоб тепер змусити її використовувати веб-сервіс impots через раніше створений проксі-клас impots. Нам доводиться дещо змінити код:
- тоді як оригінальний клас impôt мав конструктор з п’ятьма аргументами, проксі-клас impots має конструктор без параметрів. Як ми вже бачили, ці п’ять параметрів тепер зафіксовані у файлі конфігурації веб-сервісу.
- отже, більше немає потреби передавати ці п’ять параметрів як аргументи тестовій програмі
Новий код виглядає так:
Imports System
Imports Microsoft.VisualBasic
' тестова сторінка
Module testimpots
Public Sub Main(ByVal arguments() As String)
' інтерактивна програма розрахунку податку
' користувач вводить три значення з клавіатури: одружений nbEnfants заробітна плата
' після чого програма відображає суму податку до сплати
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' створення об’єкта «податок»
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' нескінченний цикл
Dim erreur As Boolean
While True
' спочатку помилок немає
erreur = False
' запитуються параметри розрахунку податку
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' що робити?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' перевірка кількості аргументів у введеному рядку
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
If Not erreur Then
' перевірка правильності параметрів
' одружений
Dim marié As String = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
Dim nbEnfants As Integer = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument nbEnfants incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' зарплата
Dim salaire As Integer = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument salaire incorrect : tapez un entier positif ou nul"))
erreur = True
End Try
' якщо параметри правильні — розраховується податок
If Not erreur Then Console.Out.WriteLine(("impôt=" + objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
У нас у одній папці знаходяться проксі impots.dll та вихідний код testimpots.
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
Ми компілюємо вихідний код testimpots.vb:
dos>vbc /r:impots.dll /r:microsoft.visualbasic.dll /r:system.web.services.dll /r:system.dll testimpots.vb
dos>dir
09/03/2004 11:28 <REP> bin
09/03/2004 11:09 5 120 impots.dll
09/03/2004 11:05 3 364 impots.vb
09/03/2004 11:35 5 632 testimpots.exe
09/03/2004 11:34 3 396 testimpots.vb
09/03/2004 10:19 431 web.config
а потім запускаємо:
dos>testimpots
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Ми дійсно отримуємо очікуваний результат.





