Содержание

basis.net.service

Модуль basis.net.service предоставляет единственный класс Service. Экземпляры этого класса служат для централизованного производства транспортов, обладающих общим базовым поведением, а также для общего управления транспортами, относящимися к сервису. Он реализует механизм сессии, который используется для авторизации, а также позволяет подписывать запросы.

Service

Service является фабрикой экземпляров basis.net.AbstractTransport, имеющих общий механизм формирования запроса и обработки ответов. Обычно используется для реализации взаимодействия с сервисами на стороне сервера (некоторым серверным API). Позволяет описывать общие правила и требования для каждого запроса, например, наличие определенных заголовков, определенный формат ответов или необходимость авторизации с последующей подписью запросов. В одном приложении может использоваться несколько экземпляров Service, но чаще всего используется один.

Свойство transportClass определяет, какой класс транспорта будет использоваться. По умолчанию иcпользуется класс basis.net.ajax.Transport.

var Service = basis.require('basis.net.service');

// создание сервиса
var service = new Service({
  transportClass: {     // новый класс, наследник basis.net.ajax.Transport
    requestHeaders: {   // добавляем заголовок по умолчанию
      Accept: 'application/json'
    }
  }
});

Фактически, все настройки транспорта настраиваются через создание нового класса. Это значит, что можно задать любую настройку и добавить любое расширение. В частности, через создание класса может быть задано и базовое поведение для всех запросов (свойство requestClass в transportClass).

При инициализации сервис создает от заданного класса еще один внутренний класс, которой реализует дополнительную логику сервиса. Этот внутренний класс и используется для создания транспортов.

Метод createTransport(config) позволяет создать транспорт сервиса. При этом создается экземпляр transportClass с конфигурацией, которая передается единственным аргументом метода.

var transport = service.createTransport({
  url: '/users',
  handler: {
    success: function(){
      // ...
    }
  }
});

Метод createAction(config) делает то же, что и функция basis.net.action.create (она и используется внутри). Только он обеспечивает, что создаваемые действия (action) используют метод createTransport(config) сервиса, для производства транспорта. Этот метод используется наиболее часто.

var object = new basis.data.Object({
  save: service.createAction({
    url: '/users',
    request: function(){
      // ...
    },
    success: function(data){
      // ...
    }
  })
});

Сессия

Если серверная сторона требует авторизации, необходимо понимать, авторизован ли экземпляр Service делать запросы или нет. Для этого используется механизм "сессии". Сессия – это пользовательский сеанс работы с удаленным сервисом. Если сервис авторизован, то сессия будет открыта (open), в противном случае – закрыта (close).

Сам сервис не имеет возможности проверить, авторизован он или нет. Это связанно с тем, что реализации авторизации на стороне сервера сильно разнятся, и нельзя получить универсального решения. Поэтому подобная логика описывается отдельно от сервиса разработчиками приложений. Такой код открывает и закрывает сессию в зависимости от определенных условий.

var Service = basis.require('basis.net.service').Service;
var service = new Service();

// предположим, что для персистентности, текущий ключ сессии хранится в cookie
// получение ключа сессии из cookies
var sessionKey = basis.require('basis.ua').cookies.get('sessionKey');

// при инициализации приложения, проверяем есть ли ключ сессии,
// и если есть – открываем сессию
if (sessionKey)
  service.openSession(sessionKey);

Для открытия сессии используется метод openSession(key, data). Ему передаются два параметра: ключ сессии (обязательный), и данные сессии (необязательный). Данные сессии могут использоваться для подписи запросов. Закрывается сессия методом closeSession().

