Платформа предоставляет возможность внешним системам  обращаться к разработанной на lsFusion системе с использованием различных сетевых протоколов. Интерфейсом такого взаимодействия является вызов некоторого действия с заданными параметрами и, при необходимости, возврат значений некоторых свойств (без параметров) в качестве результатов. Предполагаются, что все объекты параметров и результатов являются объектами встроенных классов.

Задание действия

Вызываемое действие может задаваться одним из трех способов:

Протоколы

На данный момент в платформе поддерживаются следующие сетевые протоколы :

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

Формат URL, в зависимости от способа задания действия, выглядит следующим образом :

Параметры

Параметры могут передаваться как в строке запроса (добавлением в ее конец строк формата &p=<значение параметра>), так и в теле запроса (BODY). При этом предполагается, что для выполняемого действия, сначала подставляются параметры URL (в порядке их следования в запросе), а только потом параметры BODY.

При обработке параметров BODY, параметры с типом контента из следующей таблицы считаются файлами, и передаются в параметры действия в виде объектов файлового класса (FILE, PDFFILE и т.п.). При этом в расширение файла записывается соответствующее расширение из упомянутой таблицы. Если тип контента отсутствует в этой таблице, но начинается на application, то параметр все равно считается файлом, а в расширение этого файла записывается правая часть типа контента (например для типа application/abc в расширение файла записывается abc). Параметры с типом контента application/null считаются равными NULL.

Параметры BODY с типами контента, отличными от вышеупомянутых, считаются строками, и при вызове автоматически преобразуются к классам параметров вызываемого действия. Пустые строки при этом преобразуются в NULL.

Заголовки выполняемого запроса автоматически сохраняются в свойство System.headers[TEXT]. Так, в единственный параметр этого свойства записывается название заголовка, а в значение свойства - значение этого заголовка.

Результаты

Свойства, значения которых необходимо вернуть в качестве результата, передаются в строке запроса, добавлением в ее конец строк формата &return=<имя свойства>. При этом предполагается, что значения указанных свойств возвращаются в порядке их следования в строке запроса. По умолчанию, если ни одно свойство результата не задано, результирующим свойством считается первое свойство с не NULL значением из следующего списка

Если результат запроса является файлом (FILE, PDFFILE и т.п.), то тип контента ответа, в зависимости от расширения файла, определяется в соответствии со следующей таблицей. Если расширение файла отсутствует в этой таблице, тип контента устанавливается равным application/<расширение файла>.

Расширение файла при этом определяется автоматически по аналогии с оператором WRITE.

Во всех трех верхних случаях, если значение результата равняется NULL, то вместо расширения файла в тип контента подставляется строка null (например application/null), а в качестве самого ответа возвращается пустая строка.

Результаты запроса, отличные от файловых, преобразуются к строкам и передаются с типом контента text/plain. NULL значения возвращаются как пустые строки.

Значения свойства System.headersTo[TEXT] автоматически записываются в заголовки результата запроса. Так, из единственного параметра этого свойства читается название заголовка, а из значения свойства - значение этого заголовка.

Несколько результатов / параметров в BODY

