Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ya-kostik/small-rpc
Простой RPC для проекта. Можно использовать как с HTTP, так и с сокетами, так и с любым другим транспортом.
https://github.com/ya-kostik/small-rpc
http javascript js node nodejs rpc rrpc socket tcp websockets
Last synced: 3 months ago
JSON representation
Простой RPC для проекта. Можно использовать как с HTTP, так и с сокетами, так и с любым другим транспортом.
- Host: GitHub
- URL: https://github.com/ya-kostik/small-rpc
- Owner: ya-kostik
- License: apache-2.0
- Created: 2018-06-27T15:46:55.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2023-01-07T04:15:13.000Z (about 2 years ago)
- Last Synced: 2024-11-08T10:31:47.432Z (3 months ago)
- Topics: http, javascript, js, node, nodejs, rpc, rrpc, socket, tcp, websockets
- Language: JavaScript
- Size: 417 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Small RPC
Простой RPC для проекта. Можно использовать как с HTTP, так и с сокетами, так и с любым другим транспортом.
Вытащен и допилен из RPC Redbone, работает на одном с ним протоколе.## Установка
```sh
npm install small-rpc
```
или через Yarn
```sh
yarn add small-rpc
```## Использование на сервере
Важно понимать, что библиотека использует `async`/`await` и вам понадобится NodeJS версии 8 или выше.
### Подключение
```javascript
const RPC = require('small-rpc');
const rpc = new RPC();// Добавляем объект для вызова (module):
rpc.setModule('profile', new Profile());
``````javascript
const { json, send } = require('micro');
// вызов механизма rpc, например, внутри модуля micro
module.exports = async (req, res) => {
const action = await json(req, { limit = '0.3mb', encoding = 'utf8' });
try {
send(res, 200, await rpc.call({ req, res }, action));
} catch(err) {
send(req, 500, 'Internal Server Error');
}
};
```### Валидация `action`
RPC валидирует `action` самостоятельно, и по-умолчанию стреляет обычным `Error`, если с `action` что-то не так.Чтобы более точно отлавливать такие ошибки можно использовать свой наследник от `Error`.
```javascript
class MyError extends Error {};RPC.Error = MyError;
const { json, send } = require('micro');
module.exports = async (req, res) => {
const action = await json(req, { limit = '0.3mb', encoding = 'utf8' });
try {
return send(res, 200, await rpc.call({ req, res }, action));
} catch(err) {
if (err.constructor === MyError) {
return send(req, 400, MyError.message);
}
return send(req, 500, 'Internal Server Error');
}
};
```### Добавление библиотеки модулей
Вы можете добавлять объекты пачками, и разделять их на библиотеки.
```javascript
const RPC = require('small-rpc');
const rpc = new RPC();// Добавляем модели mongoose в RPC
rpc.setLib('mongoose', mongoose.models);
```Вы также можете дополнить любую библиотеку еще одним модулем:
```javascript
// Добавит модуль Profile в библиотеку mongoose
rpc.setModule('mongoose.Profile', Profile);
```Если вы добавляете модуль, без указания библиотеки, он добавляется в библиотеку по-умолчанию — `main`.
Также если вызвать `setLib` с одним аргументом, и передать просто объект он будет записан как библиотека `main`.```javascript
// Записываем модели mongoose как библиотеку `main`
rpc.setLib(mongoose.models);// Добавляем объект для вызова profile (из которого мы будем вызывать методы) в библиотеку `main`
rpc.setModule('profile', new Profile());
```### Middleware
Вы можете использовать `middleware`-функции, чтобы определять уровни доступа и дополнительную бизнес-логику.`Middleware` бывают четырех типов:
1. Все запросы
2. Для конкретной библиотеки
3. Для конкретного модуля
4. Для конкретного методаВсе они задаются через метод `use`
```javascript
rpc.use((payload, action) => {
// Выполнится для всех запросов
});rpc.use('main', (payload, action) => {
// Выполнится для всех запросов к библиотеке `main`
});rpc.use('mongoose', (payload, action) => {
// Выполнится для всех запросов к библиотеке `mongoose`
});rpc.use('mongoose.Profile', (payload, action) => {
// Выполнится для всех запросов к библиотеке `mongoose` и модулю `Profile`
});rpc.use('mongoose.Profile.login', (payload, action) => {
// Выполнится для всех запросов к библиотеке `mongoose` и модулю `Profile` при вызове метода `login`
});
```При вызове в `middleware` передается:
`payload` — формируется при вызове `rpc`
`action` — объект действия `rpc`
`rpc` — экземляр `rpc` который вызывает `middleware`Чтобы остановить поток выполнения `middleware` нужно просто вернуть `false` из той `middleware` на которой вы хотите остановиться.
```javascript
rpc.use((payload, action) => {
if (!action.jwt) return false;
});
```Также `middleware` могут быть объектами. Чтобы использовать такую `middleware` нужно определить в объекте метод `call`, аналогичный функциональной версии.
Это может быть полезно, если `middleware` большая, и её хочется разделить на части, или если у нее есть параметры и состояние.### Middleware до и после выполнения метода
Все `middleware`, которые вы добавляете методом `use` выполняются до выполнения метода.
Чтобы добавить немного семантики в код их подключения можно использовать поле `before`.
```javascript
rpc.before.use(someMiddleware);
```
У `rpc.before.use` такой же интерфейс, как и у `use`.Если вам необходимо выполнить какую-то проверочную логику после того, как метод отработал, то для этого используется `rpc.after.use`. Такие middleware будут выполняться после того, как метод отработал и был сформирован итоговый `action`. Интерфейс добавления `after-middleware` такой же как и у обычных `middleware`, за исключением того, что четвертым аргументом в нее передается подготовленный итоговый `action`.
```javascript
rpc.after.use(someAfterMiddleware);
```
Остановка в такой `middleware` приводит к тому, что все дальнейшие `middleware` добавленные в `after` не будут выполнены, а из `rcp.call` вернется `undefined` вместо `action`.
Это отличное место, чтобы добавить реакции на возвращаемые данные, или обогатить `action`, или записать что-то в сессию клиента.## Возвращаемый `action`
После того как механизмы RPC отработают, метод `call` вернет специальный объект — `action`, который можно сразу отправить клиенту. Его анатомия:
```javascript
{
"type": "@@client/rpc/RETURN",
"id": "ID от запроса, если он был",
"payload": "То что вернул метод"
}
```Если во время выполнения `middleware` были остановлены, то `call` вернет `undefined`, вместо готового `action`.
Любой метод из модуля будет вызван с `await`, то есть методы могут возвращать `Promise` и в ответ попадет результат его штатного разрешения.
Если вы хотите, чтобы при возникновении ошибок автоматически генерировался возвращаемый `action`, используйте `safeCall` вместо обычного `call`. В таком случае при возникновении ошибки, которую можно перехватить, т. ч. асинхронной, будет сгенерирован `action`:
```javascript
{
"type": "@@client/rpc/ERROR",
"id": "ID от запроса, если он был",
"payload": {
"message": "Сообщение ошибки",
"code": "Eсли у ошибки был код, то он тут будет"
}
}
```
> `type` — может измениться в зависимости от входящего `action`.Вы можете переопределить метод генерации ответов, для этого нужно заменить методы:
- `makeOutAction(result, inAction)` — для успешных вызовов
- `makeErrorAction(error, inAction)` — если возникла ошибка## Анатомия входящего `action`
```json
{
"type": "@@service/rpc/CALL",
"id": "F12AE83",
"lib": "main",
"module": "main",
"method": "echo",
"arguments": ["Hello Redbone RPC"],
"flat": true,
"backType": null,
"merge": false,
"filter": null,
"errorType": null
}
```- `id` — запроса, задается клиентом. Будет прикладываться ко всем ответам протокола. Допустима строка до 24 символов, можно передать число, но в ответе, оно будет передано как строка.
- `lib` — имя библиотеки объектов, `main` — библиотека по-умолчанию
- `module` — имя модуля в библиотеке — `main` — модуль по-умолчанию
- `method` — имя метода в библиотеке — обязательное поле
- `arguments` — любое значение которое будет передано в качестве аргумента метода, который будет вызван
- `flat` — если в `arguments` передать массив, а `flat` задать как `true`, то массив будет разложен по аргументам метода
- `backType` — тип который будет передан в ответном `action`, если `null` или поле не задано, то будет использован тип по-умолчанию
- `merge` — если передать как `true`, то ответные данные будут лежать в корне действия, а не поля `payload`. Учтите, что `type` и `id` могут быть перекрыты в этом случае. Если результатом выполнения метода оказался не объект, то `merge` не будет иметь действия, и данные все равно будут находиться внутри поля `payload`
- `filter` — массив полей, которые нужно вернуть, если в ответе от метода был получен объект
- `errorType` — тип который будет передан в ответном `action` когда произойдет ошибка вызова (catch любой ошибки). Если задать как `null` будет использован тип по-умолчанию. (Только при `safeCall` или ручном модерировании ошибок).## Middleware из коробки
Для удобства в директории `/middlewares` есть несколько готовых `middleware` (пока только одна).
### Whitelist
Белый список: ограничивает библиотеки/модули/методы, который могут быть вызваны.
Чтобы её использовать, нужно создать экземляр класса, и передать в качестве аргумента список доступных методов:
```javascript
const whitelist = new Whitelist([
'main.mail.send',
'main.mail.get',
'main.filters',
'db.logs',
'monitoring'
]);
// Подключаем middleware
rpc.use(whitelist);
```Список можно подключать не только массивом, но и объектом:
```javascript
const whitelist = new Whitelist({
main: {
mail: {
send: true,
get: true
},
filters: true
},
db: {
logs: true
},
monitoring: true
});
// Подключаем middleware
rpc.use(whitelist);
```Если очень хочется, то можно задать и через Map:
```javascript
const whitelist = new Whitelist(
new Map([
['main.mail.send', true],
['main.mail.get', true],
['main.filters', true],
['db.logs', true],
['monitoring', true]
])
);
// Подключаем middleware
rpc.use(whitelist);
```Когда белый список формируется в виде объекта или словаря, в качестве значений узлов можно использовать функции.
Эти функции будут вызваны как `middleware`, а результат их выполнения будет использоваться для определения доступен метод/модуль/библиотека или нет.
**Важно**: такая функция должна возвращать булево значение, или оно будет приведено, то есть `undefined` будет расцениваться как `false` в `middleware`.