Статья: Web Services и MS Visual FoxPro Часть 1

В этой статье :

Для чего написана эта статья.

В последнее время все чаще можно услышать разговоры о том, что FoxPro - это устаревшая технология 80-х, что программисты, которые остались верны данной среде разработки "отстали от жизни", что у них "нет будущего" и, вообще, "они ничего не понимают в развитии IT индустрии"...  Прочитав эту статью, Вы научитесь применять FoxPro в глобальных масштабах всего мира. Для Вас будет абсолютно без разницы, для чего писать приложения - для маленькой задачи на одном компьютере, для целого предприятия, работающего в среде Интранет,  для  крупной компании,  спрятавшей своих клиентов за навороченными системами защиты "firewall", или для ее филиалов, разбросанных по всему миру, и имеющих разнородный парк серверов, на которых крутятся разношерстные операционные системы от Windows 2000, 2003 до Unix. Web Services размывают наши представления о различиях между файл - серверными и клиент - серверными приложениями. Теперь Вы можете передавать между компьютерами только минимально необходимый набор информации, применяя хранимые процедуры и транзакции на удаленном сервере данных, тем самым лишая многих критиков FoxPro основных аргументов против применения данной среды в современных надежных и легко масштабируемых приложениях (так как в этом случае Вам не надо ничего будет менять в клиентском приложении - только изменить немного WS).

Причем, Вы неожиданно откроете для себя, что такие феноменальные возможности появились у старого и доброго FoxPro еще много лет назад в 7 версии, намного опережая продвинутые продукты конкурентов и даже многие продукты самого Microsoft (например, SQL Server 2000). Проблема была только в Вас самих - Вы этим не пользовались. Мы очень надеемся, что данная статья Вам поможет пересмотреть Ваши взгляды на Ваш привычный процесс разработки приложений, дав толчок к внедрению новых технологий у Ваших клиентов и, в конечном итоге, к upgrade Ваших skills. В данной публикации мы попытаемся "объять необъятное", разумеется, с Вашей помощью в виде отзывов, которые помогут улучшить понимание данного материала и сделать его более понятным для тех, кто только начинает свой путь по нелегкой тропе к вершинам программистского мастерства.

Что такое Web Services.

В начале моей нелегкой жизни профессионального программиста   клиенты часто просили меня рассказать, как работает написанная мной программа. Мои объяснения сводились примерно к следующему: “Вы нажимаете кнопку "вывести на экран справочник товара", и компьютер посылает запрос к серверу в виде строки, в которой указывает то, что Вам надо. Далее сервер обрабатывает Ваш результат, формирует ответную строку и посылает ее назад. Моя программа принимает ответ и показывает Вам в удобочитаемом виде этот самый справочник...” Все были довольны: клиент тем, что понял, а я, что удалось ему объяснить.

Только через несколько лет я обнаружил, что это была голая ложь по отношению к файл – серверу. Даваемое мной описание подходит больше под КС (клиент-серверное приложение), а в идеале, на приложение, где источником данных является Web Service. Так что же все-таки это непонятное импортное словосочетание означает на самом деле? Цель моей скромной статьи - приоткрыть завесу тайны и мистики над этой технологией, которой пророчат большое будущее как Microsoft, так и их конкуренты.

С технической точки зрения Web Services (сокращенно WS) - это DLL, установленная на Вашем Web Server, которая может быть вызвана другими компьютерами, находящимися как во внутренней сети Intranet, так и во внешней сети Internet. Методы в этой DLL могут делать то же самое, что и COM сервер. Если объяснять простыми словами, то Web Service  просто очень маленькая программка, которая прослушивает определенный порт в IIS (Internet Information Server, или в простонародии “Сервер WEB”). Как мы, наверное, с Вами знаем, каждый WEB сервер имеет адрес IP (например,  у локального 127.0.0.1) и порт (для Вашего WEB Server по умолчанию 80 ). Когда мы с Вами набираем, например, http://www.sergey.co.uk/ , то наш Browser (программа, с помощью которой Вы “гуляете по Интернету”) посылает Ваш запрос к DNS серверу, который говорит, что для данного адреса будет следуюший IP -  62.105.65.69,  и Вы направляетесь прямо туда, где находится мой сайт. Если теперь указать имя конкретного файла, имеющего расширение .WSDL, то IIS поймет, что запрос предназначен для WS, разбудит его, и последний начнет обработку нашего запроса, как самая обычная программа, запущенная удаленно – то есть на чужом удаленном (remote) компьютере. Передав параметры WS, Вы можете запросить только интересующую Вас информацию, например, курсы валют на определенную дату, список книг в библиотеке по автору, список неоплаченных счетов... Вот собственно и все. Просто, как "Автомат Калашникова".

