Skip to content

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 відобразить таку сторінку:

Image

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

Image

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

Image

Netscape дійсно отримав те саме, що й Internet Explorer, але відобразив це по-іншому. Далі ми будемо використовувати Internet Explorer для створення знімків екрана.

10.3. Перший веб-сервіс

Ми ознайомимося з веб-сервісами на прикладі надзвичайно простого сервісу, який має три варіанти.

10.3.1. Версія 1

Для цієї першої версії ми будемо використовувати VS.NET, який має ту перевагу, що дозволяє створити каркас веб-сервісу, готовий до негайного використання. Зрозумівши цю архітектуру, ми зможемо почати діяти самостійно. Це буде предметом наступних версій.

За допомогою VS.NET створимо новий проєкт з опцією [Fichier/Nouveau/Projet]:

Image

Зверніть увагу на наступні моменти:

  • тип проєкту — 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:

Image

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

Image

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

Image

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

Image

Слід звернути увагу на кілька моментів:

  • файл називається 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:

Public Class Service1
    Inherits System.Web.Services.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:

Image

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

Image

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

Image

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

Image

Відкриємо його за допомогою текстового редактора (Notepad або іншого). Отримаємо такий вміст:

<%@ WebService Language="vb" Codebehind="Service1.asmx.vb" Class="demo.Bonjour" %>

Файл містить просту директиву для сервера 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:

Image

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

Image

Ой... це досить незрозумілий XML. Проте звернімо увагу на URL

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

Image

Ми навмисно скоротили отриману сторінку, щоб не перевантажувати нашу демонстрацію. Знову звернімо увагу на отриманий URL-адресу:

http://localhost/polyvbnet/demo/Service1.asmx?op=Bonjour

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

Image

Це знову 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 (ця назва є обов’язковою):

dos>dir bin
02/03/2004  18:12                3 072 demo2.dll

Тепер створюємо файл demo2.asmx. Саме його викликатимуть веб-клієнти. Його вміст такий:

<%@ WebService Language="vb" class="Bonjour2,demo2"%>

Ми вже зустрічали цю директиву. Вона вказує, що:

  • клас веб-служби називається Bonjour2 і знаходиться в збірці demo2.dll. IIS шукатиме цю збірку в різних місцях, зокрема в папці bin веб-служби. Саме тому ми розмістили там збірку demo2.dll.

Тепер ми можемо провести різні тести. Перевіряємо, чи IIS активний, і за допомогою браузера звертаємося за URL-адресою http://localhost/polyvbnet/demo2/demo2.asmx:

Image

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

Image

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

Image

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

Image

Ми отримуємо результат виклику методу getBonjour веб-сервісу. Тепер ми знаємо, як створити веб-сервіс без vs.net. Надалі ми не будемо зважати на те, як побудований веб-сервіс, а зосередимося лише на основних файлах.

10.3.3. Версія 3

Дві попередні версії веб-сервісу [Bonjour] використовували два файли:

  • файл .asmx, точка входу веб-сервісу
  • файл .vb — вихідний код веб-сервісу

Тут ми покажемо, що можна обійтися лише одним файлом .asmx. Код сервісу demo3.asmx такий:

<%@ WebService Language="vb" class="Bonjour3"%>

Imports System.Web.Services

<System.Web.Services.WebService(Namespace:="st.istia.univ-angers.fr")> _
Public Class Bonjour3
    Inherits System.Web.Services.WebService

    <WebMethod()> Public Function getBonjour() As String
        Return "bonjour en version3 !"
    End Function
End Class

Ми бачимо, що вихідний код сервісу тепер міститься безпосередньо у вихідному файлі demo3.asmx. Директива

<%@ WebService Language="vb" class="Bonjour3"%>

більше не посилається на клас у зовнішньому збірнику, а на клас, що міститься в тому самому вихідному файлі. Помістимо його в папку <IISroot>\polyvbnet\demo3:

Image

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

Image

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

Image

10.3.4. Версія 4

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

Image

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

Image

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

Image

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

