Основной проблемой, с которой приходится изначально бороться - это отсутствие сессионности между вызовами клиента. От кого пришел запрос, в общем случае, однозначно понять практически невозможно. В интернете содержится достаточно много публикаций на эту тему и подход (пример: , ), в общем-то, для решения проблемы един: создание на стороне сервера постоянно существующий объект: менеджер сессий. Как следствие - application server должен быть постоянно загружен на web-сервере или самостоятельно выполнять его функцию.
Следующая проблема - это обеспечение прозрачного вызова базовых методов интерфейса IAppServer с клиента соответствующих методов требуемого провайдера(TCustomProvider) на сервере в контексте сессии. Решением данной проблемы является создание наследника от TCustomRemoteServer, который инкапсулирует данные(контекст сессии) и методы по взаимодействию с менеджером сессий средствами SOAP-протокола. На стороне сервера создается наследник от TInvokableClass (TSOAPSessionManeger), и соответствующий ему IInvokable интерфейс ISOAPSessionManeger, который реализует базовые, но модифицированные, методы интерфейса IAppServer, и дополнительные методы, необходимые для авторизации, контроля состояния и др. .
1: ISOAPSessionManeger = interface(IInvokable) 2: ['{59AD0E15-EF0F-4DF3-A782-18B5FEC70AC4}'] 3: {IAppServer support} 4: function WS_AS_ApplyUpdates(const SessionID:WideString; const ProviderName: WideString; Delta: OleVariant; 5: MaxErrors: Integer; out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; stdcall; 6: function WS_AS_GetRecords(const SessionID:WideString; const ProviderName: WideString; Count: Integer; out RecsOut: Integer; 7: Options: Integer; const CommandText: WideString; 8: var Params: OleVariant; var OwnerData: OleVariant): OleVariant; stdcall; 9: function WS_AS_DataRequest(const SessionID:WideString; const ProviderName: WideString; Data: OleVariant): OleVariant; stdcall; 10: function WS_AS_GetProviderNames(const SessionID:WideString): TWideStringDynArray; stdcall; 11: function WS_AS_GetParams(const SessionID:WideString;const ProviderName: WideString; var OwnerData: OleVariant): OleVariant; stdcall; 12: function WS_AS_RowRequest(const SessionID:WideString; const ProviderName: WideString; Row: OleVariant; RequestType: Integer; 13: var OwnerData: OleVariant): OleVariant; stdcall; 14: procedure WS_AS_Execute(const SessionID:WideString; const ProviderName: WideString; const CommandText: WideString; 15: var Params: OleVariant; var OwnerData: OleVariant); stdcall; 16: {Authorithation support} 17: function WS_Login(const AUserName, APassword: WideString ; var SessionID, ErrMsg:WideString):Integer; stdcall; 18: function WS_Logout(const SessionID:WideString):Integer; stdcall; 19: function WS_GetSessionState(const SessionID:WideString):Integer;stdcall; 20: {Data-exchange support} 21: function WS_GetValue(const SessionID, AName: WideString):OleVariant;stdcall; 22: procedure WS_SetValue(const SessionID, AName: WideString; const AData: OleVariant);stdcall; 23: function WS_CallMethod(const SessionID, MethodName: WideString; const Params: OleVariant):OleVariant;stdcall; 24: end;Базовым понятием в данной модели является понятие сессии (класс TWSSession) . Сессия - это объект, который идентефицирует клиетское соединение на стороне сервера, ассоциирует с ним наборы модулей данных(наследники TDataModule), обеспечивает регистрацию провайдеров из соответствующих модулей, вызов базовых методов интерфейса IAppServer, и методы авторизации для конкретной сессии. Ключевые понятия сессии:
Для того, чтобы определить, какие модули данных необходимо создавать для данного AppID - их классы предварительно регистрируются. Поскольку интерфейс ISOAPSessionManeger является своеобразным "переходником" между клиентом и стандартным интерфейсами IAppServer посредством менеджера сессий, обеспечивая бинарную совместимость передаваемых пакетов данных, что позволяет прозрачно вызывать методы провайдеров уже в контексте сессии(т.е. только из тех дата-модулей, которые определены для данного AppID в конкретной сессии)
Вызов методов IInvokable интерфейсов в котнексте сессии. Для решения этой проблемы пришлось изменить стандартное поведение класса TSoapPascalInvoker. При определении экземпляра объекта для выполнения метода, запрашиваемого клиентом интерфейса, Delphi ищет в собственном реестре(объект InvRegistry) класс, его реализующий, а затем создает его экземпляр(Per request), или процедуру, возвращающую ссылку на этот объект(Global). Поскольку в каждом SOAP-сообщении содержится SessionID, можно определить сессиию клиента, и как следствие, получить ссылки на дата-модули, существующие в ее контексте. Далее находим дата-модуль, реализующий запрашиваемый интерфейс и возвращаем на него ссылку. Далее работает стандартная схема, а метод уже вызывается именно в контексте сессии. Поскольку предварительно мы реализуем стандартный код, вышеприведенные изменения ни в коем случае не изменяют установленного поведения компонентов и их методов. Таким образом задача создания своих собственных state-full объектов с поддержкой сессий становится тривиальной задачей: Создание наследника TDataModule TMyDM, объявление IInvokeble интерфейса IMyIntf и его регистрация в InvRegistry, включение интерфеса IMyIntf в класс TMyDM и его регистрация в системе(WSReg.RegisterDataModule(TMyDM,'MyApp')), реализация методов интерфейса.
Неожиданной оказалась проблема автоматического включения в каждый заголовок SOAP-сообщения идентификационной информации. Класс TRIO не содержит событий, которые позволили добавить в заголовок нужную информацию c использованием сдандартных для этого методов(Класс TSOAPHeader). Событие OnBeforeExecute вызывается уже полсе того, как заголовки упакованы в сообщение. Исходный код TRIO пришлось модифицировать, добавлением события OnBeforeRequest(добавить метод DoBeforeRequest) и полученный класс TWSRIO использовать в классе TwssSoapConnection(наследник TCustomRemoteServer, реализующий взаимодействие сервером приложений). Вызов удаленных методов интерфейсов предпочтительно осуществлять с использованием этого класса, или же, для компонента TRIO самостоятельно добавлять нужные заголовки(класс TWSSHeader).
Как следствие, вышеприведенных изменений вполне достаточно как для портации серверных приложений на базе TRemoteDataModule под Web Services, так и создания новых state-full, state-less Web Services приложений в привычных для программиста условиях.