Немного об XML и “заточки под него VFP”.
Итак, если мы немного знакомы с Интернет - технологиями, то обмен данными там ведется с помощью протокола HTTP, который, как известно, ничего кроме символьных строк не понимает... Как же "выкрутиться" из данной ситуации, ведь нам надо передавать цифры, спецсимволы и даже картинки с двоичными данными? Для этого люди придумали XML (Extensible Markup Language). Для тех, кто любит изучать вещи досконально, советую посетить страничку http://www.w3.org/XML/ , где можно узнать последние новости об этом языке. Мне, как программисту баз данных, достаточно знать только то, что каждое выражение (как и в случае c HTML) окружено тэгами, причем один должен быть открывающим, а второй обязательно должен закрывать выражение. Рассмотрим простой пример в FoxPro, в котором полная поддержка XML появилась еще в 7 версии (просто выделяете данный текст программы и коприруете его в открытое окно редактора FoxPro):
* создаем простую таблицу
* не забыв при этом в директории проекта иметь файл config.fpw
* где есть строка: codepage=1251
* в этом случае все создаваемые нами файлы будут иметь
* корректную кодовую страницу для работы с русским языком
SET SAFETY OFF
CREATE TABLE TESTXML.DBF FREE  (text1 c(40), number1 N(4), memo1 M)
* вносим несколько значений
INSERT INTO TESTXML (text1,number1,memo1) VALUES ;
  ('Первая строка', 1233,'Строка в Memo поле первая'+CHR(13)+CHR(10)+;
  'Вот и вторая строка в Memo')
INSERT INTO TESTXML (text1,number1,memo1) VALUES ;
  ('Еще одна строка', 1963,'FoxPro отличает от других средства разработки'+CHR(13)+CHR(10)+;
  'приложений глубокая продуманность языка для реальной жизни.')
*
* создаем файл XML из нашей таблицы
* думаю, что описание команды CURSORTOXML Вы сможете прочитать в Help
* Остановлюсь только на параметрах, примененных мною
* "TESTXML" - имя открытой таблицы
* "myXMLfile.xml" - имя файла, в который мы хотим записать полученный результат
*  1 - создать "Element-centric XML" в этом случае получаем "правильный" XML файл
*  +8 - обернуть Memo поле в CDATA - это значит, что можно передавать и двоичные значения
*  +16 - использовать кодовую страницу курсора (в нашем случае 1251 - Russian Windows)
*  +512 - вывести результат в файл
*  0 - берем все записи
*  "1" - схема данных должна быть встроена в файл. То есть как бы словарь данных
*        передается вместе с данными и в месте приема мы их правильно расшифровываем.
CURSORTOXML("TESTXML","myXMLfile.xml",1,8+16+512,0,"1")
* теперь просмотрим полученный файл
MODIFY FILE ("myXMLfile.xml")
* почистим за собой
CLOSE DATABASES
DELETE FILE TESTXML.DBF
DELETE FILE TESTXML.FPT
DELETE FILE myXMLfile.XML
example01.prg
В результате работы программы получаем следующий файл, который Вы должны увидеть на своих экранах:
<?xml version = "1.0" encoding="Windows-1251" standalone="yes"?>
<VFPData>
 <xsd:schema id="VFPData" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
        <xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xsd:element name="VFPData" msdata:IsDataSet="true">
   <xsd:complexType>
    <xsd:choice maxOccurs="unbounded">
     <xsd:element name="testxml" minOccurs="0" maxOccurs="unbounded">
      <xsd:complexType>
       <xsd:sequence>
        <xsd:element name="text1">
         <xsd:simpleType>
         <xsd:restriction base="xsd:string">
          <xsd:maxLength value="40"/>
          </xsd:restriction>
         </xsd:simpleType>
        </xsd:element>
        <xsd:element name="number1">
         <xsd:simpleType>
          <xsd:restriction base="xsd:decimal">
           <xsd:totalDigits value="4"/>
           <xsd:fractionDigits value="0"/>
          </xsd:restriction>
         </xsd:simpleType>
        </xsd:element>
        <xsd:element name="memo1">
         <xsd:simpleType>
          <xsd:restriction base="xsd:string">
           <xsd:maxLength value="2147483647"/>
          </xsd:restriction>
         </xsd:simpleType>
        </xsd:element>
       </xsd:sequence>
      </xsd:complexType>
     </xsd:element>
    </xsd:choice>
    <xsd:anyAttribute namespace="http://www.w3.org/XML/1998/namespace" processContents="lax"/>
   </xsd:complexType>
  </xsd:element>
 </xsd:schema>
 <testxml>
  <text1>Первая строка</text1>
  <number1>1233</number1>
  <memo1><![CDATA[Строка в Memo поле первая
Вот и вторая строка в Memo]]></memo1>
 </testxml>
 <testxml>
  <text1>Еще одна строка</text1>
  <number1>1963</number1>
  <memo1><![CDATA[FoxPro отличает от других средств разработки
приложений глубокая продуманность языка для реальной жизни.]]></memo1>
 </testxml>
