Введение
Активно распространяющаяся сейчас технология Web Services дает большие
преимущества при разработке различных распределенных приложений. Можно
легко интегрировать приложения, разработанные на разных платформах, не
заботясь об ограничениях локальной сети и наличии FireWall. Ведь
используются только принятые стандарты и открытые протоколы. Все хорошо,
очень здорово и очень удобно. Но как всегда, есть одно маленькое «но».
Каждое средство разработки, поддерживающее Web Services делает это
немного по-своему. И, если не знать, эти мелочи, то процесс разработки
может занять достаточно много времени.
В этой статье хотелось бы поделиться опытом, полученном специалистами
компании Metric, при разработке
Web-сервисов для компании Аэрофлот.
Описание задачи
Необходимо реализовать сервис, который бы мог предоставлять максимально
широкому кругу потребителей информацию о рейсах Аэрофлота. Текущее
актуальное расписание рейсов, а также справку о рейсах. Данная
информация может быть интересна и востребована различными компаниями.
Например, компания-разработчик программного обеспечения для турагентств
может интегрировать эту информацию в свой продукт, чтобы менеджер
агентства всегда имел под рукой актуальное расписание, а также мог
уведомить клиентов о задержках по его рейсу. Туристические сайты,
используя данную информацию, могут предоставлять более высокий сервис
для своих клиентов. Наконец, компания, чьи сотрудники регулярно летают в
командировки, может встроить работу с данной информацией в свой
корпоративный портал.
Поскольку круг клиентов, кто будет пользоваться информацией заранее
неизвестен, и уж тем более нет информации об их платформах, то
Web-сервисы являются идеальным решением для предоставления информации в
удобном виде максимально широкому кругу клиентов.
Чтобы реализовать такой сервис необходимо уметь получать аргументы,
возвращать и разбирать сложные структуры данных. В результате
проведенных экспериментов получен некоторый опыт, которым мы и делимся в
этой статье.
Создаем Web-сервис на .NET
Общую процедуру создания сервиса мы описывать не будем, она описана в
документации, а также в других статьях в разделе
XML Web Services
на GotDotNet.RU. Остановимся
только на моментах, связанные с совместимостью:
- Используйте атрибут SoapRpcService
- Возвращайте массивы объектов, а не массивы структур.
- Избегайте использования типа dateTime в аргументах.
Используйте SoapRpcService
VS.NET поддерживает два стиля для кодирования данных при передаче
SOAP-сообщений: RPC и Document. Если Вы не указываете дополнительных
атрибутов, то по умолчанию VS.NET использует стиль Document. Однако
многие программные продукты других производителей поддерживают только
стиль RPC. Поэтому, чтобы Ваш сервис был доступен наиболее широкому
кругу клиентов, необходимо использовать именно его. Для этого необходимо
использовать класс System.Web.Services.Protocols и указать атрибут
SoapRpcService.
[VB.NET]
Imports System.Web.Services
Imports System.Web.Services.Protocols
<SoapRpcService(), WebService(Namespace:="http://www.aeroflot.ru/")> _
Public Class FlightStatus
Inherits System.Web.Services.WebService
...
End Class
[C#]
using System.Web.Services;
using System.Web.Services.Protocols;
namespace Aeroflot
{
[WebService(Namespace="http://www.aeroflot.ru/")]
[SoapRpcService]
public class FlightStatus : System.Web.Services.WebService
{
....
}
}
Аналогично для методов необходимо указывать атрибут SoapRpcMethod.
Возвращайте массив объектов, а не массив структур.
При создании сервиса, использующего Document стиль кодирования, Вы
можете объявить структуру и возвращать массив структур.
Public Structure t
Public a As String
Public b As String
End Structure
Public Function Test() As t()
Dim t1() As t
Test = t1
End Function
Однако, как только Вы укажете инструкцию SoapRpcService, получите
сообщение, что массивы структур не поддерживаются при таком стиле
кодирования сообщений (Arrays of structs are not supported with encoded
SOAP.).
Поэтому, Вам необходимо объявить публичный класс и возвращать массив
элементов этого класса.
Избегайте использование типа dateTime в аргументах.
При написании клиентов пришлось столкнуться с тем, что попытки передачи
дат из разных языков часто приводили к ошибке. WebService не желал
воспринимать дату в передаваемом формате. Поэтому, чтобы упростить
процедуру взаимодействия с сервисом, лучше такие параметры объявлять
строковыми, а приведение выполнять уже внутри функции. Так с сервисом
будет просто проще работать.
Если объединить все эти правила, то код сервиса на VB.NET будет
выглядеть примерно так:
Imports System.Web.Services
Imports System.Web.Services.Protocols
<SoapRpcService(), WebService(Namespace:="http://www.aeroflot.ru/")> _
Public Class FlightStatus
Inherits System.Web.Services.WebService
Public Class Airport
Public city As String
Public name As String
Public code As String
End Class
<WebMethod(), SoapRpcMethod()> _
Public Function AirportList () As Airport()
Dim OutArray() As Airport
…………........ Заполняем массив данными ……………..
AirportList = OutArray
End Function
End Class
Пишем клиентов
Сервис создан и продемонстрируем, как к нему обращаться из разных языков
программирования.
В качестве примера будет демонстрироваться вызов функции получения
табло прилета по аэропорту Шереметьево 1 (код SVO1) за текущий день.
После получения результата будем печатать следующие поля:
- Компания
- Рейс
- Аэропорт
- Дата по расписанию
- Ожидаемая дата
Полный код примеров и описание сервиса есть на сервере
http://webservices.aeroflot.ru/.
Клиент на C#
Для начала напишем программу-клиент на одном из .NET языков – С#.
Теперь можно вызывать сервис:
<%
aeroflot.FlightStatus myFlightStatus = new aeroflot.FlightStatus() ;
aeroflot.Flight[] result = myFlightStatus.Arrival( "SVO1", DateTime.Today.ToString());
foreach ( aeroflot.Flight f in result ){
Response.Write( f.company ) ;
Response.Write( f.flight_no ) ;
Response.Write( "\t" );
Response.Write( f.airport ) ;
Response.Write( "\t" );
Response.Write( f.sched ) ;
Response.Write( "\t" );
Response.Write( f.plan ) ;
Response.Write( "\n<BR>" );
}
%>
Клиент на Perl
Для написания клиентов на Perl мы рекомендуем использовать удобный и
простой модуль SOAP::Lite. Для
тестирования использовалась версия 0.52. Данная версия уже корректно
поддерживает работу с WSDL файлами.(В предыдущей версии была
необходимость сохранять WSDL файл вручную и далее выполнять
некоторые замены, но с этой версии необходимость в этом отпала).
Во время написания клиента столкнулись только с одним некорректным
поведением модуля, а именно, игнорированием типа, описанного в WSDL
файле и попыткой автоматического выставления типа. Если происходит вызов
метода, которому в качестве значения строкового параметра передается
строка, содержащая только число, то в теле SOAP-запроса SOAP::Lite
указывает его, как тип int. Что приводит к ошибке вызова из-за
несовпадения типов параметров. Возможно, в следующих версиях SOAP::Lite
эта ошибка будет исправлена, а пока достаточно принудительно указывать
тип аргументов при их вызове.
Программа-клиент выглядит следующим образом:
use SOAP::Lite service =>
'http://webservices.aeroflot.ru/FlightStatus.wsdl';
($DAY, $MONTH, $YEAR) = (localtime)[3,4,5];
$now = sprintf("%02d.%02d.%04d", $DAY, ($MONTH + 1 ), ($YEAR + 1900));
$code = SOAP::Data
-> type ("string")
-> name ("code")
-> value ("SVO1")
;
$date = SOAP::Data
-> type ("string")
-> name ("date")
-> value ($now)
;
$flights = Arrival($code, $date);
foreach $flight ( @$flights ) {
print $$flight{"company"} . $$flight{"flight_no"} . "\t" . $$flight{"airport"} . "\t"
. $$flight{"sched"} . "\t" . $$flight{"plan"} . "\n";
}
Как видите, все довольно просто.
Клиент на PHP
Для реализации клиентов на PHP рекомендуем использовать модуль
NuSOAP.
В принципе, этот пример мало чем отличается от предыдущего.
<?php
require_once('nusoap.php');
$soapclient = new soapclient('http://webservices.aeroflot.ru/FlightStatus.wsdl','wsdl');
$code = "SVO1";
$date = date("d.m.Y");
$parameters = array($code, $date);
$flights = $soapclient->call('Arrival',$parameters);
foreach ( $flights as $flight ) {
print $flight['company'] . $flight['flight_no'] . "\t" . $flight['airport'] . "\t" . $flight['sched'] . "\t" . $flight['plan'] . "\n";
}
?>
Клиент на VB 6.0
Работать SOAP на VB 6.0 можно с использованием Microsoft Soap Toolkit
2.0. Если Вы используете только простые типы, то никакой дополнительной
работы выполнять не нужно, Soap Toolkit самостоятельно знает, как ему
преобразовывать базовые типы в XML и обратно. Но когда возникает
необходимость в передаче сложных типов (структур, классов), то тут ему
нужно помочь.
Обработка сложных типов в Soap Toolkit
Для передачи сложных типов в Soap Toolkit существуют две возможности:
- Каждый раз при получении/передаче значений данного типа
самостоятельно разбирать XML, работая с объектом типа
IXMLDOMNodeList
- Написать специализированный обработчик типа.
Более удобным является, естественно, второй вариант. Один раз создаем
обработчик типа, а потом работаем с ним, как с обычным классом.
Для создания и использования специализированного обработчика типа
необходимо:
- Создать класс, который соответствует передаваемому или
получаемому сложному типу.
- Создать класс-обработчик для этого типа, который будет
преобразовывать объекты данного класса в XML и обратно.
- Создать WSML-файл, в котором настроить соответствие типа и его
обработчика.
Описание процедуры создания своих обработчиков есть в документации по
Soap Tookit, а также можно для примера использовать обработчик типов для
нашего сервиса (см.
http://webservices.aeroflot.ru). Для тех, кто будет создавать свои
обработчики, рекомендуем использовать код этого обработчика, и создавать
свои по аналогии. Хотелось бы обратить внимание на некоторые моменты:
- Не используейте один обработчик(mapper) для всех полей, а
создавайте свой для каждого поля структуры/объекта и определяйте тип
динамически. Тогда, при смене типа данных одного из полей, даже с
простого на сложный, ничего изменять не придется, система все
отработает автоматически.
- Сложные типы в WSDL файле могут включать элементы в
строгом(sequence) или в произвольном(all) порядке . Это надо
учитывать при инициализации (Set Node =
pSchema.selectSingleNode("XSD:sequence/XSD:element[@name='city']"))
- Не все поля структуры могут быть обязательными. Поэтому, прежде
чем вызвать обработчик поля, проверьте, есть ли значение
(pNode.selectSingleNode("city") is nothing). Иначе произойдет
ошибка.
После создания специализированного обработчика класса необходимо создать
WSML файл.
<?xml version='1.0' encoding='UTF-8' ?>
<servicemapping name='FlightStatus'>
<service name='FlightStatus'>
<using PROGID='FlightStatusMapper.AirportMapper' cachable='0' ID='AirportMapper' />
<types>
<type name='Airport' targetNamespace='http://www.aeroflot.ru/' uses='AirportMapper'/>
</types>
</service>
</servicemapping>
Имя сервиса в ServiceMapping, Service берем из раздела service в WSDL
файле. В инструкции using указываем, имя своего обработчика и его
PROGID, а в инструкции type ставим соответствие типу в WSDL файле имя
своего обработчика.
Подготовительная работа завершена и можно осуществлять вызов.
Вызов сервиса
Set SoapClient = CreateObject("MSSOAP.SoapClient")
SoapClient.ClientProperty("ServerHTTPRequest") = True
Call SoapClient.mssoapinit("http://webservices.aeroflot.ru/flightstatus.wsdl", _
"", "", "http://webservices.aeroflot.ru/flightstatus.wsml")
flights = SoapClient.Arrival("SVO1", CStr(Now))
For i = 0 To UBound(flights)
Set odata = flights(i)
Debug.Print odata.Company & odata.FlightNo & vbTab & _
odata.Airport & vbTab & odata.Sched & vbTab & odata.Plan
Next
Клиент на ASP
Для работы с сервисом из ASP также необходимо использовать Soap Toolkit.
Только в отличие от VB есть одна маленькая неприятность. Дело в том, что
в результате вызова функций сервиса возвращается либо массив объектов
(Variant/Object), либо массив дат (Variant/Date), как в функции
DateList. При этом VBScript работает только с типом данных Variant.
Поэтому и обрабатываемый массив должен содержать тип Variant
(Variant/Variant). Иначе обращение к любому элементу массива вызовет
ошибку «Несовпадение типов».
Проблема решается очень просто. Добавим в специализированный
обработчик класса, который мы уже создавали функцию MakeVariantArray,
преобразующую переданный ей массив элементов в массив элементов типа
Variant.
Public Function MakeVariantArray(v) As Variant
MakeVariantArray = Empty
On Error GoTo Errors
Dim i&
If Not IsEmpty(v) Then
If UBound(v) >= 0 Then
ReDim vres(UBound(v)) As Variant
For i = 0 To UBound(v)
If IsObject(v(i)) Then
Set vres(i) = v(i)
Else
vres(i) = v(i)
End If
Next
MakeVariantArray = vres
End If
End If
Exit Function
Errors:
MakeVariantArray = Empty
End Function
Теперь все отличие кода на ASP от кода на VB заключается только в том,
что:
- Надо не забывать приводить тип аргументов (СStr и т.д.).
- Преобразовывать полученный массив в массив типа Variant.
<%
Set SoapClient = Server.CreateObject("MSSOAP.SoapClient")
Set SoapUtils = Server.CreateObject("FlightStatusMapper.Utils")
SoapClient.ClientProperty("ServerHTTPRequest") = True
Call SoapClient.mssoapinit("http://webservices.aeroflot.ru/flightstatus.wsdl", _
"", "", "http://webservices.aeroflot.ru/flightstatus.wsml")
flights = SoapClient.Arrival(cstr("SVO1"), CStr(Now))
flights = SoapUtils.MakeVariantArray(flights)
For i = 0 To UBound(flights)
Set odata = flights(i)
Response.Write odata.Company & odata.FlightNo & vbTab & _
odata.Airport & vbTab & odata.Sched & vbTab & odata.Plan & "<BR>"
Next
%>
Заключение
Как видите, при наличии небольшого навыка, создание и использование Web
Services не представляет особых сложностей. Можно быстро создавать
приложения и сервисы, и быть уверенными, что их легко можно будет
использовать в приложениях на разных платформах, из разных языков
программирования.
Информацию о своих Web-сервисах Вы можете разместить в
каталог
российских Web-сервисов на
GotDotNet.RU.
Информация по теме
- Программные продукты
- Информационные ресурсы
- Web-сервисы Аэрофлота
- Справка о рейсе
- Расписание рейсов