Если BODY запроса имеет тип multipart/* или application/x-www-form-urlencoded, то он разбирается на части и каждая из этих частей считается отдельным параметром запроса. При этом порядок этих параметров совпадает с порядком соответствующих частей в BODY запроса.

В свою очередь, если количество возвращаемых результатов больше одного, то :

Отметим, что обработка параметров и результатов http запроса во многом аналогична их обработке в обращении к внешней системе по протоколу HTTP (параметры при этом обрабатываются как результаты, и, наоборот, результаты обрабатываются как параметры)

Stateful API

Описанный выше API, по умолчанию, является REST API. Соответственно, сессия изменений создается в момент вызова, а по окончанию этого вызова сразу же закрывается. Однако, в некоторых случаях такое поведение нежелательно, и необходимо накапливать изменения в течении некоторого промежутка времени (например, в процессе ввода информации пользователем), а значит, сессию необходимо сохранять и передавать между вызовами. Для того, чтобы сделать это, при выполнении запроса в конец строки запроса можно добавить строку формата &session=<идентификатор сессии>, где <идентификатор сессии> - любая не пустая строка. В этом случае, сессия по окончанию вызову не закроется, а привяжется к переданному идентификатору, и все последующие вызовы с этим идентификатором, будут выполняться в этой сессии. Для того, чтобы закрыть сессию (по окончанию вызова), к ее идентификатору в строке запроса необходимо добавить постфикс _close (например &session=0_close).

Так как для работы с HTTP сессиями неявно используются cookie, важно не забывать сохранять / передавать cookie между stateful http-вызовами (впрочем, как правило, это делается автоматически, например браузером, HttpClient в Java и т.п.)


В текущей реализации платформы при использовании сессий элементы системы (например локальные свойства) созданные в текущем вызове удаляются, то есть в последующих вызовах не видны.

Аутентификация

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

Form API

Кроме выполнения действий, в платформе также поддерживается API (в стиле JSON API) по работе с формами, а точнее с их интерактивным представлением. Так как это stateful API и оно спроектировано для работы в асинхронном режиме (а значит непосредственно в HTTP-интерфейсе есть ряд системных параметров, вроде индекса запроса, индекса последнего полученного ответа и т.п.), этот API удобнее использовать с помощью специальных библиотек для конкретных языков / платформ, с которыми необходима интеграция:

Библиотека работы с JavaScript доступна в центральном npm-репозитарии под именем @lsfusion/core.

Ключевым понятием в этом API является понятие состояния. Под состоянием подразумевается js-объект, структура которого соответствуют элементам формы следующим образом:

Соответственно задача библиотеки автоматически поддерживать описанное выше состояние актуальным, как при создании формы, так и при ее последующем изменении (часто такое поведение называют реактивностью).

Библиотека экспортирует следующие функции:

В качестве имен групп объектов и свойств используют не имена на форме, а имена экспорта / импорта (которые впрочем если не заданы явно, совпадают с именами на форме). При работе с формой через Form API, действия созданные при помощи операторов работы с объектами NEW и DELETE автоматически получают имена экспорта / импорта NEW и DELETE соответственно (то есть например для добавления объекта, можно вызвать change(setState, {game : {NEW:true}}) ). Также в Form API на форму автоматически добавляется свойство с именем logMessage, в которое записываются все диалоговые сообщения (в том числе возникающие при нарушении ограничений).

Аутентификация, stateful и form api поддерживаются только при выполнении http-запросов на веб-сервере. При выполнении http-запросов на сервере приложений (а точнее встроенном в него веб-сервере), заголовки авторизации, также как и параметры с идентификатором сессии, игнорируются (пользователь считается анонимным). Form api на встроенном веб-сервере не поддерживается в принципе.

Примеры

Rest API (Python)

import json
import requests
from requests_toolbelt.multipart import decoder

lsfCode = ("run(INTEGER no, DATE date, FILE detail) {\n"
           "    NEW o = FOrder {\n"
           "        no(o) <- no;\n"
           "        date(o) <- date;\n"
           "        LOCAL detailId = INTEGER (INTEGER);\n"
           "        LOCAL detailQuantity = INTEGER (INTEGER);\n"
           "        IMPORT JSON FROM detail TO detailId, detailQuantity;\n"
           "        FOR imported(INTEGER i) DO {\n"
           "            NEW od = FOrderDetail {\n"
           "                id(od) <- detailId(i);\n"
           "                quantity(od) <- detailQuantity(i);\n"
           "                price(od) <- 5;\n"
           "                order(od) <- o;\n"
           "            }\n"
           "        }\n"
           "        APPLY;\n"
           "        EXPORT JSON FROM price = price(FOrderDetail od), id = id(od) WHERE order(od) == o;\n"
           "        EXPORT FROM orderPrice(o), exportFile();\n"
           "    }\n"
           "}")

order_no = 354
order_date = '10.10.2017'
order_details = [dict(id=1, quantity=10),
                 dict(id=2, quantity=15),
                 dict(id=5, quantity=4),
                 dict(id=10, quantity=18),
                 dict(id=11, quantity=1),
                 dict(id=12, quantity=3)]

order_json = json.dumps(order_details)

url = 'http://localhost:7651/eval'
payload = {'script': lsfCode, 'no': str(order_no), 'date': order_date,
           'detail': ('order.json', order_json, 'text/json')}

response = requests.post(url, files=payload)
multipart_data = decoder.MultipartDecoder.from_response(response)

sum_part, json_part = multipart_data.parts
sum = int(sum_part.text)
data = json.loads(json_part.text)

##############################################################

print(sum)
for item in data:
    print('{0:3}: price {1}'.format(int(item['id']), int(item['price'])))

##############################################################
# 205
#   4: price 5
#  18: price 5
#   3: price 5
#   1: price 5
#  10: price 5
#  15: price 5

Form API (JavaScript)

<iframe src="https://codesandbox.io/embed/vibrant-tharp-trcqt?fontsize=14" title="vibrant-tharp-trcqt" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media" style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe>