├── test ├── certs │ ├── ca-crt.srl │ ├── client1-csr.pem │ ├── server-csr.pem │ ├── client1-crt.pem │ ├── server-crt.pem │ ├── ca-crt.pem │ ├── ca-key.pem │ ├── client1-key.pem │ └── server-key.pem ├── example-amazon-dynamo-sosp2007.pdf ├── before-after.js ├── helper.js ├── peer-rpc-server.js ├── rpc-server-readstream-size.js ├── rpc-server-manual-request-handling.js ├── peer-rpc-server-secure.js ├── peer-rpc-server-streaming.js ├── peer-rpc-server-secure-streaming.js ├── peer-rpc-server-buffered.js └── peer-rpc-server-secure-buffered.js ├── logo.png ├── .gitignore ├── .travis.yml ├── index.js ├── lib ├── PeerRPCServer.js ├── PeerRPCClient.js ├── TransportRPCServer.js └── TransportRPCClient.js ├── examples ├── put_get.js ├── rpc_client_map.js ├── rpc_client_stream.js ├── rpc_client.js ├── rpc_server.js ├── rpc_server_stream.js └── ssl │ ├── rpc_client_map.js │ └── rpc_server.js ├── package.json ├── README.md └── LICENSE /test/certs/ca-crt.srl: -------------------------------------------------------------------------------- 1 | 3E6906002D94EF8DB3C52989D25AFF8896A12DDA 2 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/grenache-nodejs-http/HEAD/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | *.swo 4 | .DS_Store 5 | npm-debug.log 6 | test.pdf 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - "6" 6 | 7 | install: 8 | - npm install 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports.PeerRPCClient = require('./lib/PeerRPCClient') 2 | module.exports.PeerRPCServer = require('./lib/PeerRPCServer') 3 | -------------------------------------------------------------------------------- /test/example-amazon-dynamo-sosp2007.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/grenache-nodejs-http/HEAD/test/example-amazon-dynamo-sosp2007.pdf -------------------------------------------------------------------------------- /lib/PeerRPCServer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Base = require('grenache-nodejs-base') 4 | const TransportRPCServer = require('./TransportRPCServer') 5 | 6 | class PeerRPCServer extends Base.PeerRPCServer { 7 | getTransportClass () { 8 | return TransportRPCServer 9 | } 10 | } 11 | 12 | module.exports = PeerRPCServer 13 | -------------------------------------------------------------------------------- /examples/put_get.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Link = require('grenache-nodejs-link') 4 | 5 | const link = new Link({ 6 | grape: 'http://127.0.0.1:30001' 7 | }) 8 | link.start() 9 | 10 | setInterval(() => { 11 | link.put({ v: 'hello world' }, (err, hash) => { 12 | console.log('data saved to the DHT', err, hash) 13 | if (hash) { 14 | link.get(hash, (err, res) => { 15 | console.log('data requested to the DHT', err, res) 16 | }) 17 | } 18 | }) 19 | }, 2000) 20 | -------------------------------------------------------------------------------- /examples/rpc_client_map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Grenache = require('./../') 4 | const Link = require('grenache-nodejs-link') 5 | const Peer = Grenache.PeerRPCClient 6 | 7 | const link = new Link({ 8 | grape: 'http://127.0.0.1:30001' 9 | }) 10 | link.start() 11 | 12 | const peer = new Peer(link, {}) 13 | peer.init() 14 | 15 | const reqs = 10 16 | 17 | setTimeout(() => { 18 | for (let i = 0; i < reqs; i++) { 19 | peer.map('rpc_test', 'hello', { timeout: 10000 }, (err, data) => { 20 | console.log(err, data) 21 | }) 22 | } 23 | }, 2000) 24 | -------------------------------------------------------------------------------- /lib/PeerRPCClient.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Base = require('grenache-nodejs-base') 4 | const TransportRPCClient = require('./TransportRPCClient') 5 | 6 | class PeerRPCClient extends Base.PeerRPCClient { 7 | getTransportClass () { 8 | return TransportRPCClient 9 | } 10 | 11 | getRequestOpts (opts) { 12 | const res = { 13 | timeout: opts.timeout, 14 | compress: opts.compress 15 | } 16 | 17 | if (opts.headers) { 18 | res.headers = opts.headers 19 | } 20 | 21 | return res 22 | } 23 | } 24 | 25 | module.exports = PeerRPCClient 26 | -------------------------------------------------------------------------------- /test/before-after.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | 'use strict' 4 | 5 | const { startGrapes, stopGrapes } = require('./helper') 6 | 7 | let grapes 8 | function setupHooks (cb) { 9 | beforeEach(async function () { 10 | this.timeout(20000) 11 | 12 | grapes = await startGrapes() 13 | cb() 14 | 15 | await new Promise((resolve) => { 16 | grapes[0].once('announce', () => resolve()) 17 | }) 18 | }) 19 | 20 | afterEach(async function () { 21 | this.timeout(5000) 22 | await stopGrapes(grapes) 23 | }) 24 | 25 | return grapes 26 | } 27 | 28 | module.exports = setupHooks 29 | -------------------------------------------------------------------------------- /examples/rpc_client_stream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Grenache = require('./../') 4 | const Link = require('grenache-nodejs-link') 5 | const Peer = Grenache.PeerRPCClient 6 | const { PassThrough } = require('stream') 7 | 8 | const link = new Link({ 9 | grape: 'http://127.0.0.1:30001' 10 | }) 11 | link.start() 12 | 13 | const peer = new Peer(link, {}) 14 | peer.init() 15 | 16 | const req = peer.stream('rpc_stream', { 17 | headers: { 18 | _a: 'uploadNewPublicStream', 19 | _ar: { foo: 'bar' }, 20 | 'content-type': 'application/pdf' 21 | }, 22 | timeout: 10000 23 | }) 24 | 25 | const writable = new PassThrough() 26 | writable.write('hello') 27 | writable.pipe(req) 28 | req.on('data', (d) => { 29 | console.log('response', d.toString()) 30 | }) 31 | -------------------------------------------------------------------------------- /examples/rpc_client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Grenache = require('./../') 4 | const Link = require('grenache-nodejs-link') 5 | const Peer = Grenache.PeerRPCClient 6 | 7 | const link = new Link({ 8 | grape: 'http://127.0.0.1:30001' 9 | }) 10 | link.start() 11 | 12 | const peer = new Peer(link, {}) 13 | peer.init() 14 | 15 | const reqs = 1000 16 | let reps = 0 17 | 18 | const d1 = new Date() 19 | for (let i = 0; i < reqs; i++) { 20 | peer.request('rpc_test', 'hello', { timeout: 10000, compress: true }, (err, data) => { 21 | if (err) { 22 | console.error(err) 23 | process.exit(-1) 24 | } 25 | console.log(err, data) 26 | if (++reps === reqs) { 27 | const d2 = new Date() 28 | console.log(d2 - d1) 29 | } 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /examples/rpc_server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Grenache = require('./../') 4 | const Link = require('grenache-nodejs-link') 5 | const Peer = Grenache.PeerRPCServer 6 | 7 | const link = new Link({ 8 | grape: 'http://127.0.0.1:30001' 9 | }) 10 | link.start() 11 | 12 | const peer = new Peer(link, { 13 | timeout: 300000 14 | }) 15 | peer.init() 16 | 17 | const service = peer.transport('server') 18 | service.listen(Math.floor(Math.random() * 1001) + 1024) 19 | 20 | setInterval(function () { 21 | link.announce('rpc_test', service.port, {}) 22 | }, 1000) 23 | 24 | service.on('request', (rid, key, payload, handler) => { 25 | // console.log('peer', rid, key, payload) 26 | handler.reply(null, 'world') 27 | // handler.reply(new Error('something went wrong'), 'world') 28 | }) 29 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Grape } = require('grenache-grape') 4 | const async = require('async') 5 | 6 | const startGrapes = async () => { 7 | const grapes = [ 8 | new Grape({ 9 | dht_port: 20002, 10 | dht_bootstrap: ['127.0.0.1:20001'], 11 | api_port: 40001 12 | }), 13 | new Grape({ 14 | dht_port: 20001, 15 | dht_bootstrap: ['127.0.0.1:20002'], 16 | api_port: 30001 17 | }) 18 | ] 19 | 20 | await async.each(grapes, (grape, next) => { 21 | grape.start() 22 | grape.once('ready', () => next()) 23 | }) 24 | return grapes 25 | } 26 | 27 | const stopGrapes = async (grapes) => { 28 | await async.each(grapes, (grape, next) => { 29 | grape.stop(next) 30 | }) 31 | } 32 | 33 | module.exports = { 34 | startGrapes, 35 | stopGrapes 36 | } 37 | -------------------------------------------------------------------------------- /examples/rpc_server_stream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Grenache = require('./../') 4 | const Link = require('grenache-nodejs-link') 5 | const Peer = Grenache.PeerRPCServer 6 | 7 | const link = new Link({ 8 | grape: 'http://127.0.0.1:30001' 9 | }) 10 | link.start() 11 | 12 | const peer = new Peer(link, { 13 | timeout: 300000, 14 | disableBuffered: true 15 | }) 16 | peer.init() 17 | 18 | const service = peer.transport('server') 19 | service.listen(Math.floor(Math.random() * 1001) + 1024) 20 | 21 | setInterval(function () { 22 | link.announce('rpc_stream', service.port, {}) 23 | }, 1000) 24 | 25 | service.on('stream', (req, res, meta, handler) => { 26 | console.log(meta) // meta.isStream === true 27 | 28 | const [rid] = meta.infoHeaders 29 | 30 | req.pipe(process.stdout) 31 | 32 | handler.reply(rid, null, 'world') // convenience reply 33 | }) 34 | -------------------------------------------------------------------------------- /test/certs/client1-csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICVzCCAT8CAQAwEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEB 3 | BQADggEPADCCAQoCggEBAKiXZsaFLcb4eubwGXELePbFL//uR8W7OD0NSaKUbe6G 4 | 1leLCqWt3ljSp8BFTr8B8R/2l3i+VuedIIuzX2wJbBsTrdSEIcOfQauRnkF6RqoW 5 | bvToo7fYWpheZGiHfyVmJlr6XQbVNyzOnutEGPdpe6bJMwFdG/UCAaZeAsvAcAGx 6 | VGhAp8cjSnwht/8QZJ8/FLcHBVwc0Zd5ELmtPeaMS7GDBiUvdDYRQBRTY8ls9wPv 7 | bAuBzo0f/ude72iHwkJuDOnTVtffXuFXhloBAVnjaXK0b7PtJ8ByGZ/mZEp1a7Py 8 | sehy4w6cL3qg9cq306m/jxXrmwlVM/QJd2yhVOQnSPkCAwEAAaAAMA0GCSqGSIb3 9 | DQEBCwUAA4IBAQA6W0JeJs4HHoNzmympMhVn131SQE0wDVMUHrAPQvf5xPjnW/sh 10 | V5h+gs0yHNUX0ywEn8+eDVmHsskhcYchp/UW4XjBxTFwIx0r7jqFsVYSNjSjteCi 11 | vhlEuLOWdv4k8pLavr0SnClgDQVV/uCdhxLY0rQJeg1Bfq1CX5YIrrv08SMfYJOz 12 | xopiohYOt9EqoGuVkVm6wTxso2B4WmT9NuU5PkxulN5do+s/aVC9dZD7dpBgAUFv 13 | iXOPYmk22cwLArOnpy0FpNEnYga9EHGwb8CUFfa17kPxTMEGyFVHfeuyXF56ubo9 14 | ln+J+KGh3oWrpUSrPYax7qA/1qnKv3+6y4j2 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /test/certs/server-csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICWTCCAUECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B 3 | AQEFAAOCAQ8AMIIBCgKCAQEAwAHQjt8BD+n31SVmP7X+aNHu96KAAvzPM1kiFVHp 4 | lVXrEJYau+8yOv0eMfKnQ/Yt28jaYGFlVyRS81zpQ2P+IsdlqqOPyiwwDPPNL8Ia 5 | 7X8kPgIaXRhEOKtSjajpkvIjHqRX2vH3wj2T1XPRsQ9HExx2Hw3NFrOdTSMs61Aw 6 | GH66fcWSvNAxCtG8Ij1MEWdK5tS36q59fPHkRMpgOx0cbr5QY2U+giKi/25y+A02 7 | S0CeLE4mP5mgxQ7Rf2oaBiiaz22xCwcmARTkOuOsg5tk8/yGWoNHlAW130n7qp/t 8 | o2UT9/7269H4LdCtQYPLqjXcFKF3dD+VyxVdc2mJzleUKQIDAQABoAAwDQYJKoZI 9 | hvcNAQELBQADggEBAFfKmC+7cPTYPzA9NU5eOLJ/grmzLiqJkbrFxcWk3YVdX5wX 10 | uL3gQ6JCflhZCxMo593bOy+CzqbehI95Ogk7uQlCjj5yN6oIw35OWoWZ+yG1KANs 11 | Tas6USWJ6wZa3FQiGZTG3LMTo2MXR1o3wfbGDUmzhtH/rLFi/puI+00TEm5RZrxF 12 | ZjFvcA59NiZV5N3sAiI4RMHY1OoSEW6uaFHdIDpc6m6I0lLPNh9u1AFPxnwshAIG 13 | SLUnoMVLJgxbGwezq+2pMixYaDmyVtUjPQB5keZmdIBsQpFPx0ZNZJWj2vjGiSwL 14 | AFchAHWw3AobCHDUtApnlOfSVt+8c4P9NqMLIjc= 15 | -----END CERTIFICATE REQUEST----- 16 | -------------------------------------------------------------------------------- /test/certs/client1-crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrDCCAZQCFD5pBgAtlO+Ns8UpidJa/4iWoS3aMA0GCSqGSIb3DQEBCwUAMBMx 3 | ETAPBgNVBAMMCE15VGVzdENBMB4XDTI1MDMyODE2MzgwN1oXDTM1MDMyNjE2Mzgw 4 | N1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBAKiXZsaFLcb4eubwGXELePbFL//uR8W7OD0NSaKUbe6G1leLCqWt3ljS 6 | p8BFTr8B8R/2l3i+VuedIIuzX2wJbBsTrdSEIcOfQauRnkF6RqoWbvToo7fYWphe 7 | ZGiHfyVmJlr6XQbVNyzOnutEGPdpe6bJMwFdG/UCAaZeAsvAcAGxVGhAp8cjSnwh 8 | t/8QZJ8/FLcHBVwc0Zd5ELmtPeaMS7GDBiUvdDYRQBRTY8ls9wPvbAuBzo0f/ude 9 | 72iHwkJuDOnTVtffXuFXhloBAVnjaXK0b7PtJ8ByGZ/mZEp1a7Pysehy4w6cL3qg 10 | 9cq306m/jxXrmwlVM/QJd2yhVOQnSPkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA 11 | HM7J+3coTFZYGwYmfG8aObCjN/3gW56VG+5SOHT9NmdfIuzvWrPBIsYE+CYj1nrK 12 | IFm7TP89R2Ka0U20V09+26C0L3SyE6DUTipOc0zhGQgdLTU1bneIZGg26LtYbKJc 13 | PV0+Bt1JPT4PulIcuq4XDRFBKaoXC0lmIo1NbfWZbunz1AaBjiJWKS2YksgJJ2Ad 14 | GhL8/PJO7Ro+8xxunVikcl1MWyeYiKyKFk1J6j8bQPJ940poqSwMObXFKlUMKsm4 15 | /e6JwqiCFv+MMPL80puUv/Fue6wmjY3rpygwAU60dBPmjLpWNBbuSb/+wjPWYtgy 16 | f9m+nQEyfflc7t6anZjPMg== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /test/certs/server-crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrjCCAZYCFD5pBgAtlO+Ns8UpidJa/4iWoS3ZMA0GCSqGSIb3DQEBCwUAMBMx 3 | ETAPBgNVBAMMCE15VGVzdENBMB4XDTI1MDMyODE2MzczMloXDTM1MDMyNjE2Mzcz 4 | MlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 5 | MIIBCgKCAQEAwAHQjt8BD+n31SVmP7X+aNHu96KAAvzPM1kiFVHplVXrEJYau+8y 6 | Ov0eMfKnQ/Yt28jaYGFlVyRS81zpQ2P+IsdlqqOPyiwwDPPNL8Ia7X8kPgIaXRhE 7 | OKtSjajpkvIjHqRX2vH3wj2T1XPRsQ9HExx2Hw3NFrOdTSMs61AwGH66fcWSvNAx 8 | CtG8Ij1MEWdK5tS36q59fPHkRMpgOx0cbr5QY2U+giKi/25y+A02S0CeLE4mP5mg 9 | xQ7Rf2oaBiiaz22xCwcmARTkOuOsg5tk8/yGWoNHlAW130n7qp/to2UT9/7269H4 10 | LdCtQYPLqjXcFKF3dD+VyxVdc2mJzleUKQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 11 | AQCi/mDFU0OgxI8q1aSGR/yfS6hBhsMVl8CJ4fvSFgNU8p6hHDaBbfc0D6DR8bym 12 | mcpZ08QXEEhJbMlxACmpVE5EqR6usbSJF7ItvlM71YzxK5CAS66yAkdrhIyEStRe 13 | 1L8uRicdsIoEvjDbe80p4p76Aoe+Iz0I8V+v8aXzHII9om7zRp8OBBOIP5cGMmpt 14 | GIifsdqPZwAKnr83l/ZgyXtUAFdaTS4raf0yh+lU86I5kTJeur7MGyKFvvwCNI2B 15 | V3Cp2H8dxgXXL0R4yCUMmxTyqMktLsXQjfMC8OnVa3oetrFS+dilS7JI+WCRZ5YH 16 | iOS8YRpx9xu4Yzy/apunvaK0 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grenache-nodejs-http", 3 | "version": "1.0.1", 4 | "private": false, 5 | "description": "Granache Node.js HTTP implementation", 6 | "author": "prdn (https://bitfinex.com/)", 7 | "keywords": [ 8 | "grenache", 9 | "kademlia", 10 | "nodejs", 11 | "micro-services" 12 | ], 13 | "engines": { 14 | "node": ">=16.0" 15 | }, 16 | "dependencies": { 17 | "async": "3.2.6", 18 | "duplexify": "4.1.3", 19 | "grenache-nodejs-base": "1.0.0", 20 | "grenache-nodejs-link": "1.0.2", 21 | "node-fetch": "2.7.0", 22 | "pump": "3.0.2" 23 | }, 24 | "main": "index.js", 25 | "devDependencies": { 26 | "grenache-grape": "1.0.0", 27 | "mocha": "11.1.0", 28 | "standard": "17.1.2" 29 | }, 30 | "scripts": { 31 | "test": "npm run lint && npm run unit", 32 | "unit": "mocha", 33 | "lint": "standard", 34 | "lint:fix": "standard --fix" 35 | }, 36 | "license": "Apache-2.0", 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/bitfinexcom/grenache-nodejs-http.git" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/ssl/rpc_client_map.js: -------------------------------------------------------------------------------- 1 | // make sure you start 2 grapes 2 | // grape --dp 20001 --aph 30001 --bn '127.0.0.1:20002' 3 | // grape --dp 20002 --aph 40001 --bn '127.0.0.1:20001' 4 | 5 | 'use strict' 6 | 7 | const Link = require('grenache-nodejs-link') 8 | const Peer = require('../../').PeerRPCClient 9 | const fs = require('fs') 10 | const path = require('path') 11 | 12 | const link = new Link({ 13 | grape: 'http://127.0.0.1:30001' 14 | }) 15 | 16 | link.start() 17 | 18 | const secure = { 19 | key: fs.readFileSync(path.join(__dirname, '../../test/certs/client1-key.pem')), 20 | cert: fs.readFileSync(path.join(__dirname, '../../test/certs/client1-crt.pem')), 21 | ca: fs.readFileSync(path.join(__dirname, '../../test/certs/ca-crt.pem')), 22 | rejectUnauthorized: false // take care, can be dangerous in production! 23 | } 24 | 25 | const peer = new Peer( 26 | link, 27 | { secure } 28 | ) 29 | 30 | peer.init() 31 | 32 | const reqs = 10 33 | 34 | setTimeout(() => { 35 | for (let i = 0; i < reqs; i++) { 36 | peer.map('rpc_test', 'hello', { timeout: 10000 }, (err, data) => { 37 | console.log(err, data) 38 | }) 39 | } 40 | }, 2000) 41 | -------------------------------------------------------------------------------- /test/certs/ca-crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDBzCCAe+gAwIBAgIUGtaHkIywQCUpLJYn4KZRZUkZcYMwDQYJKoZIhvcNAQEL 3 | BQAwEzERMA8GA1UEAwwITXlUZXN0Q0EwHhcNMjUwMzI4MTYzNjQwWhcNMzUwMzI2 4 | MTYzNjQwWjATMREwDwYDVQQDDAhNeVRlc3RDQTCCASIwDQYJKoZIhvcNAQEBBQAD 5 | ggEPADCCAQoCggEBALiTm+dXfL5/TfO3cLRqcY6p5hItEWDYuCaFEEsvq74ko6B1 6 | 5cNCSxYzM34mGFHRItq/UZUb2WLgbsJGBWhPgH+pXHfLlS+s3xYBbQMaz5BEgaZO 7 | Wq/RHQHvWTv/nL/CJMjUYwUfcW1smwXg+WPsLD+LSJTCbgPnLQBp6JMrlCSo6pGd 8 | 77qqmcPQRut7Q2/5ogZ+/b1darZa1cZcHVluq67TYgw7844V1T9Ei0+ygSzZBsrG 9 | 4/nYMWlbVHxbn8bxcCPmIrT9YgwuFaq8JZ0OvR1KROJ6WV1A7EXK5XT3ojQkGoFD 10 | oHxD/H2XmOLkk3V8WqLsmh/JFrhv/tbcjyAyFG0CAwEAAaNTMFEwHQYDVR0OBBYE 11 | FEdhwkpjY0OljDiQyULJ/rqVKlwrMB8GA1UdIwQYMBaAFEdhwkpjY0OljDiQyULJ 12 | /rqVKlwrMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAIdYQQ1 13 | 9ZwBgtrAoGjuk0VS52cmKU2k4Ok1/FngjD4FCCt83WbOya+QOkjH7KyO89UYERio 14 | rA8AXkPycnSRakoPsznUzU7a0neaAfDvrEdeHd9RSc06Rijvp3EjyWe7+H0pqZUi 15 | v1jP5N8cEWmX+camz9Wz34TNCw/JspA6jIrXB5zLXxd5QDmHm1bRsWfbKI83g3jx 16 | EWlNLqDhnbNzbOXV/oxnexS2xNna4XgHQTujZIrYpEdodpyhDhnQT3XTevgWB2XW 17 | 7TwZThq0vHFJx6VkVFdf7WrsWOTHzXSIhYKl2SNifyyNyv7rv/Vg9cpbXad0Jom7 18 | opXbblgZgnTHIuc= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /examples/ssl/rpc_server.js: -------------------------------------------------------------------------------- 1 | // make sure you start 2 grapes 2 | // grape --dp 20001 --aph 30001 --bn '127.0.0.1:20002' 3 | // grape --dp 20002 --aph 40001 --bn '127.0.0.1:20001' 4 | 5 | 'use strict' 6 | 7 | const Link = require('grenache-nodejs-link') 8 | const Peer = require('../../').PeerRPCServer 9 | const fs = require('fs') 10 | const path = require('path') 11 | 12 | const link = new Link({ 13 | grape: 'http://127.0.0.1:30001' 14 | }) 15 | link.start() 16 | 17 | const opts = { 18 | secure: { 19 | key: fs.readFileSync(path.join(__dirname, '../../test/certs/server-key.pem')), 20 | cert: fs.readFileSync(path.join(__dirname, '../../test/certs/server-crt.pem')), 21 | ca: fs.readFileSync(path.join(__dirname, '../../test/certs/ca-crt.pem')), 22 | requestCert: true, 23 | rejectUnauthorized: false // take care, can be dangerous in production! 24 | } 25 | } 26 | 27 | const peer = new Peer( 28 | link, 29 | opts 30 | ) 31 | peer.init() 32 | 33 | const service = peer.transport('server') 34 | service.listen(Math.floor(Math.random() * 1001) + 1024) 35 | 36 | setInterval(function () { 37 | link.announce('rpc_test', service.port, {}) 38 | }, 1000) 39 | 40 | service.on('request', function (rid, key, payload, handler, cert) { 41 | console.log(cert.fingerprint) 42 | handler.reply(null, 'world') 43 | }) 44 | -------------------------------------------------------------------------------- /test/certs/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4k5vnV3y+f03z 3 | t3C0anGOqeYSLRFg2LgmhRBLL6u+JKOgdeXDQksWMzN+JhhR0SLav1GVG9li4G7C 4 | RgVoT4B/qVx3y5UvrN8WAW0DGs+QRIGmTlqv0R0B71k7/5y/wiTI1GMFH3FtbJsF 5 | 4Plj7Cw/i0iUwm4D5y0AaeiTK5QkqOqRne+6qpnD0Ebre0Nv+aIGfv29XWq2WtXG 6 | XB1Zbquu02IMO/OOFdU/RItPsoEs2QbKxuP52DFpW1R8W5/G8XAj5iK0/WIMLhWq 7 | vCWdDr0dSkTielldQOxFyuV096I0JBqBQ6B8Q/x9l5ji5JN1fFqi7JofyRa4b/7W 8 | 3I8gMhRtAgMBAAECggEARy8OUmMoReOc8aBOhBYFI4scoufkquAJ7TkmNoj1CEjj 9 | HhA6A1r+0FoAwub4PB2W/pwIX6Q063A45w3QKhF3MkTj14OZTqCyDK+SFj9xy2bQ 10 | RY40ZVgVgtiHJp+HGWFLhHR8l84vlY7sHpFMPSApVHxoCo8NlM3ESkOOIawN3zDp 11 | ic3vVN/b0fI9lEvcqjLLtVNDWFkhqb8OODsXgIa+29P+F+p4zIxvT0FsGN/TwYks 12 | 74djp949oJz2TEi4mkrqm8eZHsRpTgTdPOiYqRfIQ943gKouUSjm/g8newVEe51p 13 | PBMth1gn/K+PgHAcfWaGZBQEcwBreFhztOH1NPfE1QKBgQDvem2AJOCqOGCNcvx7 14 | tMqaylMNPlw1cPSanIq6oPrY+1YFejVVN66LYNcHrsUpWxdB9I9DuoNvRlNjDqzJ 15 | fWUkId2XsRm2YiEX3hBlKTmgywy1KPna7g1y12fkP7qKQQeuJ6qYuDCyWYuYzl9K 16 | Z++kQQk6t1kT/kQqlHjOudNi1wKBgQDFT4ecehhnq/LVbiZEUoKe1A9pGeLfc+w7 17 | jjL8xSGDpt74uUdNc07etijElIpmkBqnD84dV8rsbNeZqWk5dMWGQBloe69TRyId 18 | 9cY+DNH8xjU4ysts42JEUTmdJVl1aET6yIrIjunhbt8O0ThZb5BXMBTvx9cLQgQ/ 19 | AEilhPVeWwKBgQDRVPw4woOcZ9GGc5febseXjPoSHoutr0IxkFr4DlWXrAOioBPU 20 | nOq3LPxp28y+fMCUx7kfo/7WGuwzL5W8Px9U+UFhIs7uDVMOMO+17dZRBoziIqJd 21 | TC93TnGjJE3kIjobctL90ivHCt0qwmKrgLUKqErwB2sXPfDKI6SCWA8+WwKBgDQH 22 | CYpAQ6kqlQikV+DSJPE+l6WuFr23MoZts5IlZPXzDX5mVJ/elOBzBgL37BqpSu3c 23 | ZAwmtzJqbNtu6XkmCmOhCVIcsxlxe1SNFy4LcV+G4EfOaZ+XwbSj+l4umA02ZmH+ 24 | eWHzFUbFVnnwvzwOB/CFlC+58UFdsgLuzzkd+xlbAoGADMTT/asr09ZKFP9lyB2e 25 | AS2K3FCA9eio/vkBr0/PnzIv/bsjLaZ8GvmnlbLKf/gagPaKKGU8dbqyzQr8m2Tv 26 | ZcHMKN0GgxsV7B9c+HwoMR55hy8rj6zkeUenMyp4U5CuDtH3JEkrM7++ykvS9cyZ 27 | +Zr4qWiMGxHN9vDpI2AMePk= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/client1-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCol2bGhS3G+Hrm 3 | 8BlxC3j2xS//7kfFuzg9DUmilG3uhtZXiwqlrd5Y0qfARU6/AfEf9pd4vlbnnSCL 4 | s19sCWwbE63UhCHDn0GrkZ5BekaqFm706KO32FqYXmRoh38lZiZa+l0G1Tcszp7r 5 | RBj3aXumyTMBXRv1AgGmXgLLwHABsVRoQKfHI0p8Ibf/EGSfPxS3BwVcHNGXeRC5 6 | rT3mjEuxgwYlL3Q2EUAUU2PJbPcD72wLgc6NH/7nXu9oh8JCbgzp01bX317hV4Za 7 | AQFZ42lytG+z7SfAchmf5mRKdWuz8rHocuMOnC96oPXKt9Opv48V65sJVTP0CXds 8 | oVTkJ0j5AgMBAAECggEAUF0bWLZTQ+1eaIc7GW/mkXsdjln/THoaLMAQwbcPUAxm 9 | UASvZpqiyqRf4n8nMlWyoxHg0mWo928m/HDco5s6NQ4EZrE1hxJ7M1WOopeu55UZ 10 | /xSGNjSjus4D/u/X4/P1hJijj4ZDbtBvYKZtYEKf9rkh3xyLHxd9wdg+bPGmcqdW 11 | TYb0NsWGJu1uPgveUZTc/GSqzblNvOBommoDh0PNWOGcqbKUkTLcqEEJZ2Ki1Z52 12 | e1H6ZSx4sqIO0ztcjTlzFK5sf+2tjrlfKDUt2bCvZLlsWebJgYxr7B0nnpLHAuvb 13 | NLj2SdFR4Ox4S1NwEB/y8/dAm3tBcelpjajbqUiPoQKBgQDin9owiH81biVElwYt 14 | xlRuUagR05KCZJBp12AtNcTo9HVoL+/xufnR9KQ1a7UEleZQYhRrZ73QaMydo9FZ 15 | fytH2Gm5eeGWBjIVvhdFvmE7Ff740K7tKUUCNLbsHhIviZAi1bbTefPPp35vY9z5 16 | 4fNIW/z/m3cdGUczJgvvbBAOZwKBgQC+cdJsgxF03Mn+wIZHIFj+fof/HgrFzmzV 17 | 3X5yS14hAxOaU0NTg9T5FDsQ7XOvGcjDV4r44pcagcLimyGphzodIFjjVHe9vaeF 18 | tJer4g9Q/1lZGPldufA1s533yEMyjCwpdILvobTuU8VskvGzyzTyD2Z5L59PlaNt 19 | MN1NMgqRnwKBgQDRDfUQ4Fm4yCrI4yhoAZTRT7Ji/3Efp/UwLeYizn8fBqNnwRDv 20 | REgYTj1MqlcKK92SOWfszH5lSse2g/ATRyR92j1tQ+m9o2mAdSKyy7SV9OyISrmT 21 | PYbTSh2UIwlToOq9t7g3zUAERtZzmwO136G1FmaExL1UsoTjwi0wZ9dH8QKBgHJ8 22 | rok5Sfc8+9nAF3kOwzFLE6qUftlnPRpcazV+hXnHBbPStjiwmgma1d9ZQCBVmdGF 23 | ATCZ10jCFUxxAg2OE6uK1KTlFI7mQp0ocb2MyrRgrW/YiaCEtRkzf5WDuJHcnnfV 24 | aRlx1vknNxoMRG1Xjd6uz2Wu8VDAnlx38bIEkUH1AoGBAI5hPvpDYX/KbCiXRWIv 25 | bjJ1oz2tBMg+dsGr+IM2VBcsyOyioDDPGp4s6jeYdsq5MUq3fhZnJUXcVDwOvD/i 26 | F3ulQhsCBJa7Y1scc015Vm6MpbkDgKCIzWmFPb5tVDtQLdtZqevcChHrUPHAo1tT 27 | nZ+d7Eok1Kz1TUI5gg6nbteD 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/certs/server-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAAdCO3wEP6ffV 3 | JWY/tf5o0e73ooAC/M8zWSIVUemVVesQlhq77zI6/R4x8qdD9i3byNpgYWVXJFLz 4 | XOlDY/4ix2Wqo4/KLDAM880vwhrtfyQ+AhpdGEQ4q1KNqOmS8iMepFfa8ffCPZPV 5 | c9GxD0cTHHYfDc0Ws51NIyzrUDAYfrp9xZK80DEK0bwiPUwRZ0rm1Lfqrn188eRE 6 | ymA7HRxuvlBjZT6CIqL/bnL4DTZLQJ4sTiY/maDFDtF/ahoGKJrPbbELByYBFOQ6 7 | 46yDm2Tz/IZag0eUBbXfSfuqn+2jZRP3/vbr0fgt0K1Bg8uqNdwUoXd0P5XLFV1z 8 | aYnOV5QpAgMBAAECggEAXMzDuznHLqTbVpZT2/kfNBQ7Fk7g5hDAKEYtNfa4GahE 9 | C2kxFKIgOeh6fpOUCkHimjZybWDFdR+mQ98lEqAyhSawWY/iAEnpE2mGQ03ia+Um 10 | ZB3qFhKTbnxHUxZPhekn1jiCCd0m7SeL2v+2WohSxsbbxFiMRiubnFui6zx/cmZ+ 11 | Mb78bFY4c6BPsEbXlS6l62U9oiBiV28N2snhT5F79RbN9kp9fDUGotbKPZ2epEm4 12 | tHby4e/MxUsTdZpPA8PX05mVjSEK9JHZjfhhB44hTJpHTJyGenqm2n8CDnpOejh/ 13 | KnVB/b6mAfKyCdu5y1SJwZOj20OiUVbG0VnKjwr+LQKBgQD3LMs6+Vh+S7efIsJd 14 | 6Y3yIo/pF46iYvhlcpQotjIVxuYRdiYuXSFpTxomiieBcLkDXBeNWTkKpFVK/SAS 15 | N3LJXEKl4+ZquyR6sacEkTw0VPuHgCx7lO+8vZVP4q0SUMZV+dtCzjidQ9+JAVeh 16 | UKaYQfoVkCpFhNd1c271HeSWEwKBgQDG3MfDhwZIuAg3jQqr2ZzRtvbhVnltAkAf 17 | VSTIc5uflIfL3IEzWC861mibsfEE8JMfLA+VDcOLu8fdc1TEP6y2hEyAjjXEvY42 18 | xBEkYQncR1U2LPavGlqOF2LTzsRaT+/iFnJrYHkeIboWKLyDpfkZX/UxdK8WMExW 19 | 7v5S1GbkUwKBgQCf2Fn9Y271DeZLhWEI9pcTNYK3jMJzBWn5wSVLRrgRGTWDqVJ7 20 | vUJ0JfGVZtaxgMJFB/M7N64J0chO3G83GKCk04NOYJmMAEZRCj4mV/4FIcggEqWx 21 | rdlzx26d4MtoAtCgnRpMk6xNF62hnjqbWdrCsDgYZcjQeF4V2HazEYfX/QKBgBFw 22 | POhNh+SKltXgPWZSf+j4BXA/OAiyINNekQou8R+uU7Yx0PairgmX0baNhRgszIn4 23 | QCmO+m3feqhVu8I13zxmH5tKXTaydK0ixmoNRGMXskY87Sjvw68gJ99xC1DsH5oQ 24 | 49m8rQJSbNISom1c+ZobCxJaLBxjAd7BuHWlAJzXAoGAGrB6ZEjDH/mAjVpRNsZN 25 | 6P+3zst7r3PWIFBRkwAhfiS9IFtlCngIUzVJDjOAo/6yPvzK05Y0zq8xcJNVI6h/ 26 | AM42G3qhsevfEuR++blSF3i4uLOkHwkemcYjV8NcbgI3tgzn7sKBxN0l1hAVALVr 27 | 5GKcmOJY/PcTXiU8U9+sZrI= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /test/peer-rpc-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const { PeerRPCClient, PeerRPCServer } = require('../') 5 | const Link = require('grenache-nodejs-link') 6 | const setupHooks = require('./before-after.js') 7 | 8 | const PORT = 1337 9 | let link, peer, peerSrv, service, stop 10 | describe('RPC integration', () => { 11 | setupHooks((grapes) => { 12 | link = new Link({ 13 | grape: 'http://127.0.0.1:30001' 14 | }) 15 | link.start() 16 | 17 | peer = new PeerRPCClient(link, {}) 18 | peer.init() 19 | 20 | peerSrv = new PeerRPCServer(link, { timeout: 300000 }) 21 | 22 | peerSrv.init() 23 | service = peerSrv.transport('server') 24 | service.listen(PORT) 25 | 26 | link.announce('rpc_test', service.port, {}, (err, res) => { 27 | if (err) throw Error('error in announce, setup') 28 | }) 29 | 30 | stop = () => { 31 | peer.stop() 32 | link.stop() 33 | service.stop() 34 | } 35 | }) 36 | 37 | it('secure request, objects', (done) => { 38 | service.on('request', (rid, key, payload, handler) => { 39 | assert.ok(typeof rid === 'string') 40 | assert.strictEqual(key, 'rpc_test') 41 | assert.deepStrictEqual(payload, { hello: 'world' }) 42 | 43 | handler.reply(null, { hello: 'helloworld' }) 44 | }) 45 | 46 | const opts = { timeout: 100000 } 47 | peer.request('rpc_test', { hello: 'world' }, opts, (err, result) => { 48 | if (err) throw err 49 | 50 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 51 | 52 | stop() 53 | done() 54 | }) 55 | }).timeout(7000) 56 | 57 | it('secure map, strings', (done) => { 58 | service.on('request', (rid, key, payload, handler) => { 59 | assert.ok(typeof rid === 'string') 60 | assert.strictEqual(key, 'rpc_test') 61 | assert.strictEqual(payload, 'hello') 62 | 63 | handler.reply(null, 'world') 64 | }) 65 | 66 | const opts = { timeout: 100000 } 67 | peer.map('rpc_test', 'hello', opts, (err, result) => { 68 | if (err) throw err 69 | 70 | assert.deepStrictEqual(result, ['world']) 71 | 72 | stop() 73 | done() 74 | }) 75 | }).timeout(7000) 76 | }) 77 | -------------------------------------------------------------------------------- /test/rpc-server-readstream-size.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const { PeerRPCClient, PeerRPCServer } = require('../') 7 | const Link = require('grenache-nodejs-link') 8 | const setupHooks = require('./before-after.js') 9 | 10 | const PORT = 1337 11 | let link, peer, peerSrvStr, serviceStr, stop 12 | describe('RPC integration', () => { 13 | setupHooks((grapes) => { 14 | link = new Link({ 15 | grape: 'http://127.0.0.1:30001' 16 | }) 17 | link.start() 18 | 19 | peer = new PeerRPCClient(link, {}) 20 | peer.init() 21 | 22 | peerSrvStr = new PeerRPCServer(link, { 23 | timeout: 300000, 24 | disableBuffered: true 25 | }) 26 | 27 | peerSrvStr.init() 28 | serviceStr = peerSrvStr.transport('stream') 29 | serviceStr.listen(PORT) 30 | 31 | link.announce('rpc_manual', serviceStr.port, {}, (err, res) => { 32 | if (err) throw Error('error in announce, setup') 33 | }) 34 | 35 | stop = () => { 36 | peer.stop() 37 | link.stop() 38 | serviceStr.stop() 39 | } 40 | }) 41 | 42 | const pdfFile = path.join(__dirname, './example-amazon-dynamo-sosp2007.pdf') 43 | 44 | it('manual request handling works', (done) => { 45 | serviceStr.on('stream', (req, res, meta, handler) => { 46 | assert.strictEqual(meta.isStream, true) 47 | assert.strictEqual(req.headers['content-type'], 'application/pdf') 48 | 49 | const rid = meta.infoHeaders[0] 50 | const writer = fs.createWriteStream('test.pdf') 51 | req.pipe(writer) 52 | 53 | writer.on('close', () => { 54 | const origStat = fs.statSync(pdfFile) 55 | const res = fs.statSync('test.pdf') 56 | assert.strictEqual(res.size, origStat.size) 57 | 58 | handler.reply(rid, null, 'ok') 59 | }) 60 | }) 61 | 62 | const optsPdf = { 63 | key: 'example-amazon-dynamo-sosp2007-4.pdf', 64 | acl: 'public-read', 65 | bucket: 'BUCKET', 66 | contentType: 'application/pdf' 67 | } 68 | const req = peer.stream('rpc_manual', { 69 | headers: { 70 | _a: 'uploadNewPublicStream', 71 | _ar: optsPdf, 72 | 'content-type': 'application/pdf' 73 | }, 74 | timeout: 10000 75 | }) 76 | 77 | req.on('data', (data) => { 78 | const [,, msg] = JSON.parse(data.toString()) 79 | assert.strictEqual(msg, 'ok') 80 | stop() 81 | done() 82 | }) 83 | 84 | const data = fs.createReadStream(pdfFile) 85 | 86 | data.pipe(req) 87 | }).timeout(7000) 88 | }) 89 | -------------------------------------------------------------------------------- /test/rpc-server-manual-request-handling.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const { PeerRPCClient, PeerRPCServer } = require('../') 7 | const Link = require('grenache-nodejs-link') 8 | const setupHooks = require('./before-after.js') 9 | 10 | const PORT = 1337 11 | let link, peer, peerSrvStr, serviceStr, stop 12 | describe('RPC integration', () => { 13 | setupHooks((grapes) => { 14 | link = new Link({ 15 | grape: 'http://127.0.0.1:30001' 16 | }) 17 | link.start() 18 | 19 | peer = new PeerRPCClient(link, {}) 20 | peer.init() 21 | 22 | peerSrvStr = new PeerRPCServer(link, { 23 | timeout: 300000, 24 | disableBuffered: true 25 | }) 26 | 27 | peerSrvStr.init() 28 | serviceStr = peerSrvStr.transport('stream') 29 | serviceStr.listen(PORT) 30 | 31 | link.announce('rpc_manual', serviceStr.port, {}, (err, res) => { 32 | if (err) throw Error('error in announce, setup') 33 | }) 34 | 35 | stop = () => { 36 | peer.stop() 37 | link.stop() 38 | serviceStr.stop() 39 | } 40 | }) 41 | 42 | const buf = fs.readFileSync( 43 | path.join(__dirname, './example-amazon-dynamo-sosp2007.pdf') 44 | ) 45 | 46 | const optsPdf = { 47 | key: 'example-amazon-dynamo-sosp2007-4.pdf', 48 | acl: 'public-read', 49 | bucket: 'bfx-dev-robert', 50 | contentType: 'application/pdf' 51 | } 52 | 53 | const queryUploadPublicPdf = { 54 | action: 'uploadPublic', 55 | args: [buf.toString('hex'), optsPdf] 56 | } 57 | 58 | it('manual request handling works', (done) => { 59 | serviceStr.on('stream', (req, res, meta, handler) => { 60 | assert.strictEqual(meta.isStream, false) 61 | 62 | if (meta.isStream === false) { 63 | // console.log('no stream content, decide to parse') 64 | serviceStr.handleBufferedRequest(req, res, meta) 65 | } 66 | }) 67 | 68 | serviceStr.on('request', (rid, key, payload, handler, cert, meta) => { 69 | assert.ok(typeof rid === 'string') 70 | assert.strictEqual(key, 'rpc_manual') 71 | const [pdf, opts] = payload.args 72 | assert.deepStrictEqual(opts, optsPdf) 73 | 74 | assert.deepStrictEqual([pdf[0], pdf[1], pdf[2]].join(''), '255') 75 | 76 | assert.strictEqual(meta.isStream, false) 77 | handler.reply(null, 'ok') 78 | }) 79 | 80 | const opts = { timeout: 100000 } 81 | peer.request('rpc_manual', queryUploadPublicPdf, opts, (err, result) => { 82 | if (err) throw err 83 | assert.deepStrictEqual(result, 'ok') 84 | 85 | stop() 86 | done() 87 | }) 88 | }).timeout(7000) 89 | }) 90 | -------------------------------------------------------------------------------- /test/peer-rpc-server-secure.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const { PeerRPCClient, PeerRPCServer } = require('../') 7 | const Link = require('grenache-nodejs-link') 8 | const setupHooks = require('./before-after.js') 9 | 10 | const PORT = 1337 11 | let link, peer, peerSrv, service, stop 12 | describe('RPC integration', () => { 13 | setupHooks((grapes) => { 14 | link = new Link({ 15 | grape: 'http://127.0.0.1:30001' 16 | }) 17 | link.start() 18 | 19 | peer = new PeerRPCClient(link, { 20 | secure: { 21 | key: fs.readFileSync(path.join(__dirname, './certs/client1-key.pem')), 22 | cert: fs.readFileSync(path.join(__dirname, './certs/client1-crt.pem')), 23 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 24 | rejectUnauthorized: false // take care, can be dangerous in production! 25 | } 26 | }) 27 | peer.init() 28 | 29 | peerSrv = new PeerRPCServer(link, { 30 | secure: { 31 | key: fs.readFileSync(path.join(__dirname, './certs/server-key.pem')), 32 | cert: fs.readFileSync(path.join(__dirname, './certs/server-crt.pem')), 33 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 34 | requestCert: true, 35 | rejectUnauthorized: false // take care, can be dangerous in production! 36 | }, 37 | timeout: 300000 38 | }) 39 | 40 | peerSrv.init() 41 | service = peerSrv.transport('server') 42 | service.listen(PORT) 43 | 44 | link.announce('rpc_test', service.port, {}, (err, res) => { 45 | if (err) throw Error('error in announce, setup') 46 | }) 47 | 48 | stop = () => { 49 | peer.stop() 50 | link.stop() 51 | service.stop() 52 | } 53 | }) 54 | 55 | it('secure request, objects', (done) => { 56 | service.on('request', (rid, key, payload, handler, cert) => { 57 | assert.ok(typeof rid === 'string') 58 | assert.strictEqual(key, 'rpc_test') 59 | assert.deepStrictEqual(payload, { hello: 'world' }) 60 | assert.strictEqual(cert.fingerprint, 'E1:63:C1:46:B3:B6:46:3B:5E:92:98:34:40:A1:AB:FB:0E:92:D0:76') 61 | 62 | handler.reply(null, { hello: 'helloworld' }) 63 | }) 64 | 65 | const opts = { timeout: 100000 } 66 | peer.request('rpc_test', { hello: 'world' }, opts, (err, result) => { 67 | if (err) throw err 68 | 69 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 70 | 71 | stop() 72 | done() 73 | }) 74 | }).timeout(7000) 75 | 76 | it('secure map, strings', (done) => { 77 | service.on('request', (rid, key, payload, handler, cert) => { 78 | assert.ok(typeof rid === 'string') 79 | assert.strictEqual(key, 'rpc_test') 80 | assert.strictEqual(payload, 'hello') 81 | assert.strictEqual(cert.fingerprint, 'E1:63:C1:46:B3:B6:46:3B:5E:92:98:34:40:A1:AB:FB:0E:92:D0:76') 82 | 83 | handler.reply(null, 'world') 84 | }) 85 | 86 | const opts = { timeout: 100000 } 87 | peer.map('rpc_test', 'hello', opts, (err, result) => { 88 | if (err) throw err 89 | 90 | assert.deepStrictEqual(result, ['world']) 91 | 92 | stop() 93 | done() 94 | }) 95 | }).timeout(7000) 96 | }) 97 | -------------------------------------------------------------------------------- /lib/TransportRPCServer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const http = require('http') 4 | const https = require('https') 5 | const zlib = require('zlib') 6 | const assert = require('assert') 7 | 8 | const Base = require('grenache-nodejs-base') 9 | 10 | class TransportRPCServer extends Base.TransportRPCServer { 11 | constructor (client, conf) { 12 | super(client, conf) 13 | this.conf = conf 14 | 15 | this.init() 16 | this.disableBuffered = this.conf.disableBuffered || false 17 | } 18 | 19 | getSocket (secure) { 20 | if (!secure) { 21 | return http.createServer() 22 | } 23 | 24 | assert(Buffer.isBuffer(secure.key), 'conf.secure.key must be a Buffer') 25 | assert(Buffer.isBuffer(secure.cert), 'conf.secure.cert must be a Buffer') 26 | assert(Buffer.isBuffer(secure.ca), 'conf.secure.ca must be a Buffer') 27 | 28 | return https.createServer(secure) 29 | } 30 | 31 | listen (port) { 32 | const secure = this.conf.secure 33 | const socket = this.getSocket(secure) 34 | 35 | const timeout = this.conf.timeout 36 | if (timeout) { 37 | socket.setTimeout(timeout) 38 | } 39 | 40 | socket.on('request', (req, rep) => { 41 | const isStream = req.headers['transfer-encoding'] === 'chunked' 42 | const enc = req.headers['grc-compress'] || '' 43 | const isCompress = enc.includes('gzip') 44 | 45 | const cert = secure ? req.socket.getPeerCertificate() : undefined 46 | const meta = { cert, isStream, compress: isCompress } 47 | 48 | try { 49 | if (req.headers._gr) { 50 | meta.infoHeaders = JSON.parse(req.headers._gr) 51 | } 52 | } catch (e) { 53 | rep.statusCode = 500 54 | rep.end('[null, "ERR_HEADER_PARSE_GR", null]') 55 | return 56 | } 57 | 58 | meta.action = req.headers._a || null 59 | 60 | try { 61 | meta.args = JSON.parse(req.headers._ar) 62 | } catch (e) { 63 | meta.args = {} 64 | } 65 | 66 | this.emit('stream', req, rep, meta, { 67 | reply: this.sendReply.bind(this, rep) 68 | }) 69 | 70 | if (this.disableBuffered) return 71 | 72 | this.handleBufferedRequest(req, rep, meta) 73 | }).listen(port) 74 | 75 | this.socket = socket 76 | this.port = port 77 | 78 | return this 79 | } 80 | 81 | handleBufferedRequest (req, rep, meta) { 82 | const handler = { 83 | reply: (rid, err, res) => { 84 | this.sendReply(rep, rid, err, res, meta) 85 | } 86 | } 87 | 88 | let body = [] 89 | 90 | req.on('data', (chunk) => { 91 | body.push(chunk) 92 | }).on('end', async () => { 93 | body = Buffer.concat(body) 94 | 95 | if (meta.compress) { 96 | try { 97 | body = await new Promise((resolve, reject) => { 98 | zlib.gunzip(body, (err, res) => { 99 | if (err) { 100 | return reject(err) 101 | } 102 | 103 | resolve(res) 104 | }) 105 | }) 106 | } catch (err) { 107 | // NOTE: we try keeping the body, since decompression failed 108 | } 109 | } 110 | 111 | const data = this.parse(body) 112 | 113 | this.handleRequest( 114 | handler, 115 | data, 116 | meta 117 | ) 118 | }) 119 | } 120 | 121 | getRidKey (infoHeaders, data) { 122 | // a stream will supply rid and key via header 123 | let rid, key 124 | 125 | if (infoHeaders && infoHeaders[0]) { 126 | rid = infoHeaders[0] 127 | } else { 128 | rid = data[0] 129 | } 130 | 131 | if (infoHeaders && infoHeaders[1]) { 132 | key = infoHeaders[1] 133 | } else { 134 | key = data[1] 135 | } 136 | 137 | return { rid, key } 138 | } 139 | 140 | handleRequest (handler, data, meta) { 141 | if (!data) { 142 | this.emit('request-error') 143 | return 144 | } 145 | 146 | const { infoHeaders } = meta 147 | const { rid, key } = this.getRidKey(infoHeaders, data) 148 | const payload = data[2] 149 | 150 | this.emit( 151 | 'request', rid, key, payload, 152 | { 153 | reply: (err, res) => { 154 | handler.reply(rid, err, res) 155 | } 156 | }, 157 | meta.cert, // TODO remove compat mode 158 | meta 159 | ) 160 | } 161 | 162 | unlisten () { 163 | if (!this.socket) return 164 | try { 165 | this.socket.close() 166 | } catch (e) {} 167 | this.socket = null 168 | } 169 | 170 | async sendReply (rep, rid, err, res, meta) { 171 | let out = this.format([ 172 | rid, err ? err.message : null, 173 | res 174 | ]) 175 | 176 | const send = (out) => { 177 | rep.write(out) 178 | rep.end() 179 | } 180 | 181 | if (meta && meta.compress) { 182 | try { 183 | out = await new Promise((resolve, reject) => { 184 | zlib.gzip(out, (err, res) => { 185 | if (err) { 186 | return reject(err) 187 | } 188 | 189 | resolve(res) 190 | }) 191 | }) 192 | } catch (err) { 193 | out = this.format([ 194 | rid, err ? err.message : null, 195 | res 196 | ]) 197 | } 198 | } 199 | 200 | send(out) 201 | } 202 | 203 | _stop () { 204 | super._stop() 205 | this.unlisten() 206 | } 207 | } 208 | 209 | module.exports = TransportRPCServer 210 | -------------------------------------------------------------------------------- /lib/TransportRPCClient.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const duplexify = require('duplexify') 4 | const fetch = require('node-fetch') 5 | const assert = require('assert') 6 | const http = require('http') 7 | const https = require('https') 8 | const pump = require('pump') 9 | const zlib = require('zlib') 10 | const { PassThrough } = require('stream') 11 | 12 | const Base = require('grenache-nodejs-base') 13 | 14 | class TransportRPCClient extends Base.TransportRPCClient { 15 | constructor (client, conf) { 16 | super(client, conf) 17 | 18 | this.conf = conf 19 | this.init() 20 | } 21 | 22 | init () { 23 | super.init() 24 | 25 | if (this.conf.secure) { 26 | assert(Buffer.isBuffer(this.conf.secure.key), 'conf.secure.key must be a Buffer') 27 | assert(Buffer.isBuffer(this.conf.secure.cert), 'conf.secure.cert must be a Buffer') 28 | assert(Buffer.isBuffer(this.conf.secure.ca), 'conf.secure.ca must be a Buffer') 29 | } 30 | } 31 | 32 | getOpts (_opts, secure) { 33 | const u = secure 34 | ? ('https://' + this.conf.dest) 35 | : ('http://' + this.conf.dest) 36 | 37 | const def = { 38 | url: u, 39 | path: '/', 40 | method: 'POST' 41 | } 42 | 43 | const opts = { ...def, ..._opts } 44 | if (secure) { 45 | opts.agent = new https.Agent(secure) 46 | } 47 | 48 | return opts 49 | } 50 | 51 | request (key, payload, opts, cb) { 52 | this._request(key, payload, opts, cb) 53 | } 54 | 55 | async sendRequest (req) { 56 | let postData = this.format([req.rid, req.key, req.payload]) 57 | const isCompress = req.opts.compress 58 | 59 | if (isCompress) { 60 | try { 61 | postData = await (new Promise((resolve, reject) => { 62 | zlib.gzip(postData, (err, res) => { 63 | if (err) { 64 | return reject(err) 65 | } 66 | 67 | resolve(res) 68 | }) 69 | })) 70 | } catch (e) { 71 | this.handleReply( 72 | req.rid, 73 | new Error('ERR_REQUEST_ENCODING_COMPRESSION') 74 | ) 75 | } 76 | } 77 | 78 | this.post({ 79 | timeout: req.opts.timeout, 80 | body: postData, 81 | headers: { 82 | 'grc-compress': isCompress ? 'gzip' : 'none' 83 | }, 84 | encoding: null 85 | }, async (err, body) => { 86 | if (err) { 87 | this.handleReply(req.rid, new Error(`ERR_REQUEST_GENERIC: ${err.message}`)) 88 | return 89 | } 90 | 91 | if (isCompress) { 92 | try { 93 | body = await new Promise((resolve, reject) => { 94 | zlib.gunzip(body, (err, res) => { 95 | if (err) { 96 | return reject(err) 97 | } 98 | 99 | resolve(res) 100 | }) 101 | }) 102 | } catch (e) { 103 | this.handleReply( 104 | req.rid, 105 | new Error('ERR_REPLY_ENCODING_COMPRESSION') 106 | ) 107 | return 108 | } 109 | } 110 | 111 | const data = this.parse(body) 112 | 113 | if (!data) { 114 | this.handleReply(req.rid, new Error('ERR_REPLY_EMPTY')) 115 | return 116 | } 117 | 118 | const [rid, _err, res] = data 119 | this.handleReply(rid, _err ? new Error(_err) : null, res) 120 | }) 121 | } 122 | 123 | async post (_opts, _cb) { 124 | const opts = this.getOpts(_opts, this.conf.secure) 125 | 126 | let isExecuted = false 127 | 128 | const cb = (err, body) => { 129 | if (isExecuted) return 130 | isExecuted = true 131 | _cb(err, body) 132 | } 133 | 134 | try { 135 | const resp = await fetch(opts.url, { 136 | ...opts, 137 | method: 'POST' 138 | }) 139 | 140 | const body = await resp.buffer() 141 | 142 | if (!resp.ok) { 143 | const err = new Error(body.toString()) 144 | err.code = resp.status 145 | return cb(err) 146 | } 147 | 148 | return cb(null, body) 149 | } catch (err) { 150 | return cb(err) 151 | } 152 | } 153 | 154 | async requestStream (key, opts) { 155 | return this._requestStream(key, opts) 156 | } 157 | 158 | async sendRequestStream (req) { 159 | const addHeaders = {} 160 | const _h = req.opts.headers || {} 161 | Object.keys(_h).forEach((k) => { 162 | if (typeof _h[k] === 'string') { 163 | addHeaders[k] = _h[k] 164 | return 165 | } 166 | addHeaders[k] = JSON.stringify(_h[k]) 167 | }) 168 | 169 | const _opts = { 170 | headers: { 171 | _gr: this.format([req.rid, req.key]), 172 | connection: 'close', 173 | 'transfer-encoding': 'chunked', 174 | ...addHeaders 175 | }, 176 | timeout: req.opts.timeout 177 | } 178 | 179 | const opts = this.getOpts(_opts, this.conf.secure) 180 | 181 | // node-fetch does not support multiplex communication, no response is received until body is fully streamed 182 | const reqStream = new PassThrough() 183 | const resStream = new PassThrough() 184 | 185 | const url = new URL(opts.url) 186 | const httpReq = (this.conf.secure ? https : http).request({ 187 | ...opts, 188 | hostname: url.hostname, 189 | port: url.port, 190 | protocol: url.protocol, 191 | path: url.pathname, 192 | method: 'POST' 193 | }, (res) => { 194 | pump(res, resStream) 195 | }) 196 | 197 | httpReq.on('error', (err) => { 198 | reqStream.destroy(err) 199 | resStream.destroy(err) 200 | }) 201 | pump(reqStream, httpReq) 202 | return duplexify(reqStream, resStream) 203 | } 204 | } 205 | 206 | module.exports = TransportRPCClient 207 | -------------------------------------------------------------------------------- /test/peer-rpc-server-streaming.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const { PassThrough } = require('stream') 5 | const { PeerRPCClient, PeerRPCServer } = require('../') 6 | const Link = require('grenache-nodejs-link') 7 | const setupHooks = require('./before-after.js') 8 | 9 | const PORT = 1337 10 | let link, peer, peerSrvStr, serviceStr, stop 11 | describe('RPC integration', () => { 12 | setupHooks((grapes) => { 13 | link = new Link({ 14 | grape: 'http://127.0.0.1:30001' 15 | }) 16 | link.start() 17 | 18 | peer = new PeerRPCClient(link, {}) 19 | peer.init() 20 | 21 | peerSrvStr = new PeerRPCServer(link, { 22 | timeout: 300000, 23 | disableBuffered: true 24 | }) 25 | 26 | peerSrvStr.init() 27 | serviceStr = peerSrvStr.transport('stream') 28 | serviceStr.listen(PORT) 29 | 30 | link.announce('rpc_stream', serviceStr.port, {}, (err, res) => { 31 | if (err) throw Error('error in announce, setup') 32 | }) 33 | 34 | stop = () => { 35 | peer.stop() 36 | link.stop() 37 | serviceStr.stop() 38 | } 39 | }) 40 | 41 | it('streaming works, objects, buffered client, reply handler', (done) => { 42 | serviceStr.on('stream', (req, res, meta, handler) => { 43 | assert.strictEqual(meta.isStream, false) 44 | 45 | req.on('data', (d) => { 46 | const [rid, key, args] = JSON.parse(d.toString()) 47 | 48 | assert.deepStrictEqual(args, { hello: 'world' }) 49 | assert.ok(typeof rid === 'string') 50 | assert.strictEqual(key, 'rpc_stream') 51 | 52 | handler.reply(rid, null, { hello: 'helloworld' }) 53 | }) 54 | }) 55 | 56 | const opts = { timeout: 100000 } 57 | peer.request('rpc_stream', { hello: 'world' }, opts, (err, result) => { 58 | if (err) throw err 59 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 60 | 61 | stop() 62 | done() 63 | }) 64 | }).timeout(7000) 65 | 66 | it('streaming works, string, buffered client, reply handler', (done) => { 67 | serviceStr.on('stream', (req, res, meta, handler) => { 68 | assert.strictEqual(meta.isStream, false) 69 | 70 | req.on('data', (d) => { 71 | const [rid, key, args] = JSON.parse(d.toString()) 72 | 73 | assert.strictEqual(args, 'hello') 74 | assert.ok(typeof rid === 'string') 75 | assert.strictEqual(key, 'rpc_stream') 76 | 77 | handler.reply(rid, null, 'world') 78 | }) 79 | }) 80 | 81 | const opts = { timeout: 100000 } 82 | peer.request('rpc_stream', 'hello', opts, (err, result) => { 83 | if (err) throw err 84 | assert.strictEqual(result, 'world') 85 | 86 | stop() 87 | done() 88 | }) 89 | }).timeout(7000) 90 | 91 | it('streaming works, string, buffered client, stream sent back', (done) => { 92 | serviceStr.on('stream', (req, res, meta, handler) => { 93 | assert.strictEqual(meta.isStream, false) 94 | 95 | req.on('data', (d) => { 96 | const [rid, key, args] = JSON.parse(d.toString()) 97 | 98 | assert.strictEqual(args, 'hello') 99 | assert.ok(typeof rid === 'string') 100 | assert.strictEqual(key, 'rpc_stream') 101 | 102 | const writable = new PassThrough() 103 | const payload = JSON.stringify([rid, null, 'world2']) 104 | writable.pipe(res) 105 | writable.write(payload) 106 | writable.end() 107 | }) 108 | }) 109 | 110 | const opts = { timeout: 100000 } 111 | peer.request('rpc_stream', 'hello', opts, (err, result) => { 112 | if (err) throw err 113 | assert.strictEqual(result, 'world2') 114 | 115 | stop() 116 | done() 117 | }) 118 | }).timeout(7000) 119 | 120 | it('streaming works, buffer, stream client, stream on server', (done) => { 121 | const reqPayload = Buffer.from('megalargefile') 122 | const resPayload = Buffer.from('superlargefile') 123 | 124 | serviceStr.on('stream', (req, res, meta, handler) => { 125 | assert.strictEqual(meta.isStream, true) 126 | 127 | req.on('data', (d) => { 128 | const [rid, key] = meta.infoHeaders 129 | 130 | assert.ok(d.equals(reqPayload), 'received Buffer equals payload on server') 131 | 132 | assert.ok(typeof rid === 'string') 133 | assert.strictEqual(key, 'rpc_stream') 134 | 135 | const writable = new PassThrough() 136 | writable.pipe(res) 137 | writable.write(resPayload) 138 | writable.end() 139 | }) 140 | }) 141 | 142 | const req = peer.stream('rpc_stream', { 143 | timeout: 10000 144 | }) 145 | 146 | req 147 | .on('data', (data) => { 148 | assert.ok(data.equals(resPayload), 'received Buffer equals payload on client') 149 | 150 | stop() 151 | done() 152 | }) 153 | .on('end', () => {}) 154 | .on('error', (e) => { console.error(e) }) 155 | 156 | const writable = new PassThrough() 157 | writable.write(reqPayload) 158 | writable.pipe(req) 159 | }).timeout(7000) 160 | 161 | it('streaming works, buffer, stream client, stream on server, additional headers', (done) => { 162 | const reqPayload = Buffer.from('megalargefile') 163 | const resPayload = Buffer.from('superlargefile') 164 | 165 | serviceStr.on('stream', (req, res, meta, handler) => { 166 | assert.strictEqual(meta.isStream, true) 167 | 168 | req.on('data', (d) => { 169 | const [rid, key] = meta.infoHeaders 170 | 171 | assert.ok(d.equals(reqPayload), 'received Buffer equals payload on server') 172 | 173 | assert.ok(typeof rid === 'string') 174 | assert.strictEqual(key, 'rpc_stream') 175 | 176 | assert.strictEqual(meta.action, 'uploadPublic') 177 | assert.strictEqual(meta.args.foo, 'bar') 178 | 179 | const writable = new PassThrough() 180 | writable.pipe(res) 181 | writable.write(resPayload) 182 | writable.end() 183 | }) 184 | }) 185 | 186 | const req = peer.stream('rpc_stream', { 187 | timeout: 10000, 188 | headers: { 189 | _a: 'uploadPublic', 190 | _ar: { foo: 'bar' } 191 | } 192 | }) 193 | 194 | req 195 | .on('data', (data) => { 196 | assert.ok(data.equals(resPayload), 'received Buffer equals payload on client') 197 | 198 | stop() 199 | done() 200 | }) 201 | .on('end', () => {}) 202 | .on('error', (e) => { console.error(e) }) 203 | 204 | const writable = new PassThrough() 205 | writable.write(reqPayload) 206 | writable.pipe(req) 207 | }).timeout(7000) 208 | }) 209 | -------------------------------------------------------------------------------- /test/peer-rpc-server-secure-streaming.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const fs = require('fs') 5 | const path = require('path') 6 | const { PassThrough } = require('stream') 7 | const { PeerRPCClient, PeerRPCServer } = require('../') 8 | const Link = require('grenache-nodejs-link') 9 | const setupHooks = require('./before-after.js') 10 | 11 | const PORT = 1337 12 | let link, peer, peerSrvStr, serviceStr, stop 13 | describe('RPC integration', () => { 14 | setupHooks((grapes) => { 15 | link = new Link({ 16 | grape: 'http://127.0.0.1:30001' 17 | }) 18 | link.start() 19 | 20 | peer = new PeerRPCClient(link, { 21 | secure: { 22 | key: fs.readFileSync(path.join(__dirname, './certs/client1-key.pem')), 23 | cert: fs.readFileSync(path.join(__dirname, './certs/client1-crt.pem')), 24 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 25 | rejectUnauthorized: false // take care, can be dangerous in production! 26 | } 27 | }) 28 | peer.init() 29 | 30 | peerSrvStr = new PeerRPCServer(link, { 31 | secure: { 32 | key: fs.readFileSync(path.join(__dirname, './certs/server-key.pem')), 33 | cert: fs.readFileSync(path.join(__dirname, './certs/server-crt.pem')), 34 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 35 | requestCert: true, 36 | rejectUnauthorized: false // take care, can be dangerous in production! 37 | }, 38 | timeout: 300000, 39 | disableBuffered: true 40 | }) 41 | 42 | peerSrvStr.init() 43 | serviceStr = peerSrvStr.transport('stream') 44 | serviceStr.listen(PORT) 45 | 46 | link.announce('rpc_stream', serviceStr.port, {}, (err, res) => { 47 | if (err) throw Error('error in announce, setup') 48 | }) 49 | 50 | stop = () => { 51 | peer.stop() 52 | link.stop() 53 | serviceStr.stop() 54 | } 55 | }) 56 | 57 | it('streaming works, objects, buffered client, reply handler', (done) => { 58 | serviceStr.on('stream', (req, res, meta, handler) => { 59 | assert.strictEqual(meta.isStream, false) 60 | 61 | req.on('data', (d) => { 62 | const [rid, key, args] = JSON.parse(d.toString()) 63 | 64 | assert.deepStrictEqual(args, { hello: 'world' }) 65 | assert.ok(typeof rid === 'string') 66 | assert.strictEqual(key, 'rpc_stream') 67 | 68 | handler.reply(rid, null, { hello: 'helloworld' }) 69 | }) 70 | }) 71 | 72 | const opts = { timeout: 100000 } 73 | peer.request('rpc_stream', { hello: 'world' }, opts, (err, result) => { 74 | if (err) throw err 75 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 76 | 77 | stop() 78 | done() 79 | }) 80 | }).timeout(7000) 81 | 82 | it('streaming works, string, buffered client, reply handler', (done) => { 83 | serviceStr.on('stream', (req, res, meta, handler) => { 84 | assert.strictEqual(meta.isStream, false) 85 | 86 | req.on('data', (d) => { 87 | const [rid, key, args] = JSON.parse(d.toString()) 88 | 89 | assert.strictEqual(args, 'hello') 90 | assert.ok(typeof rid === 'string') 91 | assert.strictEqual(key, 'rpc_stream') 92 | 93 | handler.reply(rid, null, 'world') 94 | }) 95 | }) 96 | 97 | const opts = { timeout: 100000 } 98 | peer.request('rpc_stream', 'hello', opts, (err, result) => { 99 | if (err) throw err 100 | assert.strictEqual(result, 'world') 101 | 102 | stop() 103 | done() 104 | }) 105 | }).timeout(7000) 106 | 107 | it('streaming works, string, buffered client, stream sent back', (done) => { 108 | serviceStr.on('stream', (req, res, meta, handler) => { 109 | assert.strictEqual(meta.isStream, false) 110 | 111 | req.on('data', (d) => { 112 | const [rid, key, args] = JSON.parse(d.toString()) 113 | 114 | assert.strictEqual(args, 'hello') 115 | assert.ok(typeof rid === 'string') 116 | assert.strictEqual(key, 'rpc_stream') 117 | 118 | const writable = new PassThrough() 119 | const payload = JSON.stringify([rid, null, 'world2']) 120 | writable.pipe(res) 121 | writable.write(payload) 122 | writable.end() 123 | }) 124 | }) 125 | 126 | const opts = { timeout: 100000 } 127 | peer.request('rpc_stream', 'hello', opts, (err, result) => { 128 | if (err) throw err 129 | assert.strictEqual(result, 'world2') 130 | 131 | stop() 132 | done() 133 | }) 134 | }).timeout(7000) 135 | 136 | it('streaming works, buffer, stream client, stream on server', (done) => { 137 | const reqPayload = Buffer.from('megalargefile') 138 | const resPayload = Buffer.from('superlargefile') 139 | 140 | serviceStr.on('stream', (req, res, meta, handler) => { 141 | assert.strictEqual(meta.isStream, true) 142 | 143 | req.on('data', (d) => { 144 | const [rid, key] = meta.infoHeaders 145 | 146 | assert.ok(d.equals(reqPayload), 'received Buffer equals payload on server') 147 | 148 | assert.ok(typeof rid === 'string') 149 | assert.strictEqual(key, 'rpc_stream') 150 | 151 | const writable = new PassThrough() 152 | writable.pipe(res) 153 | writable.write(resPayload) 154 | writable.end() 155 | }) 156 | }) 157 | 158 | const req = peer.stream('rpc_stream', { 159 | timeout: 10000 160 | }) 161 | 162 | req 163 | .on('data', (data) => { 164 | assert.ok(data.equals(resPayload), 'received Buffer equals payload on client') 165 | 166 | stop() 167 | done() 168 | }) 169 | .on('end', () => {}) 170 | .on('error', (e) => { console.error(e) }) 171 | 172 | const writable = new PassThrough() 173 | writable.write(reqPayload) 174 | writable.pipe(req) 175 | }).timeout(7000) 176 | 177 | it('streaming works, buffer, stream client, stream on server, additional headers', (done) => { 178 | const reqPayload = Buffer.from('megalargefile') 179 | const resPayload = Buffer.from('superlargefile') 180 | 181 | serviceStr.on('stream', (req, res, meta, handler) => { 182 | assert.strictEqual(meta.isStream, true) 183 | 184 | req.on('data', (d) => { 185 | const [rid, key] = meta.infoHeaders 186 | 187 | assert.ok(d.equals(reqPayload), 'received Buffer equals payload on server') 188 | 189 | assert.ok(typeof rid === 'string') 190 | assert.strictEqual(key, 'rpc_stream') 191 | 192 | assert.strictEqual(meta.action, 'uploadPublic') 193 | assert.strictEqual(meta.args.foo, 'bar') 194 | 195 | const writable = new PassThrough() 196 | writable.pipe(res) 197 | writable.write(resPayload) 198 | writable.end() 199 | }) 200 | }) 201 | 202 | const req = peer.stream('rpc_stream', { 203 | timeout: 10000, 204 | headers: { 205 | _a: 'uploadPublic', 206 | _ar: { foo: 'bar' } 207 | } 208 | }) 209 | 210 | req 211 | .on('data', (data) => { 212 | assert.ok(data.equals(resPayload), 'received Buffer equals payload on client') 213 | 214 | stop() 215 | done() 216 | }) 217 | .on('end', () => {}) 218 | .on('error', (e) => { console.error(e) }) 219 | 220 | const writable = new PassThrough() 221 | writable.write(reqPayload) 222 | writable.pipe(req) 223 | }).timeout(7000) 224 | }) 225 | -------------------------------------------------------------------------------- /test/peer-rpc-server-buffered.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const { PassThrough } = require('stream') 5 | const fs = require('fs') 6 | const path = require('path') 7 | const { PeerRPCClient, PeerRPCServer } = require('../') 8 | const Link = require('grenache-nodejs-link') 9 | const setupHooks = require('./before-after.js') 10 | 11 | const PORT = 1337 12 | let link, peer, peerSrvBuf, serviceBuf, stop 13 | describe('RPC integration', () => { 14 | setupHooks((grapes) => { 15 | link = new Link({ 16 | grape: 'http://127.0.0.1:30001' 17 | }) 18 | link.start() 19 | 20 | peer = new PeerRPCClient(link, {}) 21 | peer.init() 22 | 23 | peerSrvBuf = new PeerRPCServer(link, { 24 | timeout: 300000 25 | }) 26 | 27 | peerSrvBuf.init() 28 | serviceBuf = peerSrvBuf.transport('buffered') 29 | serviceBuf.listen(PORT) 30 | 31 | link.announce('rpc_buf', serviceBuf.port, {}, (err, res) => { 32 | if (err) throw Error('error in announce, setup') 33 | }) 34 | 35 | stop = () => { 36 | peer.stop() 37 | link.stop() 38 | serviceBuf.stop() 39 | } 40 | }) 41 | 42 | it('buffered parsing works, objects', (done) => { 43 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 44 | assert.ok(typeof rid === 'string') 45 | assert.strictEqual(key, 'rpc_buf') 46 | assert.deepStrictEqual(payload, { hello: 'world' }) 47 | 48 | assert.strictEqual(meta.isStream, false) 49 | 50 | handler.reply(null, { hello: 'helloworld' }) 51 | }) 52 | 53 | const opts = { timeout: 100000 } 54 | peer.request('rpc_buf', { hello: 'world' }, opts, (err, result) => { 55 | if (err) throw err 56 | 57 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 58 | 59 | stop() 60 | done() 61 | }) 62 | }).timeout(7000) 63 | 64 | it('buffered parsing works, strings', (done) => { 65 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 66 | assert.ok(typeof rid === 'string') 67 | assert.strictEqual(key, 'rpc_buf') 68 | assert.strictEqual(payload, 'hello') 69 | 70 | assert.strictEqual(meta.isStream, false) 71 | 72 | handler.reply(null, 'world') 73 | }) 74 | 75 | const opts = { timeout: 100000 } 76 | peer.request('rpc_buf', 'hello', opts, (err, result) => { 77 | if (err) throw err 78 | 79 | assert.strictEqual(result, 'world') 80 | 81 | stop() 82 | done() 83 | }) 84 | }).timeout(7000) 85 | 86 | it('buffered parsing works, compressed strings', (done) => { 87 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 88 | assert.ok(typeof rid === 'string') 89 | assert.strictEqual(key, 'rpc_buf') 90 | assert.strictEqual(payload, 'hello') 91 | 92 | assert.strictEqual(meta.isStream, false) 93 | assert.strictEqual(meta.compress, true) 94 | 95 | handler.reply(null, 'world') 96 | }) 97 | 98 | const opts = { timeout: 100000, compress: true } 99 | peer.request('rpc_buf', 'hello', opts, (err, result) => { 100 | if (err) throw err 101 | 102 | assert.strictEqual(result, 'world') 103 | 104 | stop() 105 | done() 106 | }) 107 | }).timeout(7000) 108 | 109 | it('buffered parsing works, strings from streaming client', (done) => { 110 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 111 | assert.ok(typeof rid === 'string') 112 | assert.strictEqual(key, 'rpc_buf') 113 | assert.strictEqual(payload, 'hello') 114 | 115 | assert.strictEqual(meta.isStream, true) 116 | 117 | handler.reply(null, 'world') 118 | }) 119 | 120 | const req = peer.stream('rpc_buf', { 121 | timeout: 10000 122 | }) 123 | 124 | req 125 | .on('data', (data) => { 126 | const d = JSON.parse(data.toString()) 127 | // ["65c5737f-bbce-40f9-b48a-af8bd8869d66",null,"world"] 128 | assert.strictEqual(d[1], null) 129 | assert.strictEqual(d[2], 'world') 130 | stop() 131 | done() 132 | }) 133 | .on('end', () => {}) 134 | .on('error', (e) => { console.error(e) }) 135 | 136 | const writable = new PassThrough() 137 | writable.write('["UUID", "rpc_buf", "hello"]') 138 | writable.end() 139 | writable.pipe(req) 140 | }).timeout(7000) 141 | 142 | it('buffered parsing works, objects from streaming client', (done) => { 143 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 144 | assert.ok(typeof rid === 'string') 145 | assert.strictEqual(key, 'rpc_buf') 146 | assert.deepStrictEqual(payload, { hello: 'world' }) 147 | 148 | assert.strictEqual(meta.isStream, true) 149 | 150 | handler.reply(null, { hello: 'helloworld' }) 151 | }) 152 | 153 | const req = peer.stream('rpc_buf', { 154 | timeout: 10000 155 | }) 156 | 157 | req 158 | .on('data', (data) => { 159 | const d = JSON.parse(data.toString()) 160 | assert.strictEqual(d[1], null) 161 | assert.deepStrictEqual(d[2], { hello: 'helloworld' }) 162 | stop() 163 | done() 164 | }) 165 | .on('end', () => {}) 166 | .on('error', (e) => { console.error(e) }) 167 | 168 | const writable = new PassThrough() 169 | writable.write('["UUID", "rpc_buf", { "hello": "world" }]') 170 | writable.end() 171 | writable.pipe(req) 172 | }).timeout(7000) 173 | 174 | it('buffered parsing works, extra headers', (done) => { 175 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 176 | assert.ok(typeof rid === 'string') 177 | assert.strictEqual(key, 'rpc_buf') 178 | assert.deepStrictEqual(payload, { hello: 'world' }) 179 | 180 | assert.strictEqual(meta.action, 'uploadPublic') 181 | assert.deepStrictEqual(meta.args, { foo: 'bar' }) 182 | assert.strictEqual(meta.isStream, true) 183 | 184 | handler.reply(null, { hello: 'helloworld' }) 185 | }) 186 | 187 | const req = peer.stream('rpc_buf', { 188 | timeout: 10000, 189 | headers: { _a: 'uploadPublic', _ar: { foo: 'bar' } } 190 | }) 191 | 192 | req 193 | .on('data', (data) => { 194 | const d = JSON.parse(data.toString()) 195 | assert.strictEqual(d[1], null) 196 | assert.deepStrictEqual(d[2], { hello: 'helloworld' }) 197 | stop() 198 | done() 199 | }) 200 | .on('end', () => {}) 201 | .on('error', (e) => { console.error(e) }) 202 | 203 | const writable = new PassThrough() 204 | writable.write('["UUID", "rpc_buf", { "hello": "world" }]') 205 | writable.end() 206 | writable.pipe(req) 207 | }).timeout(7000) 208 | 209 | it('buf/buf: serialized files work', (done) => { 210 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 211 | assert.ok(typeof rid === 'string') 212 | assert.strictEqual(key, 'rpc_buf') 213 | 214 | assert.strictEqual(payload.action, 'uploadPublic') 215 | assert.strictEqual(payload.args[1].key, 'example-amazon-dynamo-sosp2007-4.pdf') 216 | assert.strictEqual(typeof payload.args[0], 'string') 217 | 218 | assert.strictEqual(meta.isStream, false) 219 | 220 | handler.reply(null, 'ok') 221 | }) 222 | 223 | const opts = { timeout: 100000 } 224 | const buf = fs.readFileSync( 225 | path.join(__dirname, './example-amazon-dynamo-sosp2007.pdf') 226 | ) 227 | 228 | const optsPdf = { 229 | key: 'example-amazon-dynamo-sosp2007-4.pdf', 230 | acl: 'public-read', 231 | bucket: 'bfx-dev-robert', 232 | contentType: 'application/pdf' 233 | } 234 | 235 | const queryUploadPublicPdf = { 236 | action: 'uploadPublic', 237 | args: [buf.toString('hex'), optsPdf] 238 | } 239 | peer.request('rpc_buf', queryUploadPublicPdf, opts, (err, result) => { 240 | if (err) throw err 241 | 242 | assert.deepStrictEqual(result, 'ok') 243 | 244 | stop() 245 | done() 246 | }) 247 | }).timeout(7000) 248 | }) 249 | -------------------------------------------------------------------------------- /test/peer-rpc-server-secure-buffered.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const assert = require('assert') 4 | const { PassThrough } = require('stream') 5 | const fs = require('fs') 6 | const path = require('path') 7 | const { PeerRPCClient, PeerRPCServer } = require('../') 8 | const Link = require('grenache-nodejs-link') 9 | const setupHooks = require('./before-after.js') 10 | 11 | const PORT = 1337 12 | let link, peer, peerSrvBuf, serviceBuf, stop 13 | describe('RPC integration', () => { 14 | setupHooks((grapes) => { 15 | link = new Link({ 16 | grape: 'http://127.0.0.1:30001' 17 | }) 18 | link.start() 19 | 20 | peer = new PeerRPCClient(link, { 21 | secure: { 22 | key: fs.readFileSync(path.join(__dirname, './certs/client1-key.pem')), 23 | cert: fs.readFileSync(path.join(__dirname, './certs/client1-crt.pem')), 24 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 25 | rejectUnauthorized: false // take care, can be dangerous in production! 26 | } 27 | }) 28 | peer.init() 29 | 30 | peerSrvBuf = new PeerRPCServer(link, { 31 | secure: { 32 | key: fs.readFileSync(path.join(__dirname, './certs/server-key.pem')), 33 | cert: fs.readFileSync(path.join(__dirname, './certs/server-crt.pem')), 34 | ca: fs.readFileSync(path.join(__dirname, './certs/ca-crt.pem')), 35 | requestCert: true, 36 | rejectUnauthorized: false // take care, can be dangerous in production! 37 | }, 38 | timeout: 300000 39 | }) 40 | 41 | peerSrvBuf.init() 42 | serviceBuf = peerSrvBuf.transport('buffered') 43 | serviceBuf.listen(PORT) 44 | 45 | link.announce('rpc_buf', serviceBuf.port, {}, (err, res) => { 46 | if (err) throw Error('error in announce, setup') 47 | }) 48 | 49 | stop = () => { 50 | peer.stop() 51 | link.stop() 52 | serviceBuf.stop() 53 | } 54 | }) 55 | 56 | it('buffered parsing works, objects', (done) => { 57 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 58 | assert.ok(typeof rid === 'string') 59 | assert.strictEqual(key, 'rpc_buf') 60 | assert.deepStrictEqual(payload, { hello: 'world' }) 61 | 62 | assert.strictEqual(meta.isStream, false) 63 | 64 | handler.reply(null, { hello: 'helloworld' }) 65 | }) 66 | 67 | const opts = { timeout: 100000 } 68 | peer.request('rpc_buf', { hello: 'world' }, opts, (err, result) => { 69 | if (err) throw err 70 | 71 | assert.deepStrictEqual(result, { hello: 'helloworld' }) 72 | 73 | stop() 74 | done() 75 | }) 76 | }).timeout(7000) 77 | 78 | it('buffered parsing works, strings', (done) => { 79 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 80 | assert.ok(typeof rid === 'string') 81 | assert.strictEqual(key, 'rpc_buf') 82 | assert.strictEqual(payload, 'hello') 83 | 84 | assert.strictEqual(meta.isStream, false) 85 | 86 | handler.reply(null, 'world') 87 | }) 88 | 89 | const opts = { timeout: 100000 } 90 | peer.request('rpc_buf', 'hello', opts, (err, result) => { 91 | if (err) throw err 92 | 93 | assert.strictEqual(result, 'world') 94 | 95 | stop() 96 | done() 97 | }) 98 | }).timeout(7000) 99 | 100 | it('buffered parsing works, compressed strings', (done) => { 101 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 102 | assert.ok(typeof rid === 'string') 103 | assert.strictEqual(key, 'rpc_buf') 104 | assert.strictEqual(payload, 'hello') 105 | 106 | assert.strictEqual(meta.isStream, false) 107 | assert.strictEqual(meta.compress, true) 108 | 109 | handler.reply(null, 'world') 110 | }) 111 | 112 | const opts = { timeout: 100000, compress: true } 113 | peer.request('rpc_buf', 'hello', opts, (err, result) => { 114 | if (err) throw err 115 | 116 | assert.strictEqual(result, 'world') 117 | 118 | stop() 119 | done() 120 | }) 121 | }).timeout(7000) 122 | 123 | it('buffered parsing works, strings from streaming client', (done) => { 124 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 125 | assert.ok(typeof rid === 'string') 126 | assert.strictEqual(key, 'rpc_buf') 127 | assert.strictEqual(payload, 'hello') 128 | 129 | assert.strictEqual(meta.isStream, true) 130 | 131 | handler.reply(null, 'world') 132 | }) 133 | 134 | const req = peer.stream('rpc_buf', { 135 | timeout: 10000 136 | }) 137 | 138 | req 139 | .on('data', (data) => { 140 | const d = JSON.parse(data.toString()) 141 | // ["65c5737f-bbce-40f9-b48a-af8bd8869d66",null,"world"] 142 | assert.strictEqual(d[1], null) 143 | assert.strictEqual(d[2], 'world') 144 | stop() 145 | done() 146 | }) 147 | .on('end', () => {}) 148 | .on('error', (e) => { console.error(e) }) 149 | 150 | const writable = new PassThrough() 151 | writable.write('["UUID", "rpc_buf", "hello"]') 152 | writable.end() 153 | writable.pipe(req) 154 | }).timeout(7000) 155 | 156 | it('buffered parsing works, objects from streaming client', (done) => { 157 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 158 | assert.ok(typeof rid === 'string') 159 | assert.strictEqual(key, 'rpc_buf') 160 | assert.deepStrictEqual(payload, { hello: 'world' }) 161 | 162 | assert.strictEqual(meta.isStream, true) 163 | 164 | handler.reply(null, { hello: 'helloworld' }) 165 | }) 166 | 167 | const req = peer.stream('rpc_buf', { 168 | timeout: 10000 169 | }) 170 | 171 | req 172 | .on('data', (data) => { 173 | const d = JSON.parse(data.toString()) 174 | assert.strictEqual(d[1], null) 175 | assert.deepStrictEqual(d[2], { hello: 'helloworld' }) 176 | stop() 177 | done() 178 | }) 179 | .on('end', () => {}) 180 | .on('error', (e) => { console.error(e) }) 181 | 182 | const writable = new PassThrough() 183 | writable.write('["UUID", "rpc_buf", { "hello": "world" }]') 184 | writable.end() 185 | writable.pipe(req) 186 | }).timeout(7000) 187 | 188 | it('buffered parsing works, extra headers', (done) => { 189 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 190 | assert.ok(typeof rid === 'string') 191 | assert.strictEqual(key, 'rpc_buf') 192 | assert.deepStrictEqual(payload, { hello: 'world' }) 193 | 194 | assert.strictEqual(meta.action, 'uploadPublic') 195 | assert.deepStrictEqual(meta.args, { foo: 'bar' }) 196 | assert.strictEqual(meta.isStream, true) 197 | 198 | handler.reply(null, { hello: 'helloworld' }) 199 | }) 200 | 201 | const req = peer.stream('rpc_buf', { 202 | timeout: 10000, 203 | headers: { _a: 'uploadPublic', _ar: { foo: 'bar' } } 204 | }) 205 | 206 | req 207 | .on('data', (data) => { 208 | const d = JSON.parse(data.toString()) 209 | assert.strictEqual(d[1], null) 210 | assert.deepStrictEqual(d[2], { hello: 'helloworld' }) 211 | stop() 212 | done() 213 | }) 214 | .on('end', () => {}) 215 | .on('error', (e) => { console.error(e) }) 216 | 217 | const writable = new PassThrough() 218 | writable.write('["UUID", "rpc_buf", { "hello": "world" }]') 219 | writable.end() 220 | writable.pipe(req) 221 | }).timeout(7000) 222 | 223 | it('buf/buf: serialized files work', (done) => { 224 | serviceBuf.on('request', (rid, key, payload, handler, cert, meta) => { 225 | assert.ok(typeof rid === 'string') 226 | assert.strictEqual(key, 'rpc_buf') 227 | 228 | assert.strictEqual(payload.action, 'uploadPublic') 229 | assert.strictEqual(payload.args[1].key, 'example-amazon-dynamo-sosp2007-4.pdf') 230 | assert.strictEqual(typeof payload.args[0], 'string') 231 | 232 | assert.strictEqual(meta.isStream, false) 233 | 234 | handler.reply(null, 'ok') 235 | }) 236 | 237 | const opts = { timeout: 100000 } 238 | const buf = fs.readFileSync( 239 | path.join(__dirname, './example-amazon-dynamo-sosp2007.pdf') 240 | ) 241 | 242 | const optsPdf = { 243 | key: 'example-amazon-dynamo-sosp2007-4.pdf', 244 | acl: 'public-read', 245 | bucket: 'bfx-dev-robert', 246 | contentType: 'application/pdf' 247 | } 248 | 249 | const queryUploadPublicPdf = { 250 | action: 'uploadPublic', 251 | args: [buf.toString('hex'), optsPdf] 252 | } 253 | peer.request('rpc_buf', queryUploadPublicPdf, opts, (err, result) => { 254 | if (err) throw err 255 | 256 | assert.deepStrictEqual(result, 'ok') 257 | 258 | stop() 259 | done() 260 | }) 261 | }).timeout(7000) 262 | }) 263 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Grenache](https://github.com/bitfinexcom/grenache) Node.JS HTTP implementation 2 | 3 | 4 | 5 | Grenache is a micro-framework for connecting microservices. Its simple and optimized for performance. 6 | 7 | Internally, Grenache uses Distributed Hash Tables (DHT, known from Bittorrent) for Peer to Peer connections. You can find more details how Grenche internally works at the [Main Project Homepage](https://github.com/bitfinexcom/grenache) 8 | 9 | - [Setup](#setup) 10 | - [Examples](#examples) 11 | - [API](#api) 12 | 13 | ## Setup 14 | 15 | ### Install 16 | ``` 17 | npm install --save grenache-nodejs-http 18 | ``` 19 | 20 | ### Other Requirements 21 | 22 | Install `Grenache Grape`: https://github.com/bitfinexcom/grenache-grape: 23 | 24 | ```bash 25 | npm i -g grenache-grape 26 | ``` 27 | 28 | ``` 29 | // Start 2 Grapes 30 | grape --dp 20001 --aph 30001 --bn '127.0.0.1:20002' 31 | grape --dp 20002 --aph 40001 --bn '127.0.0.1:20001' 32 | ``` 33 | 34 | ### Examples 35 | 36 | #### RPC Server / Client 37 | 38 | This RPC Server example announces a service called `rpc_test` 39 | on the overlay network. When a request from a client is received, 40 | it replies with `world`. It receives the payload `hello` from the 41 | client. 42 | 43 | The client sends `hello` and receives `world` from the server. 44 | 45 | Internally the DHT is asked for the IP of the server and then the 46 | request is done as Peer-to-Peer request via websockets. 47 | 48 | **Grape:** 49 | 50 | ```bash 51 | grape --dp 20001 --aph 30001 --bn '127.0.0.1:20002' 52 | grape --dp 20002 --aph 40001 --bn '127.0.0.1:20001' 53 | ``` 54 | 55 | **Server:** 56 | 57 | ```js 58 | const Link = require('grenache-nodejs-link') 59 | 60 | const link = new Link({ 61 | grape: 'http://127.0.0.1:30001' 62 | }) 63 | link.start() 64 | 65 | const peer = new PeerRPCServer(link, { 66 | timeout: 300000 67 | }) 68 | peer.init() 69 | 70 | const service = peer.transport('server') 71 | service.listen(_.random(1000) + 1024) 72 | 73 | setInterval(function () { 74 | link.announce('rpc_test', service.port, {}) 75 | }, 1000) 76 | 77 | service.on('request', (rid, key, payload, handler) => { 78 | console.log(payload) // hello 79 | handler.reply(null, 'world') 80 | }) 81 | ``` 82 | 83 | **Client:** 84 | 85 | ```js 86 | const Link = require('grenache-nodejs-link') 87 | 88 | const link = new Link({ 89 | grape: 'http://127.0.0.1:30001' 90 | }) 91 | link.start() 92 | 93 | const peer = new PeerRPCClient(link, {}) 94 | peer.init() 95 | 96 | peer.request('rpc_test', 'hello', { timeout: 10000 }, (err, data) => { 97 | if (err) { 98 | console.error(err) 99 | process.exit(-1) 100 | } 101 | console.log(data) // world 102 | }) 103 | ``` 104 | 105 | [Code Server](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_server.js) 106 | [Code Client](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_client.js) 107 | 108 | ## API 109 | 110 | ### Class: PeerRPCServer 111 | 112 | #### Event: 'stream' 113 | 114 | Always emitted as son as a request arrives. Emits the raw `req` and `res` streams 115 | of the request and some preparsed metadata. Used for streaming. If 116 | `disableBuffered` is set to `false`, the server will attempt to buffer after 117 | emitting the stream event. 118 | 119 | ```js 120 | serviceStr.on('stream', (req, res, meta, handler) => { 121 | console.log(meta) // meta.isStream === true 122 | 123 | const [rid, key] = meta.infoHeaders 124 | 125 | req.pipe(process.stdout) 126 | 127 | handler.reply(rid, null, 'world') // convenience reply 128 | }) 129 | 130 | ``` 131 | 132 | [Example](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_server_stream.js). 133 | 134 | 135 | 136 | #### Event: 'request' 137 | 138 | Emitted when a request from a RPC client is received. In the lifecycle of a 139 | request this happens after the server has parsed an buffered the whole data. 140 | When the server runs with `disableBuffered: true`, the event must emitted manually, 141 | if needed, or by calling the buffering request handlers manually. 142 | 143 | - `rid` unique request id 144 | - `key` name of the service 145 | - `payload` Payload sent by client 146 | - `handler` Handler object, used to reply to a client. 147 | 148 | ```js 149 | service.on('request', (rid, key, payload, handler) => { 150 | handler.reply(null, 'world') 151 | }) 152 | ``` 153 | 154 | #### new PeerRPCServer(link, [options]) 155 | 156 | - `link ` Instance of a [Link Class](#new-linkoptions) 157 | - `options ` 158 | - `disableBuffered ` Disable automatic buffering of the incoming request data stream. Useful for streaming. 159 | - `timeout ` Server-side socket timeout 160 | - `secure ` TLS options 161 | - `key ` 162 | - `cert ` 163 | - `ca ` 164 | - `requestCert ` 165 | - `rejectUnauthorized ` 166 | 167 | Creates a new instance of a `PeerRPCServer`, which connects to the DHT 168 | using the passed `link`. 169 | 170 | #### peer.init() 171 | 172 | Sets the peer active. Must get called before we get a transport 173 | to set up a server. 174 | 175 | #### peer.transport('server') 176 | 177 | Must get called after the peer is active. Sets peer into server- 178 | mode. 179 | 180 | #### peer.listen(port) 181 | 182 | Lets the `PeerRPCServer` listen on the desired `port`. The port is 183 | stored in the DHT. 184 | 185 | #### peer.port 186 | 187 | Port of the server (set by `listen(port)`). 188 | 189 | #### Example 190 | 191 | This RPC Server example announces a service called `rpc_test` 192 | on the overlay network. When a request from a client is received, 193 | it replies with `world`. It receives the payload `hello` from the 194 | client. 195 | 196 | The client sends `hello` and receives `world` from the server. 197 | 198 | Internally the DHT is asked for the IP of the server and then the 199 | request is done as Peer-to-Peer request via websockets. 200 | 201 | **Server:** 202 | 203 | ```js 204 | const Link = require('grenache-nodejs-link') 205 | 206 | const link = new Link({ 207 | grape: 'http://127.0.0.1:30001' 208 | }) 209 | link.start() 210 | 211 | const peer = new PeerRPCServer(link, {}) 212 | peer.init() 213 | 214 | const service = peer.transport('server') 215 | service.listen(_.random(1000) + 1024) 216 | 217 | setInterval(function () { 218 | link.announce('rpc_test', service.port, {}) 219 | }, 1000) 220 | 221 | service.on('request', (rid, key, payload, handler) => { 222 | console.log(payload) // hello 223 | handler.reply(null, 'world') 224 | }) 225 | ``` 226 | 227 | **Client:** 228 | 229 | ```js 230 | const Link = require('grenache-nodejs-link') 231 | 232 | const link = new Link({ 233 | grape: 'http://127.0.0.1:30001' 234 | }) 235 | link.start() 236 | 237 | const peer = new PeerRPCClient(link, {}) 238 | peer.init() 239 | 240 | peer.request('rpc_test', 'hello', { timeout: 10000 }, (err, data) => { 241 | if (err) { 242 | console.error(err) 243 | process.exit(-1) 244 | } 245 | console.log(data) // world 246 | }) 247 | ``` 248 | 249 | [Server](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_server.js) 250 | [Client](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_client.js) 251 | 252 | 253 | ### Class: PeerRPCClient 254 | 255 | #### new PeerRPCClient(link, [options]) 256 | 257 | - `link ` Instance of a [Link Class](#new-linkoptions) 258 | - `options ` 259 | - `maxActiveKeyDests ` 260 | - `maxActiveDestTransports ` 261 | - `secure ` TLS options 262 | - `key ` 263 | - `cert ` 264 | - `ca ` 265 | - `rejectUnauthorized ` 266 | 267 | 268 | Creates a new instance of a `PeerRPCClient`, which connects to the DHT 269 | using the passed `link`. 270 | 271 | A PeerRPCClient can communicate with multiple Servers and map work items over them. 272 | With `maxActiveKeyDests` you can limit the maximum amount of destinations. 273 | Additionally, you can limit the amount of transports with `maxActiveDestTransports`. 274 | 275 | #### peer.init() 276 | 277 | Sets the peer active. Must get called before we start to make requests. 278 | 279 | #### peer.map(name, payload, [options], callback) 280 | - `name ` Name of the service to address 281 | - `payload ` Payload to send 282 | - `options ` Options for the request 283 | - `timeout ` timeout in ms 284 | - `limit ` maximum requests per available worker 285 | - `callback ` 286 | 287 | Maps a number of requests over the amount of registered workers / PeerRPCServers. 288 | [Example](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_client_map.js). 289 | 290 | 291 | #### peer.request(name, payload, [options], callback) 292 | - `name ` Name of the service to address 293 | - `payload ` Payload to send 294 | - `options ` Options for the request 295 | - `timeout ` timeout in ms 296 | - `retry ` attempts to make before giving up. default is 1 297 | - `callback ` 298 | 299 | Sends a single request to a RPC server/worker. 300 | [Example](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_client.js). 301 | 302 | 303 | #### peer.stream(name, opts) 304 | - `name ` Name of the service to address 305 | - `options ` Options for the request 306 | - `timeout ` timeout in ms 307 | - `headers ` Headers to add to the request 308 | 309 | Looks a service up and returns a req-object which is a stream. 310 | Additional parameters (e.g. content-type), can be added via options. 311 | 312 | The default metadata values for the request id and key are automatically 313 | via header. 314 | 315 | [Example](https://github.com/bitfinexcom/grenache-nodejs-http/tree/master/examples/rpc_client_stream.js). 316 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------