├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── client.js
├── examples
├── cli-extended.js
├── cli-single-function.js
├── client.js
├── custom-serializer.js
├── example.js
├── reconnect.js
├── server.js
├── use.js
└── validation.js
├── lib
├── bin.js
├── client.js
└── server.js
├── package.json
├── server.js
└── test
├── async-await.js
├── basic.test.js
├── cli.test.js
├── custom-parser-serializer.test.js
├── errors.test.js
├── get-port.js
├── ipc.test.js
├── reconnect.test.js
├── use.test.js
└── validation.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # mac files
61 | .DS_Store
62 |
63 | # vim swap files
64 | *.swp
65 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "8"
5 | - "7"
6 | - "6"
7 | - "5"
8 | - "4"
9 |
10 | after_script:
11 | - npm run coveralls
12 |
13 | notifications:
14 | email:
15 | on_success: never
16 | on_failure: always
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Tomas Della Vedova
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rinvoke
2 |
3 | [](http://standardjs.com/)
4 | [](https://travis-ci.org/delvedor/rinvoke) [](https://coveralls.io/github/delvedor/rinvoke?branch=master)
5 |
6 | Build your distributed functions system with **Rinvoke**!
7 | **Rinvoke** is a RPC library based on net sockets, can work both with **tcp sockets** and **ipc**.
8 | It has built in reconnect logic and supports multiple parser/serializers, such as [msgpack](http://msgpack.org/) or [protbuf](https://developers.google.com/protocol-buffers/).
9 | Internally uses [tentacoli](https://github.com/mcollina/tentacoli) to multiplex the requests and [avvio](https://github.com/mcollina/avvio) to guarantee the asynchronous bootstrap of the application, it also provide an handy request validation out of the box with [*JSON schema*](http://json-schema.org/).
10 |
11 |
12 | ## Install
13 | ```
14 | npm i rinvoke --save
15 | ```
16 |
17 |
18 | ## Usage
19 | Rinvoke could be used as a server or client, so when your require it you must specify it.
20 | Let's see an example for the server:
21 | ```js
22 | const rinvoke = require('rinvoke/server')()
23 |
24 | rinvoke.register('concat', (req, reply) => {
25 | reply(null, req.a + req.b)
26 | })
27 |
28 | rinvoke.listen(3000, err => {
29 | if (err) throw err
30 | })
31 | ```
32 | And now for the client:
33 | ```js
34 | const rinvoke = require('rinvoke/client')({
35 | port: 3000
36 | })
37 |
38 | rinvoke.invoke({
39 | procedure: 'concat',
40 | a: 'hello ',
41 | b: 'world'
42 | }, (err, result) => {
43 | if (err) {
44 | console.log(err)
45 | return
46 | }
47 | console.log(result)
48 | })
49 | ```
50 | The client could seem synchronous but internally everything is handled asynchronously with events.
51 | Checkout the [examples folder](https://github.com/delvedor/rinvoke/tree/master/examples) if you want to see more examples!
52 |
53 |
54 | ## API
55 |
56 |
57 | ### Server
58 | #### `server([opts])`
59 | Instance a new server, the options object can accept a custom parser/serializer via the `codec` field.
60 | ```js
61 | const rinvoke = require('rinvoke/server')({
62 | codec: {
63 | encode: JSON.stringify,
64 | decode: JSON.parse
65 | }
66 | })
67 | ```
68 | The default codec is *JSON*.
69 | **Events**:
70 | - `'connection'`
71 | - `'error'`
72 | - `'request'`
73 | - `'listening'`
74 |
75 | #### `register(procedureName, [schema,] procedureFunction)`
76 | Registers a new procedure, the name of the procedure must be a string, the function has the following signature: `(request, reply)` where `request` is the request object and `reply` a function t send the response back to the client.
77 | ```js
78 | rinvoke.register('concat', (req, reply) => {
79 | reply(null, req.a + req.b)
80 | })
81 | ```
82 | *Promises* and *async/await* are supported as well!
83 | ```js
84 | rinvoke.register('concat', async req => {
85 | return req.a + req.b
86 | })
87 | ```
88 |
89 | #### Validation
90 | Rinvoke offers you out of the box a nice and standard way to validate your requests, [*JSON schema*](http://json-schema.org/)!
91 | Internally uses [ajv](https://github.com/epoberezkin/ajv/blob/master/README.md) to achieve the maximum speed and correctness.
92 | ```js
93 | rinvoke.register('concat', {
94 | type: 'object',
95 | properties: {
96 | a: { type: 'string' },
97 | b: { type: 'string' }
98 | },
99 | required: ['a', 'b']
100 | }, (req, reply) => {
101 | reply(null, req.a + req.b)
102 | })
103 | ```
104 |
105 | #### `listen(portOrPath, [address], callback)`
106 | Run the server over the specified `port` (and `address`, default to `127.0.0.1`), if you specify a path (as a string)
107 | it will use the system socket to perform ipc.
108 | ```js
109 | rinvoke.listen(3000, err => {
110 | if (err) throw err
111 | })
112 |
113 | rinvoke.listen(3000, '127.0.0.1', err => {
114 | if (err) throw err
115 | })
116 |
117 | rinvoke.listen('/tmp/socket.sock', err => {
118 | if (err) throw err
119 | })
120 | ```
121 |
122 |
123 | ### Client
124 |
125 | #### `client(options)`
126 | Instance a new client, the options object must contain a `port` or `path` field, furthermore can accept a custom parser/serializer via the `codec` field. If you want to activate the automatic reconnection handling pass `reconnect: true` (3 attempts with 1s timeout), if you want to configure the timeout handling pass an object like the following:
127 | ```js
128 | const rinvoke = require('rinvoke/client')({
129 | port: 3000,
130 | address: '127.0.0.1'
131 | reconnect: {
132 | attempts: 5,
133 | timeout: 2000
134 | },
135 | codec: {
136 | encode: JSON.stringify,
137 | decode: JSON.parse
138 | }
139 | })
140 | ```
141 | The default codec is *JSON*.
142 | **Events**:
143 | - `'connect'`
144 | - `'error'`
145 | - `'close'`
146 | - `'timeout'`
147 |
148 | #### `invoke(request, callback)`
149 | Invoke a procedure on the server, the request object **must** contain the key `procedure` with the name of the function to call.
150 | The callback is a function with the following signature: `(error, response)`.
151 | ```js
152 | rinvoke.invoke({
153 | procedure: 'concat',
154 | a: 'hello ',
155 | b: 'world'
156 | }, (err, result) => {
157 | if (err) {
158 | console.log(err)
159 | return
160 | }
161 | console.log(result)
162 | })
163 | ```
164 | *Promises* are supported as well!
165 | ```js
166 | rinvoke
167 | .invoke({
168 | procedure: 'concat',
169 | a: 'a',
170 | b: 'b'
171 | })
172 | .then(console.log)
173 | .catch(console.log)
174 | ```
175 |
176 | #### `fire(request [, callback])`
177 | Fire (and forget) a procedure on the server, the request object **must** contain the key `procedure` with the name of the function to call.
178 | The optional callback will be called if there is an error while sending the message, or after the message has been sent successfully.
179 |
180 | ```js
181 | rinvoke.fire({
182 | procedure: 'concat',
183 | a: 'hello ',
184 | b: 'world'
185 | })
186 | ```
187 |
188 | #### `timeout(time)`
189 | Sets the timeout of the socket.
190 | #### `keepAlive(bool)`
191 | Sets the `keep-alive` property.
192 |
193 |
194 | ### Method for both client and server
195 | #### `use(callback)`
196 | The callback is a function witb the following signature: `instance, options, next`.
197 | Where `instance` is the client instance, options, is an options object and `next` a function you must call when your code is ready.
198 | This api is useful if you need to load an utility, a database connection for example. `use` will guarantee the load order an that your client/server will boot up once every `use` has completed.
199 | ```js
200 | rinvoke.use((instance, opts, next) => {
201 | dbClient.connect(opts.url, (err, conn) => {
202 | instance.db = conn // now you can access in your function the database connection with `this.db`
203 | next()
204 | })
205 | })
206 | ```
207 | #### `onClose(callback)`
208 | Hook that will be called once you fire the `close` callback.
209 | ```js
210 | rinvoke.onClose((instance, done) => {
211 | // do something
212 | done()
213 | })
214 | ```
215 |
216 | #### `close(callback)`
217 | Once you call this function the socket server and client will close and all the registered functions with `onClose` will be called.
218 | ```js
219 | rinvoke.close((err, instance, done) => {
220 | // do something
221 | done()
222 | })
223 | ```
224 |
225 | ### CLI
226 | You can even run the server with the integrated cli!
227 | In your `package.json` add:
228 | ```json
229 | {
230 | "scripts": {
231 | "start": "rinvoke server.js"
232 | }
233 | }
234 | ```
235 | And then create your server file:
236 | ```js
237 | module.exports = async req => `Hello ${req.name}!`
238 | ```
239 | You can also use an extended version of the above example:
240 | ```js
241 | function sayHello (rinvoke, opts, next) {
242 | rinvoke.register('hello', (req, reply) => {
243 | reply(null, { hello: 'world' })
244 | })
245 |
246 | next()
247 | }
248 |
249 | module.exports = sayHello
250 | ```
251 | The options of the cli are:
252 | ```
253 | --port -p # default 3000
254 | --address -a # default 127.0.0.1
255 | --path -P # path of the ipc web socket
256 | --name -n # name of your exported function
257 | ```
258 |
259 |
260 | ## Acknowledgements
261 |
262 | This project is kindly sponsored by [LetzDoIt](http://www.letzdoitapp.com/).
263 |
264 |
265 | ## License
266 |
267 | [MIT](./LICENSE)
268 |
269 | Copyright © 2017 Tomas Della Vedova
270 |
--------------------------------------------------------------------------------
/client.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/client')
2 |
--------------------------------------------------------------------------------
/examples/cli-extended.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | function sayHello (rinvoke, opts, next) {
4 | rinvoke.register('hello', (req, reply) => {
5 | reply(null, { hello: 'world' })
6 | })
7 |
8 | next()
9 | }
10 |
11 | module.exports = sayHello
12 |
--------------------------------------------------------------------------------
/examples/cli-single-function.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | function sayHello (req, reply) {
4 | reply(null, 'hello!')
5 | }
6 |
7 | module.exports = sayHello
8 |
--------------------------------------------------------------------------------
/examples/client.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const rinvoke = require('../client')({
4 | port: 3030,
5 | host: '127.0.0.1'
6 | })
7 |
8 | rinvoke.invoke({
9 | procedure: 'concat',
10 | a: 'a',
11 | b: 'b'
12 | }, (err, result) => {
13 | if (err) console.log(err)
14 | console.log(result)
15 | })
16 |
17 | rinvoke
18 | .invoke({
19 | procedure: 'concat',
20 | a: 'a',
21 | b: 'b'
22 | })
23 | .then(console.log)
24 | .catch(console.log)
25 |
--------------------------------------------------------------------------------
/examples/custom-serializer.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const msgpack = require('msgpack5')()
4 | const server = require('../lib/server')({
5 | codec: msgpack
6 | })
7 | const Client = require('../lib/client')
8 |
9 | // register a new function
10 | server.register('concat', (req, reply) => reply(null, req.a + req.b))
11 |
12 | // run the listener
13 | server.listen(3030, err => {
14 | if (err) throw err
15 |
16 | // connect to the listener
17 | const client = Client({
18 | port: 3030,
19 | codec: msgpack
20 | })
21 | // invoke the remote function
22 | client.invoke({
23 | procedure: 'concat',
24 | a: 'a',
25 | b: 'b'
26 | }, (err, res) => {
27 | if (err) console.log(err)
28 | console.log('concat:', res)
29 | // close the connections
30 | server.close(err => { if (err) throw err })
31 | client.close(err => { if (err) throw err })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/examples/example.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const server = require('../lib/server')()
4 | const Client = require('../lib/client')
5 |
6 | // register a new function
7 | server.register('concat', (req, reply) => reply(null, req.a + req.b))
8 |
9 | // run the listener
10 | server.listen(3030, err => {
11 | if (err) throw err
12 |
13 | // connect to the listener
14 | const client = Client({ port: 3030 })
15 | // invoke the remote function
16 | client.invoke({
17 | procedure: 'concat',
18 | a: 'a',
19 | b: 'b'
20 | }, (err, res) => {
21 | if (err) console.log(err)
22 | console.log('concat:', res)
23 | // close the connections
24 | server.close(err => { if (err) throw err })
25 | client.close(err => { if (err) throw err })
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/examples/reconnect.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const server = require('../lib/server')()
4 | const Client = require('../lib/client')
5 |
6 | // register a new function
7 | server.register('concat', (req, reply) => reply(null, req.a + req.b))
8 |
9 | // run the listener
10 | server.listen(3030, err => {
11 | if (err) throw err
12 |
13 | // connect to the listener
14 | const client = Client({
15 | port: 3030,
16 | reconnect: { // could be also true
17 | attempts: 3,
18 | timeout: 1000
19 | }
20 | })
21 | // invoke the remote function
22 | client.invoke({
23 | procedure: 'concat',
24 | a: 'a',
25 | b: 'b'
26 | }, (err, res) => {
27 | if (err) console.log(err)
28 | console.log('concat:', res)
29 | // close the connections
30 | server.close(err => { if (err) throw err })
31 | client.close(err => { if (err) throw err })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/examples/server.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const rinvoke = require('../server')()
4 |
5 | rinvoke.register('concat', (req, reply) => reply(null, req.a + req.b))
6 |
7 | rinvoke.listen(3030, err => {
8 | if (err) throw err
9 | })
10 |
--------------------------------------------------------------------------------
/examples/use.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const server = require('../lib/server')()
4 | const Client = require('../lib/client')
5 |
6 | server.use((instance, opts, next) => {
7 | instance.concat = (a, b) => a + b
8 | next()
9 | })
10 | // register a new function
11 | server.register('concat', (req, reply) => {
12 | reply(null, server.concat(req.a, req.b))
13 | })
14 |
15 | // run the listener
16 | server.listen(3030, err => {
17 | if (err) throw err
18 |
19 | // connect to the listener
20 | const client = Client({ port: 3030 })
21 | // invoke the remote function
22 | client.invoke({
23 | procedure: 'concat',
24 | a: 'a',
25 | b: 'b'
26 | }, (err, res) => {
27 | if (err) console.log(err)
28 | console.log('concat:', res)
29 | // close the connections
30 | server.close(err => { if (err) throw err })
31 | client.close(err => { if (err) throw err })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/examples/validation.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const server = require('../lib/server')()
4 | const Client = require('../lib/client')
5 |
6 | // register a new function
7 | server.register('concat', {
8 | type: 'object',
9 | properties: {
10 | a: { type: 'string' },
11 | b: { type: 'string' }
12 | },
13 | required: ['a', 'b']
14 | }, (req, reply) => {
15 | reply(null, req.a + req.b)
16 | })
17 |
18 | // run the listener
19 | server.listen(3030, err => {
20 | if (err) throw err
21 |
22 | // connect to the listener
23 | const client = Client({ port: 3030 })
24 | // invoke the remote function
25 | client.invoke({
26 | procedure: 'concat',
27 | a: 'a',
28 | b: 'b'
29 | }, (err, res) => {
30 | if (err) console.log(err)
31 | console.log('concat:', res)
32 | // close the connections
33 | server.close(err => { if (err) throw err })
34 | client.close(err => { if (err) throw err })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/lib/bin.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const path = require('path')
6 | const minimist = require('minimist')
7 | const Server = require('./server')
8 | const version = require('../package.json').version
9 |
10 | function start (opts, callback) {
11 | /* istanbul ignore if */
12 | if (opts.help) {
13 | console.log(`# Rinvoke v${version} #
14 |
15 | --port -p # default 3000
16 | --address -a # default 127.0.0.1
17 | --path -P # path of the ipc web socket
18 | --name -n # name of your exported function
19 | `)
20 | return
21 | }
22 |
23 | var fn = null
24 | try {
25 | fn = require(path.resolve(process.cwd(), opts._[0]))
26 | } catch (e) {
27 | /* istanbul ignore next */
28 | console.log(`Cannot find the specified file: '${opts._[0]}'`)
29 | /* istanbul ignore next */
30 | process.exit(1)
31 | }
32 |
33 | const server = Server(opts)
34 | if (opts.name) {
35 | server.register(opts.name, fn)
36 | } else {
37 | server.use(fn)
38 | }
39 |
40 | server.listen(opts.port, err => {
41 | /* istanbul ignore if */
42 | if (err) throw err
43 | /* istanbul ignore else */
44 | if (callback) {
45 | callback(server)
46 | } else {
47 | console.log(`Function listening at ${opts.port}`)
48 | }
49 | })
50 | }
51 |
52 | /* istanbul ignore next */
53 | function cli () {
54 | start(minimist(process.argv.slice(2), {
55 | integer: ['port'],
56 | alias: {
57 | port: 'p',
58 | help: 'h',
59 | address: 'a',
60 | path: 'P',
61 | name: 'n'
62 | },
63 | default: {
64 | port: 3000,
65 | host: '127.0.0.1'
66 | }
67 | }))
68 | }
69 |
70 | /* istanbul ignore if */
71 | if (require.main === module) {
72 | cli()
73 | }
74 |
75 | module.exports = { start }
76 |
--------------------------------------------------------------------------------
/lib/client.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const debug = require('debug')('rinvoke-client')
4 | const assert = require('assert')
5 | const EE = require('events').EventEmitter
6 | const inherits = require('util').inherits
7 | const net = require('net')
8 | const pump = require('pump')
9 | const tentacoli = require('tentacoli')
10 | const avvio = require('avvio')
11 | const promisify = require('util.promisify')
12 |
13 | function Client (opts) {
14 | if (!(this instanceof Client)) {
15 | return new Client(opts)
16 | }
17 |
18 | avvio(this)
19 |
20 | assert(opts, 'Missing client options')
21 | this._opts = opts
22 | this._opts.host = this._opts.host || '127.0.0.1'
23 | this._opts.codec = this._opts.codec || {
24 | encode: JSON.stringify,
25 | decode: JSON.parse
26 | }
27 |
28 | this._reconnect = !!this._opts.reconnect
29 | if (typeof this._opts.reconnect !== 'object') {
30 | this._opts.reconnect = {}
31 | }
32 | this._reconnectAttempts = this._opts.reconnect.attempts || 3
33 | this._reconnectTimeout = this._opts.reconnect.timeout || 1000
34 | this._reconnectAttemptsCount = 0
35 |
36 | this._socket = null
37 | this._client = null
38 |
39 | this.ready(runClient)
40 | this.isReady = false
41 | this.isClosing = false
42 |
43 | this.onClose(instance => {
44 | debug('on close')
45 | instance.isClosing = true
46 | instance._socket.end()
47 | instance._client.destroy()
48 | })
49 | }
50 |
51 | function runClient (err, context, done) {
52 | /* istanbul ignore if */
53 | if (err) throw err
54 | if (context._opts.path) {
55 | debug('run the ipc client')
56 | context._socket = net.connect(context._opts.path)
57 | } else {
58 | debug('run the tcp client')
59 | context._socket = net.connect(context._opts.port, context._opts.host)
60 | }
61 | context._client = tentacoli(context._opts)
62 | pump(context._socket, context._client, context._socket)
63 |
64 | context._socket.on('connect', handleConnect)
65 | function handleConnect () {
66 | debug('connected to the server')
67 | context.isReady = true
68 | context._reconnectAttemptsCount = 0
69 | context.emit('connect')
70 | done()
71 | }
72 |
73 | context._socket.on('timeout', handleTimeout)
74 | /* istanbul ignore next */
75 | function handleTimeout () {
76 | debug('socket timeout event')
77 | handleReconnect(() => {
78 | context.close()
79 | context.emit('timeout')
80 | })
81 | }
82 |
83 | context._socket.on('error', handleError)
84 | /* istanbul ignore next */
85 | function handleError (err) {
86 | debug('socket error event', err)
87 | handleReconnect(() => {
88 | context.close()
89 | context.emit('error', err)
90 | })
91 | }
92 |
93 | context._socket.on('close', handleClose)
94 | /* istanbul ignore next */
95 | function handleClose () {
96 | debug('socket close event')
97 | if (context.isClosing) return
98 | handleReconnect(() => {
99 | context.close()
100 | context.emit('close')
101 | })
102 | }
103 |
104 | context._client.on('error', handleTError)
105 | /* istanbul ignore next */
106 | function handleTError (err) {
107 | debug('tentacoli error event', err)
108 | handleReconnect(() => {
109 | context.close()
110 | context.emit('error', err)
111 | })
112 | }
113 |
114 | function handleReconnect (cb) {
115 | if (!context._reconnect) {
116 | cb()
117 | } else if (context._reconnectAttemptsCount++ >= context._reconnectAttempts) {
118 | cb()
119 | } else {
120 | debug('remove listeners before try reconnect')
121 | context._socket.removeListener('connect', handleConnect)
122 | context._socket.removeListener('timeout', handleTimeout)
123 | context._socket.removeListener('error', handleError)
124 | context._socket.removeListener('close', handleClose)
125 | context._client.removeListener('error', handleTError)
126 |
127 | context._socket.destroy()
128 | context._client.destroy()
129 | context._socket = null
130 | context._client = null
131 | context.isReady = false
132 |
133 | setTimeout(() => {
134 | debug('try reconnect')
135 | runClient(null, context, () => {})
136 | }, context._reconnectTimeout)
137 | }
138 | }
139 | }
140 |
141 | inherits(Client, EE)
142 |
143 | Client.prototype.invoke = promisify(function invoke (opts, cb) {
144 | assert(typeof opts.procedure === 'string', 'Procedure must be a string')
145 |
146 | if (!this.isReady) {
147 | debug('client not ready yet, wait for connect')
148 | this.once('connect', () => {
149 | debug('invoke', opts)
150 | this._client.request(opts, cb)
151 | })
152 | return
153 | }
154 |
155 | debug('invoke', opts)
156 | this._client.request(opts, cb)
157 | })
158 |
159 | Client.prototype.fire = promisify(function fire (opts, cb) {
160 | assert(typeof opts.procedure === 'string', 'Procedure must be a string')
161 | cb = cb || noop
162 |
163 | if (!this.isReady) {
164 | debug('client not ready yet, wait for connect')
165 | this.once('connect', () => {
166 | debug('fire', opts)
167 | this._client.fire(opts, cb)
168 | })
169 | return
170 | }
171 |
172 | debug('fire', opts)
173 | this._client.fire(opts, cb)
174 | })
175 |
176 | Client.prototype.timeout = function timeout (t) {
177 | assert(typeof t === 'number', 'timeout must be a number')
178 | debug('set timeout', t)
179 |
180 | if (!this.isReady) {
181 | this.once('connect', () => {
182 | this._socket.setTimeout(t)
183 | })
184 | return
185 | }
186 |
187 | this._socket.setTimeout(t)
188 | }
189 |
190 | Client.prototype.keepAlive = function timeout (bool) {
191 | assert(typeof bool === 'boolean', 'keepAlive must be a boolean')
192 | debug('set keep alive', bool)
193 |
194 | if (!this.isReady) {
195 | this.once('connect', () => {
196 | this._socket.setKeepAlive(bool)
197 | })
198 | return
199 | }
200 |
201 | this._socket.setKeepAlive(bool)
202 | }
203 |
204 | function noop () {}
205 |
206 | module.exports = Client
207 |
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const debug = require('debug')('rinvoke-server')
4 | const assert = require('assert')
5 | const net = require('net')
6 | const pump = require('pump')
7 | const tentacoli = require('tentacoli')
8 | const avvio = require('avvio')
9 | const EE = require('events').EventEmitter
10 | const inherits = require('util').inherits
11 | const Ajv = require('ajv')
12 | const ajv = new Ajv({ coerceTypes: true })
13 |
14 | /* istanbul ignore next */
15 | const noop = err => { if (err) throw err }
16 |
17 | function Server (opts) {
18 | if (!(this instanceof Server)) {
19 | return new Server(opts)
20 | }
21 |
22 | avvio(this)
23 |
24 | this._functions = Object.create(null)
25 | this._validations = Object.create(null)
26 | this._server = net.createServer(create.bind(this))
27 |
28 | /* istanbul ignore next */
29 | this._server.on('error', err => {
30 | debug('error event', err)
31 | this.close()
32 | this.emit('error', err)
33 | })
34 |
35 | this._server.on('connection', () => {
36 | debug('new connection')
37 | this.emit('connection')
38 | })
39 |
40 | this._opts = opts || {}
41 | this._opts.codec = this._opts.codec || {
42 | encode: JSON.stringify,
43 | decode: JSON.parse
44 | }
45 |
46 | this.onClose((instance, done) => {
47 | debug('on close')
48 | instance._server.close(done)
49 | })
50 | }
51 |
52 | inherits(Server, EE)
53 |
54 | function create (original) {
55 | const stream = tentacoli(this._opts)
56 | pump(stream, original, stream)
57 | stream.on('request', handle.bind(this))
58 |
59 | /* istanbul ignore next */
60 | stream.on('error', err => {
61 | debug('tentacoli error', err)
62 | this.close()
63 | this.emit('error', err)
64 | })
65 | }
66 |
67 | function handle (request, reply) {
68 | debug('incoming request', request)
69 | this.emit('request', request)
70 | var fn = this._functions[request.procedure]
71 | var validate = this._validations[request.procedure]
72 |
73 | if (fn) {
74 | debug('found procedure')
75 |
76 | if (validate) {
77 | debug('validate request')
78 | var valid = validate(request)
79 | if (valid !== true) {
80 | debug('request not valid')
81 | reply(new Error('400'), valid)
82 | return
83 | }
84 | debug('request valid')
85 | }
86 |
87 | var result = fn.call(this, request, reply)
88 | } else {
89 | debug('procedure not found')
90 | reply(new Error('404'), null)
91 | return
92 | }
93 |
94 | if (result && result.then) {
95 | result
96 | .then(res => {
97 | reply.call(this, null, res)
98 | })
99 | .catch(err => {
100 | reply.call(this, err, null)
101 | })
102 | }
103 | }
104 |
105 | Server.prototype.register = function (procedure, schema, fn) {
106 | if (!fn) {
107 | fn = schema
108 | schema = null
109 | }
110 | assert(typeof procedure === 'string', 'procedure must be a string')
111 | assert(typeof schema === 'object', 'schema must be an object')
112 | assert(typeof fn === 'function', 'fn must be a function')
113 | assert(!this._functions[procedure], `You have already registered procedure '${procedure}'`)
114 | debug('register new procedure:', procedure)
115 |
116 | if (schema) {
117 | schema.properties.procedure = { type: 'string' }
118 | schema.required = schema.required || []
119 | schema.required.push('procedure')
120 | this._validations[procedure] = ajv.compile(schema)
121 | }
122 |
123 | this._functions[procedure] = fn
124 | return this
125 | }
126 |
127 | Server.prototype.listen = function (portOrPath, address, cb) {
128 | assert(typeof portOrPath === 'number' || typeof portOrPath === 'string', 'portOrPath should be a number or a string')
129 | if (typeof address === 'function') {
130 | cb = address
131 | address = null
132 | }
133 | cb = cb || noop
134 |
135 | this.ready(err => {
136 | debug(`Run the ${typeof portOrPath === 'number' ? 'tcp' : 'ipc'} server`)
137 | /* istanbul ignore if */
138 | if (err) return cb(err)
139 | if (address) {
140 | this._server.listen(portOrPath, address, err => {
141 | cb(err)
142 | this.emit('listening')
143 | })
144 | } else {
145 | this._server.listen(portOrPath, err => {
146 | cb(err)
147 | this.emit('listening')
148 | })
149 | }
150 | })
151 | }
152 |
153 | module.exports = Server
154 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rinvoke",
3 | "version": "1.1.0",
4 | "description": "RPC library based on net sockets, can work both with tcp sockets and ipc",
5 | "main": "./server.js",
6 | "bin": {
7 | "rinvoke": "./lib/bin.js"
8 | },
9 | "scripts": {
10 | "test": "standard && tap test/*.test.js",
11 | "coveralls": "tap test/*.test.js --cov --coverage-report=text-lcov | coveralls"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/delvedor/rinvoke.git"
16 | },
17 | "keywords": [
18 | "rpc",
19 | "tcp",
20 | "ipc",
21 | "function",
22 | "remote",
23 | "multiplex",
24 | "fire",
25 | "forget",
26 | "fire and forget"
27 | ],
28 | "author": "Tomas Della Vedova - @delvedor (http://delved.org)",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/delvedor/rinvoke/issues"
32 | },
33 | "homepage": "https://github.com/delvedor/rinvoke#readme",
34 | "dependencies": {
35 | "ajv": "^5.2.2",
36 | "avvio": "^2.0.3",
37 | "debug": "^3.0.0",
38 | "minimist": "^1.2.0",
39 | "pump": "^1.0.2",
40 | "tentacoli": "^1.0.0",
41 | "util.promisify": "^1.0.0"
42 | },
43 | "devDependencies": {
44 | "coveralls": "^2.13.1",
45 | "msgpack5": "^3.5.0",
46 | "standard": "^10.0.3",
47 | "tap": "^10.7.2",
48 | "then-sleep": "^1.0.1"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/server')
2 |
--------------------------------------------------------------------------------
/test/async-await.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Server = require('../lib/server')
4 | const Client = require('../lib/client')
5 | const sleep = require('then-sleep')
6 |
7 | function asyncTest (test, port) {
8 | test('should support async await', t => {
9 | t.plan(7)
10 | const server = Server()
11 |
12 | server.register('concat', async req => {
13 | t.equal(req.a, 'a')
14 | t.equal(req.b, 'b')
15 | await sleep(200)
16 | return req.a + req.b
17 | })
18 |
19 | server.listen(port, err => {
20 | t.error(err)
21 |
22 | const client = Client({ port })
23 |
24 | client.invoke({
25 | procedure: 'concat',
26 | a: 'a',
27 | b: 'b'
28 | }, (err, res) => {
29 | t.error(err)
30 | t.equal(res, 'ab')
31 | client.close(t.error)
32 | server.close(t.error)
33 | })
34 | })
35 | })
36 | }
37 |
38 | module.exports = asyncTest
39 |
--------------------------------------------------------------------------------
/test/basic.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Server = require('../lib/server')
7 | const Client = require('../lib/client')
8 | const getPort = require('./get-port')
9 |
10 | var port = 0
11 |
12 | beforeEach(done => {
13 | getPort((err, p) => {
14 | if (err) throw err
15 | port = p
16 | done()
17 | })
18 | })
19 |
20 | test('should register a function and reply to the request', t => {
21 | t.plan(8)
22 | const server = Server()
23 |
24 | server.register('concat', (req, reply) => {
25 | t.equal(req.a, 'a')
26 | t.equal(req.b, 'b')
27 | t.is(typeof reply, 'function')
28 | reply(null, req.a + req.b)
29 | })
30 |
31 | server.listen(port, err => {
32 | t.error(err)
33 |
34 | const client = Client({ port })
35 |
36 | client.invoke({
37 | procedure: 'concat',
38 | a: 'a',
39 | b: 'b'
40 | }, (err, res) => {
41 | t.error(err)
42 | t.equal(res, 'ab')
43 | client.close(t.error)
44 | server.close(t.error)
45 | })
46 | })
47 | })
48 |
49 | test('should register a function and reply to the request (multiple)', t => {
50 | t.plan(13)
51 | const server = Server()
52 |
53 | server.register('concat', (req, reply) => {
54 | t.equal(req.a, 'a')
55 | t.equal(req.b, 'b')
56 | t.is(typeof reply, 'function')
57 | reply(null, req.a + req.b)
58 | })
59 |
60 | server.listen(port, err => {
61 | t.error(err)
62 |
63 | const client = Client({ port })
64 |
65 | client.invoke({
66 | procedure: 'concat',
67 | a: 'a',
68 | b: 'b'
69 | }, (err, res) => {
70 | t.error(err)
71 | t.equal(res, 'ab')
72 |
73 | client.invoke({
74 | procedure: 'concat',
75 | a: 'a',
76 | b: 'b'
77 | }, (err, res) => {
78 | t.error(err)
79 | t.equal(res, 'ab')
80 | client.close(t.error)
81 | server.close(t.error)
82 | })
83 | })
84 | })
85 | })
86 |
87 | test('should register a function and reply to the request - error', t => {
88 | t.plan(5)
89 | const server = Server()
90 |
91 | server.register('concat', (req, reply) => {
92 | reply(new Error('some error'), null)
93 | })
94 |
95 | server.listen(port, err => {
96 | t.error(err)
97 |
98 | const client = Client({ port })
99 |
100 | client.invoke({
101 | procedure: 'concat',
102 | a: 'a',
103 | b: 'b'
104 | }, (err, res) => {
105 | t.equal(err.message, 'some error')
106 | t.equal(res, null)
107 | client.close(t.error)
108 | server.close(t.error)
109 | })
110 | })
111 | })
112 |
113 | test('function not found', t => {
114 | t.plan(5)
115 | const server = Server()
116 |
117 | server.listen(port, err => {
118 | t.error(err)
119 |
120 | const client = Client({ port })
121 |
122 | client.invoke({
123 | procedure: 'concat',
124 | a: 'a',
125 | b: 'b'
126 | }, (err, res) => {
127 | t.equal(err.message, '404')
128 | t.equal(res, null)
129 | client.close(t.error)
130 | server.close(t.error)
131 | })
132 | })
133 | })
134 |
135 | test('should register a function and reply to the request (promises - resolve)', t => {
136 | t.plan(5)
137 | const server = Server()
138 |
139 | server.register('concat', (req, reply) => {
140 | const p = new Promise((resolve, reject) => {
141 | resolve(req.a + req.b)
142 | })
143 | return p
144 | })
145 |
146 | server.listen(port, err => {
147 | t.error(err)
148 |
149 | const client = Client({ port })
150 |
151 | client.invoke({
152 | procedure: 'concat',
153 | a: 'a',
154 | b: 'b'
155 | }, (err, res) => {
156 | t.error(err)
157 | t.equal(res, 'ab')
158 | client.close(t.error)
159 | server.close(t.error)
160 | })
161 | })
162 | })
163 |
164 | test('should register a function and reply to the request (promises - reject)', t => {
165 | t.plan(5)
166 | const server = Server()
167 |
168 | server.register('concat', (req, reply) => {
169 | const p = new Promise((resolve, reject) => {
170 | reject(new Error('some error'))
171 | })
172 | return p
173 | })
174 |
175 | server.listen(port, err => {
176 | t.error(err)
177 |
178 | const client = Client({ port })
179 |
180 | client.invoke({
181 | procedure: 'concat',
182 | a: 'a',
183 | b: 'b'
184 | }, (err, res) => {
185 | t.equal(err.message, 'some error')
186 | t.equal(res, null)
187 | client.close(t.error)
188 | server.close(t.error)
189 | })
190 | })
191 | })
192 |
193 | test('client should set timeout and keep alive', t => {
194 | t.plan(8)
195 | const server = Server()
196 |
197 | server.register('concat', (req, reply) => {
198 | t.equal(req.a, 'a')
199 | t.equal(req.b, 'b')
200 | t.is(typeof reply, 'function')
201 | reply(null, req.a + req.b)
202 | })
203 |
204 | server.listen(port, err => {
205 | t.error(err)
206 |
207 | const client = Client({ port })
208 |
209 | client.timeout(500)
210 | client.keepAlive(true)
211 |
212 | client.invoke({
213 | procedure: 'concat',
214 | a: 'a',
215 | b: 'b'
216 | }, (err, res) => {
217 | t.error(err)
218 | t.equal(res, 'ab')
219 | client.close(t.error)
220 | server.close(t.error)
221 | })
222 | })
223 | })
224 |
225 | test('client should set timeout and keep alive / 2', t => {
226 | t.plan(8)
227 | const server = Server()
228 |
229 | server.register('concat', (req, reply) => {
230 | t.equal(req.a, 'a')
231 | t.equal(req.b, 'b')
232 | t.is(typeof reply, 'function')
233 | reply(null, req.a + req.b)
234 | })
235 |
236 | server.listen(port, err => {
237 | t.error(err)
238 |
239 | const client = Client({ port })
240 |
241 | client.invoke({
242 | procedure: 'concat',
243 | a: 'a',
244 | b: 'b'
245 | }, (err, res) => {
246 | t.error(err)
247 | t.equal(res, 'ab')
248 |
249 | client.timeout(500)
250 | client.keepAlive(true)
251 |
252 | client.close(t.error)
253 | server.close(t.error)
254 | })
255 | })
256 | })
257 |
258 | test('async await support', t => {
259 | if (Number(process.versions.node[0]) >= 8) {
260 | require('./async-await')(t.test, port)
261 | } else {
262 | t.pass('Skip because Node version < 8')
263 | }
264 | t.end()
265 | })
266 |
267 | test('listen should work even without a callback', t => {
268 | t.plan(7)
269 | const server = Server()
270 |
271 | server.register('concat', (req, reply) => {
272 | t.equal(req.a, 'a')
273 | t.equal(req.b, 'b')
274 | t.is(typeof reply, 'function')
275 | reply(null, req.a + req.b)
276 | })
277 |
278 | server.listen(port)
279 |
280 | server.on('listening', () => {
281 | const client = Client({ port })
282 |
283 | client.invoke({
284 | procedure: 'concat',
285 | a: 'a',
286 | b: 'b'
287 | }, (err, res) => {
288 | t.error(err)
289 | t.equal(res, 'ab')
290 | client.close(t.error)
291 | server.close(t.error)
292 | })
293 | })
294 | })
295 |
296 | test('should accept a port and an address', t => {
297 | t.plan(7)
298 | const server = Server()
299 |
300 | server.register('concat', (req, reply) => {
301 | t.equal(req.a, 'a')
302 | t.equal(req.b, 'b')
303 | t.is(typeof reply, 'function')
304 | reply(null, req.a + req.b)
305 | })
306 |
307 | server.listen(port, '127.0.0.1')
308 |
309 | server.on('listening', () => {
310 | const client = Client({ port })
311 |
312 | client.invoke({
313 | procedure: 'concat',
314 | a: 'a',
315 | b: 'b'
316 | }, (err, res) => {
317 | t.error(err)
318 | t.equal(res, 'ab')
319 | client.close(t.error)
320 | server.close(t.error)
321 | })
322 | })
323 | })
324 |
325 | test('fire and forget', t => {
326 | t.plan(6)
327 | const server = Server()
328 | var client = null
329 |
330 | server.register('concat', (req, reply) => {
331 | t.equal(req.a, 'a')
332 | t.equal(req.b, 'b')
333 | t.is(typeof reply, 'function')
334 | client.close(t.error)
335 | server.close(t.error)
336 | })
337 |
338 | server.listen(port, err => {
339 | t.error(err)
340 |
341 | client = Client({ port })
342 |
343 | client.fire({
344 | procedure: 'concat',
345 | a: 'a',
346 | b: 'b'
347 | })
348 | })
349 | })
350 |
351 | test('fire and forget (multiple)', t => {
352 | t.plan(9)
353 | const server = Server()
354 | var client = null
355 | var check = false
356 |
357 | server.register('concat', (req, reply) => {
358 | t.equal(req.a, 'a')
359 | t.equal(req.b, 'b')
360 | t.is(typeof reply, 'function')
361 | if (check) {
362 | client.close(t.error)
363 | server.close(t.error)
364 | } else {
365 | check = true
366 | }
367 | })
368 |
369 | server.listen(port, err => {
370 | t.error(err)
371 |
372 | client = Client({ port })
373 |
374 | client.fire({
375 | procedure: 'concat',
376 | a: 'a',
377 | b: 'b'
378 | })
379 |
380 | setTimeout(() => {
381 | client.fire({
382 | procedure: 'concat',
383 | a: 'a',
384 | b: 'b'
385 | })
386 | }, 500)
387 | })
388 | })
389 |
390 | test('fire and forget with cb', t => {
391 | t.plan(7)
392 | const server = Server()
393 | var client = null
394 |
395 | server.register('concat', (req, reply) => {
396 | t.equal(req.a, 'a')
397 | t.equal(req.b, 'b')
398 | t.is(typeof reply, 'function')
399 | client.close(t.error)
400 | server.close(t.error)
401 | })
402 |
403 | server.listen(port, err => {
404 | t.error(err)
405 |
406 | client = Client({ port })
407 |
408 | client.fire({
409 | procedure: 'concat',
410 | a: 'a',
411 | b: 'b'
412 | }, t.error)
413 | })
414 | })
415 |
416 | test('fire and forget with cb, should not care abotu reply', t => {
417 | t.plan(7)
418 | const server = Server()
419 | var client = null
420 |
421 | server.register('concat', (req, reply) => {
422 | t.equal(req.a, 'a')
423 | t.equal(req.b, 'b')
424 | t.is(typeof reply, 'function')
425 | reply(new Error('kaboom'))
426 | client.close(t.error)
427 | server.close(t.error)
428 | })
429 |
430 | server.listen(port, err => {
431 | t.error(err)
432 |
433 | client = Client({ port })
434 |
435 | client.fire({
436 | procedure: 'concat',
437 | a: 'a',
438 | b: 'b'
439 | }, t.error)
440 | })
441 | })
442 |
--------------------------------------------------------------------------------
/test/cli.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Client = require('../lib/client')
7 | const cli = require('../lib/bin')
8 | const getPort = require('./get-port')
9 |
10 | var port = 0
11 |
12 | beforeEach(done => {
13 | getPort((err, p) => {
14 | if (err) throw err
15 | port = p
16 | done()
17 | })
18 | })
19 |
20 | test('cli single function', t => {
21 | t.plan(4)
22 |
23 | cli.start({
24 | host: '127.0.0.1',
25 | port: port,
26 | name: 'hello',
27 | _: ['./examples/cli-single-function.js']
28 | }, server => {
29 | const client = Client({ port })
30 |
31 | client.invoke({ procedure: 'hello' }, (err, res) => {
32 | t.error(err)
33 | t.equal(res, 'hello!')
34 | client.close(t.error)
35 | server.close(t.error)
36 | })
37 | })
38 | })
39 |
40 | test('cli extended', t => {
41 | t.plan(4)
42 |
43 | cli.start({
44 | host: '127.0.0.1',
45 | port: port,
46 | _: ['./examples/cli-extended.js']
47 | }, server => {
48 | const client = Client({ port })
49 |
50 | client.invoke({ procedure: 'hello' }, (err, res) => {
51 | t.error(err)
52 | t.deepEqual(res, { hello: 'world' })
53 | client.close(t.error)
54 | server.close(t.error)
55 | })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/test/custom-parser-serializer.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Server = require('../lib/server')
7 | const Client = require('../lib/client')
8 | const msgpack = require('msgpack5')()
9 | const getPort = require('./get-port')
10 |
11 | var port = 0
12 |
13 | beforeEach(done => {
14 | getPort((err, p) => {
15 | if (err) throw err
16 | port = p
17 | done()
18 | })
19 | })
20 |
21 | test('use msgpack5 as custom serializer/parser', t => {
22 | t.plan(6)
23 | const payload = { procedure: 'hello', hello: 'world' }
24 | const server = Server({
25 | codec: msgpack
26 | })
27 |
28 | server.register('hello', function (req, reply) {
29 | t.deepEqual(req, payload)
30 | reply(null, req)
31 | })
32 |
33 | server.listen(port, err => {
34 | t.error(err)
35 |
36 | const client = Client({
37 | port: port,
38 | codec: msgpack
39 | })
40 |
41 | client.invoke(payload, (err, res) => {
42 | t.error(err)
43 | t.deepEqual(res, payload)
44 | client.close(t.error)
45 | server.close(t.error)
46 | })
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/test/errors.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const test = t.test
5 | const Server = require('../lib/server')
6 |
7 | test('server assertions', t => {
8 | t.plan(3)
9 | const server = Server()
10 |
11 | try {
12 | server.register(null, () => {})
13 | t.fail()
14 | } catch (e) {
15 | t.is(e.message, 'procedure must be a string')
16 | }
17 |
18 | try {
19 | server.register('', null)
20 | t.fail()
21 | } catch (e) {
22 | t.is(e.message, 'fn must be a function')
23 | }
24 |
25 | try {
26 | server.listen(null)
27 | t.fail()
28 | } catch (e) {
29 | t.is(e.message, 'portOrPath should be a number or a string')
30 | }
31 | })
32 |
--------------------------------------------------------------------------------
/test/get-port.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const net = require('net')
4 |
5 | function getPort (cb) {
6 | const server = net.createServer()
7 |
8 | server.unref()
9 | server.on('error', err => {
10 | cb(err, null)
11 | })
12 |
13 | server.listen(0, () => {
14 | const port = server.address().port
15 | server.close(() => {
16 | cb(null, port)
17 | })
18 | })
19 | }
20 |
21 | module.exports = getPort
22 |
--------------------------------------------------------------------------------
/test/ipc.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const t = require('tap')
5 | const beforeEach = t.beforeEach
6 | const test = t.test
7 | const Server = require('../lib/server')
8 | const Client = require('../lib/client')
9 |
10 | const socket = '/tmp/ipc-test.sock'
11 |
12 | beforeEach(done => {
13 | fs.unlink(socket, () => done())
14 | })
15 |
16 | test('Should communicate via ipc', t => {
17 | t.plan(8)
18 | const server = Server()
19 |
20 | server.register('concat', (req, reply) => {
21 | t.equal(req.a, 'a')
22 | t.equal(req.b, 'b')
23 | t.is(typeof reply, 'function')
24 | reply(null, req.a + req.b)
25 | })
26 |
27 | server.listen(socket, err => {
28 | t.error(err)
29 |
30 | const client = Client({ path: socket })
31 |
32 | client.invoke({
33 | procedure: 'concat',
34 | a: 'a',
35 | b: 'b'
36 | }, (err, res) => {
37 | t.error(err)
38 | t.equal(res, 'ab')
39 | client.close(t.error)
40 | server.close(t.error)
41 | })
42 | })
43 | })
44 |
--------------------------------------------------------------------------------
/test/reconnect.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Server = require('../lib/server')
7 | const Client = require('../lib/client')
8 | const getPort = require('./get-port')
9 |
10 | var port = 0
11 |
12 | beforeEach(done => {
13 | getPort((err, p) => {
14 | if (err) throw err
15 | port = p
16 | done()
17 | })
18 | })
19 |
20 | test('the client should try to reconnect before emit an error', t => {
21 | t.plan(7)
22 |
23 | setTimeout(() => {
24 | const server = Server()
25 | server.register('concat', (req, reply) => {
26 | t.equal(req.a, 'a')
27 | t.equal(req.b, 'b')
28 | t.is(typeof reply, 'function')
29 | reply(null, req.a + req.b)
30 | })
31 |
32 | server.listen(port, t.error)
33 | server.on('connection', server.close)
34 | }, 1000)
35 |
36 | const client = Client({
37 | port: port,
38 | reconnect: {
39 | timeout: 1500
40 | }
41 | })
42 |
43 | client.on('error', () => {
44 | t.fail('should not error')
45 | })
46 |
47 | client.invoke({
48 | procedure: 'concat',
49 | a: 'a',
50 | b: 'b'
51 | }, (err, res) => {
52 | t.error(err)
53 | t.equal(res, 'ab')
54 | client.close(t.error)
55 | })
56 | })
57 |
58 | test('client reconnect fail', t => {
59 | t.plan(1)
60 |
61 | const client = Client({
62 | port: port,
63 | reconnect: {
64 | timeout: 100
65 | }
66 | })
67 |
68 | client.on('error', () => {
69 | t.pass('should error')
70 | })
71 | })
72 |
73 | test('client without reconnect', t => {
74 | t.plan(1)
75 |
76 | const client = Client({
77 | port: port
78 | })
79 |
80 | client.on('error', () => {
81 | t.pass('should error')
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/test/use.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Server = require('../lib/server')
7 | const Client = require('../lib/client')
8 | const getPort = require('./get-port')
9 |
10 | var port = 0
11 |
12 | beforeEach(done => {
13 | getPort((err, p) => {
14 | if (err) throw err
15 | port = p
16 | done()
17 | })
18 | })
19 |
20 | test('use can add methods to the instance', t => {
21 | t.plan(7)
22 | const server = Server()
23 |
24 | server.use((instance, opts, next) => {
25 | t.ok(instance instanceof Server)
26 | instance.concat = (a, b) => a + b
27 | next()
28 | })
29 |
30 | server.register('concat', function (req, reply) {
31 | t.ok(this.concat)
32 | reply(null, this.concat(req.a, req.b))
33 | })
34 |
35 | server.listen(port, err => {
36 | t.error(err)
37 |
38 | const client = Client({ port })
39 |
40 | client.invoke({
41 | procedure: 'concat',
42 | a: 'a',
43 | b: 'b'
44 | }, (err, res) => {
45 | t.error(err)
46 | t.equal(res, 'ab')
47 | client.close(t.error)
48 | server.close(t.error)
49 | })
50 | })
51 | })
52 |
53 | test('close event', t => {
54 | t.plan(1)
55 | const server = Server()
56 |
57 | server
58 | .use((instance, opts, next) => {
59 | instance.onClose(i => {
60 | t.ok(i instanceof Server)
61 | })
62 | next()
63 | })
64 | .after(() => {
65 | server.close()
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/test/validation.test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const t = require('tap')
4 | const beforeEach = t.beforeEach
5 | const test = t.test
6 | const Server = require('../lib/server')
7 | const Client = require('../lib/client')
8 | const getPort = require('./get-port')
9 |
10 | var port = 0
11 |
12 | beforeEach(done => {
13 | getPort((err, p) => {
14 | if (err) throw err
15 | port = p
16 | done()
17 | })
18 | })
19 |
20 | test('should validate the request object - valid', t => {
21 | t.plan(8)
22 | const server = Server()
23 |
24 | server.register('concat', {
25 | type: 'object',
26 | properties: {
27 | a: { type: 'string' },
28 | b: { type: 'string' }
29 | },
30 | required: ['a', 'b']
31 | }, (req, reply) => {
32 | t.equal(req.a, 'a')
33 | t.equal(req.b, 'b')
34 | t.is(typeof reply, 'function')
35 | reply(null, req.a + req.b)
36 | })
37 |
38 | server.listen(port, err => {
39 | t.error(err)
40 |
41 | const client = Client({ port })
42 |
43 | client.invoke({
44 | procedure: 'concat',
45 | a: 'a',
46 | b: 'b'
47 | }, (err, res) => {
48 | t.error(err)
49 | t.equal(res, 'ab')
50 | client.close(t.error)
51 | server.close(t.error)
52 | })
53 | })
54 | })
55 |
56 | test('should validate the request object (without required) - valid', t => {
57 | t.plan(8)
58 | const server = Server()
59 |
60 | server.register('concat', {
61 | type: 'object',
62 | properties: {
63 | a: { type: 'string' },
64 | b: { type: 'string' }
65 | }
66 | }, (req, reply) => {
67 | t.equal(req.a, 'a')
68 | t.equal(req.b, 'b')
69 | t.is(typeof reply, 'function')
70 | reply(null, req.a + req.b)
71 | })
72 |
73 | server.listen(port, err => {
74 | t.error(err)
75 |
76 | const client = Client({ port })
77 |
78 | client.invoke({
79 | procedure: 'concat',
80 | a: 'a',
81 | b: 'b'
82 | }, (err, res) => {
83 | t.error(err)
84 | t.equal(res, 'ab')
85 | client.close(t.error)
86 | server.close(t.error)
87 | })
88 | })
89 | })
90 |
91 | test('should validate the request object - not valid', t => {
92 | t.plan(5)
93 | const server = Server()
94 |
95 | server.register('concat', {
96 | type: 'object',
97 | properties: {
98 | a: { type: 'string' },
99 | b: { type: 'string' }
100 | },
101 | required: ['a', 'b']
102 | }, (req, reply) => {
103 | t.fail('this should not be called')
104 | })
105 |
106 | server.listen(port, err => {
107 | t.error(err)
108 |
109 | const client = Client({ port })
110 |
111 | client.invoke({
112 | procedure: 'concat',
113 | a: 'a'
114 | }, (err, res) => {
115 | t.equal(err.message, '400')
116 | t.is(typeof res, 'object')
117 | client.close(t.error)
118 | server.close(t.error)
119 | })
120 | })
121 | })
122 |
--------------------------------------------------------------------------------