</VFPData>

Полученный результат грубо можно разбить на три части. В первой части (строке) идет объяснение, что это такое. Из всего этого нас интересует только encoding="Windows-1251", что собственно говорит о том, что мы правильно получили кодировку русского языка, и при приеме информации у нас не должно возникнуть проблем.

Во второй части идет описание формата данных. Можно очень грубо провести аналогию с заголовком таблиц DBF самого Visual FoxPro. Типы данных могут немного отличаться от привычных нам, так, например, для поля Memo - это все тот же string, но с очень большой длиной. Одно из преимуществ формата XML эксперты называют "human readable" - то есть возможность человека читать его  и понимать. Я очень надеюсь, что Вы принадлежите к человечеству и, соответственно, сможете понять, что там есть.

Третья часть, собственно, данные. Они будут зависеть от того, что Вы туда включили. В общем-то ничего сложного, я только хочу обратить внимание на загадочные буквы CDATA вокруг Memo полей. FoxPro позволяет зашифровывать данные на лету в формат base64,  который позволяет "оборачивать" двоичные файлы и передавать их в виде все тех же привычных для HTTP символов. По примерно такому же принципу мы передаем в электронной почте всевозможные картинки, музыку и даже видео.

Совсем не плохо для практически всего одной команды FoxPro!

Простой пример получения данных посредством WS из Интернета.

Давайте теперь, наконец, сделаем что-то практически, что - то "крутое" и глобальное. Для следующего примера Вам понадобится связь с внешним миром, то есть Internet. Подойдет даже очень медленное модемное соединение. Усложним пример тем, что источником данных будет MS SQL Server, а Web Services будут построены на основе любимого и  революционного  детища компании Microsoft - ASP.NET (если иметь в виду некоторые принципы, заложенные в  основу ASP.NET, как, например, создание предкомпилированного кода, то эта возможность была уже применена в FoxBase 20 лет назад). То есть данный пример покажет истинную независимость Вас, как разработчика, от среды.

Итак, вот код, который Вам предлагается скопировать в окно редактора FoxPro и запустить:

***********
* пример получения новостей с сайта www.sergey.co.uk
***********
SET TALK OFF
* создаем объект на основе пакета SOAP 3.0 
o=CREATEOBJECT("MSSoap.SoapClient30")
* соединяемся с удаленным источником данных
* в качестве параметра стандартного метода MSSoapInit задается URL
o.MSSoapInit("http://www.sergey.co.uk/WebModules/NewsManaer/Headlines.asmx?WSDL")
* вызываем функцию данного Web Service как самую обычную функцию FoxPro
* и не важно, что объект расположен от Вас за тысячи километров
* Данная функция запрашивет заголовки новостей
*o.getheadlines(1)
loexception=NULL && устанавливаем объект, в котором будем отлавливать ошибки
tt=''
* применяем конструкцию TRY..CATCH чтобы возможные проблемы не повлияли на наше
* приложение. В принципе все можно было "упрятать" в эту конструкцию
TRY
  tt=o.getheadlines(1)
CATCH TO loexception && если будет ошибка, то программа нам ее распечатает
  ? loexception.MESSAGE
  ? loexception.ERRORNO
ENDTRY

*? tt && если убрать здесь комментарий то на экране Вы увидите в случае успеха тип (Object)

IF ISNULL(loexception) && если не было ошибок, то продолжим
  lcXML=tt.ITEM(0).parentnode.XML && 
*  ? lcXML && если убрать здесь комментарий то на экране Вы увидите принятый Вами XML файл
*  STRTOFILE(lcXML,'aaa.xml') && можно записать XML в файл на диск
* для дальнейшей работы создаем XML Adapter
  LOCAL oXA AS XMLADAPTER 
  oXA = CREATEOBJECT("XMLAdapter")
* загружаем полученную информацию в созданный адаптер  
  oXA.LOADXML(lcXML,.F.,.T.) 
  IF USED('NEWS')
    USE IN NEWS
  ENDIF
* создаем таблицу из адаптера с названием NEWS
  oXA.TABLES[1].TOCURSOR(.F.,"NEWS")
  BROWSE && смотрим, что получили

ENDIF
* как обычно чистим за собой
RELEASE o

example02.prg

