Одно из ключевых направлений развития платформы данных InterSystems IRIS - открытость. Открытость во взаимодействии с языками программирования, технологиями и протоколами. Поддержка языков программирования двусторонняя - возможен как вызов кода из InterSystems IRIS, так и предоставляется API для работы с InterSystems IRIS извне. В этой статье речь пойдёт о первом варианте - вызове кода из InterSystems IRIS. Целью этого небольшого повествования является демонстрация того, как просто и удобно можно это сделать. Я не буду сравнивать различные языки программирования (хотя в конце есть таблица по скорости работы различных имплементаций), всё зависит от решаемых вами задач и требований, предъявляемых к результату разработки. В этой статье я продемонстрирую несколько различных подходов к вызовам сторонних библиотек, а реализовывать мы будем одну и ту же функциональность - вызов функции DELFATE из библиотеки zlib.
Недавно я прочитал вот эту статью. Речь там идет как раз о вызове функции DELFATE на NodeJS из платформы InterSystems IRIS, и эта статья натолкнула меня на мысль описать вызов кода и на других языках. Итак, начнем с NodeJS. Я беру код почти целиком из статьи Бернда, за исключением того, что в нем не используются файлы, а прямое http-соединение для передачи данных. В целом лучше передавать данные в теле запроса, и кодировать как запрос, так и ответ в виде base64. Тем не менее, вот код:
//zlibserver.js
const express = require('express');
const zlib = require('zlib');
var app = express();
app.get('/zlibapi/:text', function(req, res) {
res.type('application/json');
var text=req.params.text;
try {
zlib.deflate(text, (err, buffer) => {
if (!err) {
res.status(200).send(buffer.toString('binary'));
} else {
res.status(500).json( { "error" : err.message});
// handle error
}
});
}
catch(err) {
res.status(500).json({ "error" : err.message});
return;
}
});
app.listen(3000, function(){
console.log("zlibserver started");
});
Чтобы запустить сервер, выполните в терминале ОС (должны быть установлены node
и npm
):
cd <repo>\node
npm install
node ./zlibserver.js
Что здесь происходит? Слушаем порт 3000
, сжимаем DEFLATE тело запроса и возвращаем сжатые данные в ответ. На стороне InterSystems IRIS используется http запрос для взаимодействия с данным API:
/// NodeJS implementation
/// do ##class(isc.zlib.Test).node()
ClassMethod node(text As %String = "Hello World", Output response As %String) As %Status
{
kill response
set req = ##class(%Net.HttpRequest).%New()
set req.Server = "localhost"
set req.Port = 3000
set req.Location = "/zlibapi/" _ text
set sc = req.Get(,,$$$NO)
quit:$$$ISERR(sc) sc
set response = req.HttpResponse.Data.Read($$$MaxStringLength)
quit sc
}
Обратите внимание, что я устанавливаю третий аргумент set sc = req.Get(,,$$$NO)
- reset
равным нулю. Если вы пишете интерфейс для внешнего http(s) сервера, то лучше всего повторно использовать один объект запроса и просто модифицировать его по мере необходимости для выполнения новых запросов.
Java Gateway позволяет вызывать произвольный Java-код. Стандартная библиотека Java включает класс Deflater
, который делает именно то, что нам нужно:
package isc.zlib;
import java.util.Arrays;
import java.util.zip.Deflater;
public abstract class Java {
public static byte[] compress(String inputString) {
byte[] output = new byte[inputString.length()*3];
try {
// Encode a String into bytes
byte[] input = inputString.getBytes("UTF-8");
// Compress the bytes
Deflater compresser = new Deflater();
compresser.setInput(input);
compresser.finish();
int compressedDataLength = compresser.deflate(output);
compresser.end();
output = Arrays.copyOfRange(output, 0, compressedDataLength);
} catch (java.io.UnsupportedEncodingException ex) {
// handle
}
return output;
}
}
Особенность этой имплементации заключается в том, что она возвращает массив byte[]
, который становится потоком на стороне InterSystems IRIS. Я пытался вернуть строку, но не смог найти, как сформировать бинарную строку из byte[]
. Если у вас есть какие-то идеи, пожалуйста, оставьте комментарий.
Чтобы запустить код, поместите jar из релизов в папку <instance>/bin
, загрузите код в свой инстанс InterSystems IRIS и выполните:
write $System.Status.GetErrorText(##class(isc.zlib.Utils).createGateway())
write $System.Status.GetErrorText(##class(isc.zlib.Utils).updateJar())
Проверьте метод createGateway
перед запуском. Второй аргумент javaHome
предполагает что переменная окружения JAVA_HOME
установлена. Если это не так, вручную передайте путь до Java 1.8 JRE. Для сжатия строки text
выполните этот код:
set gateway = ##class(isc.zlib.Utils).connect()
set response = ##class(isc.zlib.Java).compress(gateway, text)
Библиотека InterSystems Callout это динамическая библиотека, предоставляющая функции, которые вы можете вызвать из InterSystems IRIS.
Вот наша библиотека:
#define ZF_DLL
// Ugly Windows hack
#ifndef ulong
typedef unsigned long ulong;
#endif
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#include "zlib.h"
#include <cdzf.h>
int Compress(char* istream, CACHE_EXSTRP retval)
{
ulong srcLen = strlen(istream)+1; // +1 for the trailing `\0`
ulong destLen = compressBound(srcLen); // estimate size needed for the buffer
char* ostream = malloc(destLen);
int res = compress(ostream, &destLen, istream, srcLen);
CACHEEXSTRKILL(retval);
if (!CACHEEXSTRNEW(retval,destLen)) {return ZF_FAILURE;}
memcpy(retval->str.ch,ostream,destLen); // copy to retval->str.ch
return ZF_SUCCESS;
}
ZFBEGIN
ZFENTRY("Compress","cJ",Compress)
ZFEND
Для запуска загрузите dll
или so
со страницы релизов в папку <instance>/bin
. В репозитории есть также скрипты для сборки вашей собственной версии библиотеки.
Перед сборкой установите:
- Linux:
apt install build-essential zlib1g zlib1g-devel
- Windows: WinBuilds
Для работы с Callout библиотекой выполните:
set path = ##class(isc.zlib.Test).getLibPath() //get path to library file
set response = $ZF(-3, path, "Compress", text) // execute function
do $ZF(-3, "") //unload library
Используя Python Gateway вызовем данный код:
import zlib
zlib.compress(b'text')
Из InterSystems IRIS: set out = ##class(isc.py.Callout).SimpleString("import zlib" _ $$$NL _ "x = zlib.compress(b'" _ text _ "')", "x")
Реализация на C# также возвращает поток, а не строку:
using System;
using System.IO;
using System.IO.Compression;
namespace isc.zlib
{
public class Net
{
public static byte[] compress(String str)
{
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream gzip = new DeflateStream(output, CompressionMode.Compress))
{
using (StreamWriter writer = new StreamWriter(gzip, System.Text.Encoding.UTF8))
{
writer.Write(str);
}
}
return output.ToArray();
}
}
}
}
Для запуска загрузите zlibnet.dll
со страницы релизов в папку <instance>/bin
.
write $System.Status.GetErrorText(##class(isc.zlib.Utils).createNetGateway())
write $System.Status.GetErrorText(##class(isc.zlib.Utils).updateNet())
Для сжатия строки text
выполните этот код:
set gateway = ##class(isc.zlib.Utils).connect()
set response = ##class(isc.zlib.Net).compress(gateway, text)
Несколько неожиданно в статье о механизмах вызова кода на других языках, но в ObjectScript также есть встроенная функция Compress (и парная функция Decompress). Вызывается так: set response = $extract($SYSTEM.Util.Compress(text), 2, *-1)
Помните, что поиск в документации или вопрос на Developers Community может сэкономить вам некоторое время.
Я запустил простой тест (1Kb text, 1 000 000 итераций) на Linux (VPS) и Windows (Ноутбук) и получил следующие результаты.
Windows:
Метод | С | ObjectScript | Python | Java | NodeJS | С# |
---|---|---|---|---|---|---|
Время | 22,77 | 33,41 | 91,52 | 152,73 | 622,51 | 216,43 |
Скорость (Kb/s) | 43912 | 29927 | 10670 | 6547 | 1606 | 4512 |
Разница, % | -/- | 46,73 | 401,93 | 570,75 | 2633,90 | 950,5 |
Linux:
Метод | C | ObjectScript | Python | Java | NodeJS |
---|---|---|---|---|---|
Время | 76,3541 | 76,499 | 283,84 | 147,2436 | 953,7311 |
Скорость (Kb/s) | 13097 | 13072 | 3440 | 6791 | 1049 |
Разница, % | -/- | 0,19 | 371 | 92,84 | 1149,09% |
Для запуска тестов загрузите код и вызовите: do ##class(isc.zlib.Test).test(textLength, iterations)
С платформой InterSystems IRIS вы легко можете использовать существующий код на ряде популярных языков. Однако выбор реализации не всегда прост: необходимо учитывать несколько метрик, таких как скорость разработки, производительность, кроссплатформенность и простота сопровождения. Ответы на эти вопросы помогут вам определиться с оптимальным планом разработки.