Image

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

Image

10.3.5. Висновок

Ми продемонстрували кілька способів створення веб-сервісу. Надалі ми будемо використовувати метод із варіанту 3 для створення сервісу та метод із варіанту 4 для його локалізації. Таким чином, нам не знадобиться VS.NET. Проте варто відзначити корисність використання VS.NET, оскільки він допомагає у налагодженні. Існують безкоштовні інструменти для розробки веб-додатків, зокрема продукт WebMatrix, спонсором якого є Microsoft, і який можна знайти на URL [http://www.asp.net/webmatrix]. Це чудовий інструмент для початку веб-програмування без попередніх інвестицій.

10.4. Веб-сервіс операцій

Розглянемо веб-сервіс, який пропонує п’ять функцій:

  1. додати(a,b), що поверне a+b
  2. віднімати(a,b), що поверне a-b
  3. multiplier(a,b), що поверне a*b
  4. diviser(a,b), що поверне a/b
  5. «все»(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:

Image

До цієї фізичної папки ми прив’язуємо віртуальну папку IIS [operations]:

Зайдемо на сервіс за допомогою браузера. URl, який потрібно запросити, — це [http://localhost/operations/operations.asmx]:

Image

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

Image

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

      <WebMethod>  _
      Function ajouter(a As Double, b As Double) As Double
         Return a + b
      End Function 

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

Image

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

Image

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

Image

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

Image

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

Image

У будь-якому випадку відповідь сервера має такий вигляд:

<?xml version="1.0" encoding="utf-8"?>
[réponse au format XML]
  • відповідь має формат XML
  • рядок 1 є стандартним і завжди присутній у відповіді
  • наступні рядки залежать від типу результату (double, ArrayOfDouble), кількості результатів та простору імен веб-сервісу (тут — st.istia.univ-angers.fr).

Існує кілька способів звернення до веб-сервісу та отримання відповіді. Повернемося до URL цього сервісу:

Image

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

Image

Image

Image

Ці два методи доступу до функцій веб-сервісу називаються відповідно: HTTP-POST та SOAP. Тепер розглянемо їх по черзі.

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

10.5. Клієнт HTTP-POST

Ми дотримуємося методу, запропонованого веб-сервісом:

Image

Прокоментуємо те, що написано. Насамперед веб-клієнт повинен надіслати такі заголовки HTTP:

POST /operations/operations.asmx/ajouter HTTP/1.1
Веб-клієнт надсилає запит POST на URL /operations/operations.asmx/ajouter згідно з протоколом HTTP версії 1.1
HOST: localhost
Вказується цільова машина запиту. У даному випадку — localhost. Цей заголовок став обов’язковим у версії 1.1 протоколу HTTP
Content-Type: application/x-www-form-urlencoded
Тут вказується, що після заголовків HTTP будуть надіслані додаткові параметри у форматі urlencoded. Цей формат замінює певні символи на їхні шістнадцяткові коди.
Content-length: 7
Це кількість символів у рядку параметрів, який буде надіслано після заголовків 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 символів, які, за його словами, він хотів надіслати. Що ми робимо:

a=2&b=3

Тут слід звернути увагу, що наш 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 веб-сервісу:

dos>httpPost2 http://localhost/operations/operations.asmx

Потім клієнт зчитує команди, введені з клавіатури, та виконує їх. Вони мають такий формат:

fonction a b

де «функція» — це функція веб-сервісу, що викликається (додавання, віднімання, множення, ділення), а a та b — значення, над якими ця функція буде працювати. Наприклад:

ajouter 6 7

Після цього клієнт надсилає необхідний запит 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(), для зчитування відповіді сервера, оскільки відомо, що вона складається з низки рядків, останній з яких не закінчується символом кінця рядка. Отримавши всю відповідь сервера, клієнт аналізує її, щоб знайти результат запитуваної операції та вивести його на екран:

[résultat=13]

Розглянемо код нашого клієнта:


' простори імен
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

У заголовку

--> Content-Length: 7

необхідно вказати розмір параметрів, які клієнт надішле за заголовками HTTP:

--> a=6&b=7

Для цього використовується такий код:


        ' формування рядка запиту
        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))

Після ознайомлення з цією першою відповіддю клієнт повинен надіслати свої параметри:

--> a=6&b=7

Для цього він використовує такий код:

        ' відправлення параметрів запиту
        OUT.Write(requête)
        ' відлуння
        Console.Out.WriteLine(("--> " + requête))

Після цього сервер надішле свою відповідь. Вона складається з двох частин:

  1. заголовків HTTP, що закінчуються порожнім рядком
  2. відповідь у форматі 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 символів розбивається на рядки тексту для потреб відстеження екрану. Серед цих рядків шукаємо рядок із результатом:

<-- <double xmlns="st.istia.univ-angers.fr">13</double>

— знову ж таки за допомогою регулярного виразу. Після знаходження результату його виводиться на екран.

[résultat=13]

Кінець коду клієнта виглядає так:


        ' побудова регулярного виразу для отримання результату
        ' у потоці відповіді веб-сервера
        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:

Image

Image

Запит клієнта — це запит POST. Отже, ми побачимо деякі механізми, що вже використовувалися попереднім клієнтом. Головна відмінність полягає в тому, що тоді як клієнт HTTP-POST надсилав параметри a та b у формі

    a=A&b=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>

Код:

1
2
3
4
        ' відправлення параметрів запиту
        OUT.Write(requêteSOAP)
        ' відлуння
        Console.Out.WriteLine(("--> " + requêteSOAP))

Після цього сервер надішле остаточну відповідь:

<-- 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

Конструктору передаються два параметри:

  1. URI веб-сервісу, до якого він повинен підключитися
  2. логічне значення verbose, яке, по суті, вимагає, щоб мережеві обміни відображалися на екрані; інакше вони не будуть відображатися.

Під час створення об’єкта формуються потоки IN для читання з мережі, OUT для запису в мережу, а також словник функцій, що підтримуються сервісом. Після створення об’єкта відкривається з’єднання «клієнт-сервер», і його потоки IN та OUT стають доступними для використання.

Метод Close дозволяє закрити з’єднання з сервером.

Метод ExecuteFonction — це той самий метод, який ми написали для розглянутого клієнта SOAP, за винятком кількох дрібних деталей:

  1. Параметри uri, IN та OUT, які раніше передавалися як параметри методу, більше не потрібно передавати, оскільки тепер вони є атрибутами екземпляра, доступними для всіх методів цього екземпляра
  2. Метод ExecuteFonction, який раніше повертав тип void і виводив результат функції на екран, тепер повертає цей результат, а отже, тип string.

Зазвичай клієнт використовуватиме клас clientSOAP наступним чином:

  1. створення об’єкта clientSOAP, який встановить з’єднання з веб-сервісом
  2. повторне використання методу executeFonction
  3. припинення з'єднання з веб-сервісом за допомогою методу 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

Тепер клієнт став значно простішим, і в ньому немає жодних мережевих операцій. Клієнт приймає два параметри:

  1. URI веб-сервісу operations
  2. опціональний ключовий параметр 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. Графічний інтерфейс матиме такий вигляд:

Елементи управління такі:

тип
назва
роль
1
TextBox
txtURI
URI служби веб-операцій
2
Кнопка
btnOuvrir
відкриває з'єднання з веб-сервісом
3
Кнопка
btnFermer
закриває з'єднання з веб-сервісом
4
ComboBox
cmbFonctions
перелік операцій (додавання, віднімання, множення, ділення)
5
TextBox
txtA
аргументи функцій
6
TextBox
txtB
аргумент b функцій
7
TextBox
txtRésultat
результат функції(a,b)
8
Кнопка
btnCalculer
запускає обчислення функції(a,b)
9
TextBox
txtErreur
виводить повідомлення про стан з'єднання

Існують деякі обмеження щодо роботи:

  • кнопка btnOuvrir активна лише тоді, коли поле txtURI не порожнє і з’єднання ще не відкрито
  • кнопка btnFermer активна лише тоді, коли відкрито з’єднання з веб-сервісом
  • кнопка btnCalculer активна лише тоді, коли з’єднання відкрито, а поля txtA та txtB не порожні
  • поля txtRésultat та txtErreur мають атрибут ReadOnly із значенням «true»

Клієнт спочатку встановлює з’єднання з веб-сервісом за допомогою кнопки [Ouvrir]:

Image

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

Image

Image

Image

Image

Image

Далі наведено код програми. Ми опустили код форми, який тут не має значення.


'простори імен
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

Потім графічний інтерфейс було запущено за допомогою команди:

dos>clientsoapgui

10.8. Проксі-клієнт

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

Платформа .NET розвиває цю логіку далі. Як тільки стає відомо, до якого веб-сервісу потрібно звернутися, ми можемо автоматично згенерувати клас, який слугуватиме посередником для доступу до функцій веб-сервісу та приховає всю мережеву частину. Цей клас називають проксі для веб-сервісу, для якого він був згенерований.

Як згенерувати клас-проксі для веб-сервісу? Веб-сервіс завжди супроводжується описовим файлом у форматі XML. Якщо URI нашого веб-сервісу operations — це http://localhost/operations/operations.asmx, то його файл опису доступний за адресою http://localhost/operations/operations.asmx?wsdl, як показано на наступному знімку екрана:

Image

Тут ми бачимо файл 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
....

На перший погляд цей код здається дещо складним. Нам не потрібно розуміти його деталі, щоб ним користуватися. Спочатку розглянемо оголошення класу:

Public Class operations
    Inherits System.Web.Services.Protocols.SoapHttpClientProtocol

Клас має назву operations, що відповідає веб-сервісу, для якого він був створений. Він походить від класу SoapHttpClientProtocol:

Image

Наш проксі-клас має єдиний конструктор:

    Public Sub New()
        MyBase.New
        Me.Url = "http://localhost/operations/operations.asmx"
    End Sub

Конструктор присвоює атрибуту 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>vbc /t:library /r:system.web.services.dll /r:system.xml.dll /r:system.dll operations.vb
dos>dir
04/03/2004  17:17                6 663 operations.vb
04/03/2004  17:24                7 680 operations.dll

Тепер напишемо консольний клієнт. Він викликається без параметрів і виконує запити, введені з клавіатури:

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:

<configuration>
    <appSettings>
...
    </appSettings>
</configuration>

Параметр ініціалізації з іменем P, що має значення V, буде оголошено за допомогою такого рядка:


        <add key="P" value="V"/>

Як веб-сервіс отримує цю інформацію? Коли IIS завантажує веб-сервіс, він перевіряє, чи є в тій самій папці файл web.config. Якщо так, він його зчитує. Значення V параметра P отримується за допомогою інструкції:

        String P=ConfigurationSettings.AppSettings["V"];

де 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:

dos>dir
09/03/2004  08:25               632 personne.asmx
09/03/2004  08:08               186 web.config

Прив’яжемо віртуальну папку IIS /config до попередньої фізичної папки. Запустимо IIS, а потім у браузері введемо URL-адресу http://localhost/config/personne.asmx служби personne:

Image

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

Image

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

Image

Ми успішно отримали інформацію, що міститься у файлі 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, який використовується в директиві:

Imports 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:

dos>dir impots\bin
30/01/2002  02:02           327 680 Microsoft.Data.Odbc.dll

Служба та її файл конфігурації були розміщені у impots:

dos>dir impots
09/03/2004  10:13             4 669 impots.asmx
09/03/2004  10:19               431 web.config

Фізична папка веб-сервісу була пов’язана з віртуальною папкою /impots у IIS. Сторінка сервісу виглядає наступним чином:

Image

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

Image

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

Image

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

Повернемося до сторінки сервісу:

Image

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

Image

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

Image

Результат правильний.

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

Ми дійсно отримуємо очікуваний результат.