По умолчанию транспорт сервиса не учитывает, открыта сессия или нет. Для того, чтобы это учитывалось, сервису необходимо задать свойству isSecure значение true. В этом случае, транспорты не будут совершать запросы (они будут игнорироваться), если сессия закрыта. Этим поведением управляет свойство needSignature (DEPRECATED), по умолчанию оно установлено в true. Значение false, для этого свойства, необходимо выставлять только для транспортов, которые должны выполняться независимо от того, открыта сессия или нет. Обычно это запросы на получение ключа сессии (login), его проверки, отмены ключа сессии (logout), напоминание пароля и т.п.

var DataObject = basis.require('basis.data').Object;
var Service = basis.require('basis.net.service').Service;
var cookies = basis.require('basis.ua').cookies;

var service = new Service({
  isSecure: true
});

var profile = new DataObject({
  login: service.createAction({
    needSignature: false,            // DEPRECATED иначе запрос не будет выполнятся
    method: 'POST',
    url: '/login',
    request: function(login, pwd){
      return {                       // POST /login
        params: {                    //
          login: login,              // login=[login]&password=[pwd]
          password: pwd              //
        }
      }
    },
    success: function(data){
      // предположим, сервер отдал JSON
      // { "status": "ok", "session": "..." }
      service.openSession(data.session);

      // сохраняем ключ сессии в cookie
      cookies.set('sessionKey', data.session);
    }
  })
});

if (cookies.get('sessionKey'))
  service.openSession(cookies.get('sessionKey'));
else
  profile.login('test', '123');

Так как сессия в общем случае является пользовательским сеансом, то предполагается, что возможна смена ключа сессии или временная заморозка сессии. В этом случае пользовательский сеанс включает в себя несколько сеансов (сессий) с удаленным сервисом.

Ситуация потери сессии с удаленым сервисом возможна из-за действия пользователя (пользователь нажал заблокировать или "выйти") или ввиду определенных условий клиентской или серверной стороны (пользователь долго не работал с интерфейсом, долго не отправлялись запросы, превышено время жизни сессии). В этом случае не обязательно закрывать сессию, достаточно ее заморозить. Заморозка означает, что продолжение работы с сервисом возможно, при этом не предполагается смены пользователя (в рамках удаленного сервиса). Поэтому приложение должно максимально сохранить свое состояние, заблокировав интерфейс и предложив, например, форму для ввода только пароля.

Замораживается сессия методом freeze, а размораживается методом unfreeze. При заморозке все выполняемые запросы обрываются и добавляются в очередь, а при разморозке эти запросы выполняются повторно.

На данный момент (1.3) сохраняются все запросы. Но это не выглядит безопасно. В будущем этим поведением можно будет управлять.

В случае ошибки запроса выполняется метод сервиса isSessionExpiredError, которому передается запрос (request). Если метод возвращает true, то считается, что сессия с удаленным сервисом истекла, и сессия замораживается.

var Service = basis.require('basis.net.service').Service;

var service = new Service({
  isSecure: true,
  isSessionExpiredError: function(request){  // считаем ответы от сервера с кодом 401,
    return request.xhr.status == 401;        // как сигнал, что сессия устарела
  }
});

Класс Service является наследником basis.event.Emitter. Для его экземпляров обрабатывабтся следующие события (все они связанны с сессией):

  • sessionOpen – сессия открыта;
  • sessionClose – сессия закрыта;
  • sessionFreeze – сессия разморожена;
  • sessionUnfreeze – сессия заморожена.

Подпись запросов

Если сервис использует механизм сессии, появляется возможность подписи запросов. Когда сессия открыта, то каждый раз перед выполнением запроса транспорт передает этот запрос в вызов метода signature. Кроме того, в этот метод передается значение data, которое задается при открытии сессии.

var Service = basis.require('basis.net.service').Service;
var service = new Service({
  isSecure: true,
  signature: function(transport, sessionData){
    console.log('sign request', sessionData);
    transport.setParam('session', session.publicKey);
  }
});

service.openSession('key', {
  publicKey: 'value'
});

var transport = service.createTransport();
transport.request();
// sign request { publicKey: 'value' }

TODO jwt как пример publicKey