Программа собственно выполняет простую функию - запрашивает данные из удаленного источника и представляет в привычном для нас виде - в виде таблицы, с которой нам, программистам, уже можно делать все, что пожелаем. Комментарии объясняют для чего нам нужны основные операторы. Нами применены некоторые новые конструкции, но думаю, что они у Вас не вызовут проблем с пониманием. Если же нет, то пишите - поговорим о них более подробно .

В результате работы программы получим следующую "картинку":

Окно Browse после выполнения программы
Обращу Ваше внимание только на одну деталь: все символьные поля имеют тип Memo. Это связано с тем, что в ASP.NET WS надо как и в SOAP указывать обязательность передачи схемы данных внутри XML файла. Нами этого не было сделано, и FoxPro по умолчанию поставил для строковой переменной максимально возможную длину. Решить эту проблему можно двумя путями: попросить разработчиков Web Servise включать схему в XML файл, или если это невозможно (что, как правило, бывает в нашей жизни), то поработать немного самим и изменить структуру полученной таблицы в месте приема. Есть еще вариант с внешним XSD файлом, но я предлагаю опустить его, чтобы не усложнять нашу и без того сложную жизнь.
Под влиянием общественности родился еще один пример, который может даже носить некоторый практический характер - получение курсов валют, публекуемых Центральным Банком России. Пример очень похож на предыдущий, так что копируйте и запускайте:
***********
* пример получения курса валют ЦБ РФ с сайта http://web.cbr.ru/
***********
SET TALK OFF
* создаем объект на основе пакета SOAP 3.0
o=CREATEOBJECT("MSSoap.SoapClient30")
* соединяемся с удаленным источником данных
* в качестве параметра стандартного метода MSSoapInit задается URL
o.MSSoapInit("http://www.cbr.ru/DailyInfoWebServ/DailyInfo.asmx?WSDL")
* вызываем функцию данного Web Service как самую обычную функцию FoxPro
* Данная функция запрашивет курсы валют как DataSet
*o.DailyInfo()
loexception=NULL && устанавливаем объект, в котором будем отлавливать ошибки
tt=''
* применяем конструкцию TRY..CATCH чтобы возможные проблемы не повлияли на наше
* приложение. В принципе все можно было "упрятать" в эту конструкцию
TRY
  tt=o.GetCursOnDate(DATE()) && параметр - текущая дата date()
CATCH TO loexception && если будет ошибка, то программа нам ее распечатает
  ? loexception.MESSAGE
  ? loexception.ERRORNO
ENDTRY

*? tt && если убрать здесь комментарий то на экране Вы увидите в случае успеха тип (Object)

IF ISNULL(loexception) && если не было ошибок, то продолжим
  lcXML=tt.ITEM(0).parentnode.XML &&
*  ? lcXML && если убрать здесь комментарий то на экране Вы увидите принятый Вами XML файл
*  STRTOFILE(lcXML,'aaa.xml') && можно записать XML в файл на диск
* для дальнейшей работы создаем XML Adapter
  LOCAL oXA AS XMLADAPTER
  oXA = CREATEOBJECT("XMLAdapter")
* загружаем полученную информацию в созданный адаптер
  oXA.LOADXML(lcXML,.F.,.T.)
  IF USED('CURS')
    USE IN CURS
  ENDIF
* создаем таблицу из адаптера с названием CURS
  oXA.TABLES[1].TOCURSOR(.F.,"CURS")
* немного поработаем над полученной таблицей для красоты
* к сожалению в 8 версии неправильно работает команда ALTER TABLE
* по этому применим дополнительный SELECT
  IF VERSION(5)=800
    SELECT SPACE(30) AS VNAME, 0000.0 AS VNOM ,000.0000 AS VCURS , SPACE(3) AS  VCHCODE ;
      FROM CURS WHERE 2=1 ;
      INTO CURSOR NEWCURS NOFILTER ;
      UNION ALL ;
      SELECT MLINE(VNAME,1), VNOM,VCURS , MLINE(VCHCODE,1) ;
      FROM CURS
  ELSE
    ALTER TABLE CURS ALTER COLUMN VNAME C(30)
    ALTER TABLE CURS ALTER COLUMN VCHCODE C(3)
    ALTER TABLE CURS ALTER COLUMN VNOM N(5,2)
    ALTER TABLE CURS ALTER COLUMN VCURS N(8,4)
  ENDIF

  BROWSE && смотрим, что получили

ENDIF
* как обычно чистим за собой
RELEASE o
CLOSE DATABASES

example03.prg

Результат работы показан на нижеследующем рисунке (не плохой результат за пару минут работы):
Окно Browse после выполнения программы Курсы Валют ЦБ РФ
Думаю, что для первой части достаточно. Мы ждем Ваших откликов и замечаний - что-то изменить, что-то добавить или, может, описать возможные проблемы...