A p2p Service Registry (🕳🥊 a holepunch project)
Features
- 📇 Add existing services (apis, dbs, etc) to the registry
- 💻 In just a few lines of code, node apis can be registered
- ⚖️ Failover and load balancing between multiple service instances
- 👩👧👧 run multiple versions of apis running, giving time to depreciate them
- 🥡 clients request versions of apis with wildcard matching
- 📚 clients keep local copies of a readable registry for fast lookups and replication between them
- 📱 an experimental web proxy to expose services to webapps
Devops in a p2p world is much easier. Come aboard! ⛴ 🚢
As your micro service architecture grows to span many processes on many machines just register your services and easily find and connect them together.
npm i -g hyperseaport
Your registry is unique! The seed ensures a unique and consistent public key. Only those who know the public key can connect and uses the services the registry maintains.
$ hyperseaport seed > ~/.config/hyperseaport
If you inspect the file ~/.config/hyperseaport
it will look like
registrySeed=c84c7034a0309479299d81468b7bc59592a96b3a919fd1ff159aea1879407382
registryPublicKey=5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
The registrySeed should be secret! It's what makes your registry your own. The registryPublicKey should be shared so services and consumers can connect and use.
$ hyperseaport --web 8777
⦖ ./cli.js --web 8777
Writer started.
storing in /Users/ryanr/Library/Preferences/hyperseaport
Registry started.
registryPublicKey=5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
web server listening on port 8777
The registry is stored as a hyperbee in /Users/ryanr/Library/Prefrences/hyperseaport.
We've started with the experimental web proxy on port 8777. We'll show that off later.
Here is an example that registers and existing couchdb running on localhost port (-p) 5984 as a service. The role (-r) of the service a semver string that represents the name and version of the running instace. We use the registry publicKey from step 2 to find and connect to the registry, and register our service.
The code below can be run on the same different host than step 2, and it automagically connects and registers the service.
$ hyperseaport service --port 5984 --role [email protected] --registryPublicKey 5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
started p2p service on abe213285052e5c2f2166d144afcd71e31aa5c7d72656d7b956a2c93f76d260f
connecting to registry 5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
registered service {
role: 'couchdb',
version: '3.2.2',
_tags: {},
tags: [],
hash: 'couchdb|3.2.2|'
}
Note: for service registration the server version should be exact
Assuming the service you registered in 3 is an http service, you can now use the registry proxy (if enabled).
Try some urls like
- curl localhost:8777/role/[email protected]/
- curl localhost:8777/role/[email protected]/some/path/on/the/service
- curl localhost:8777/list/[email protected]
- curl localhost:877/instance/abe213285052e5c2f2166d144afcd71e31aa5c7d72656d7b956a2c93f76d260f/some/path/on/this/service
/role/${role@version}/path/on/service
This could be a one stop shop for your mobile apps. We will add more features to it (auth, etc) so it is experimental.
If you dont want to start the web proxy with the registry, or you want more redundand web proxies (round robin dns), you can start them up
$ hyperseaport web --port 5984 --registryPublicKey 5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
This is the part that is like ngrok, or a reverse proxy. You want to USE the service somewhere else, without knowing the IP, or vpn. So you start a hyperseaport proxy on a port that makes it look like the service is running locally on that port.
This can be done on the same or different host then the steps above.
Lookup the service by a role (r) and expose the service locally on a totally different port (p). We use the registry publicKey from step 2 to find and connect to the registry, and find the service.
$ hyperseaport proxy --port 5985 --role [email protected] --registryPublicKey 5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
connecting to registry 5b64a8956d8f2404c4f4b4e6f402ef439f610f7fe297718093641359130b0d45
connected to registry
found service {
id: 'abe213285052e5c2f2166d144afcd71e31aa5c7d72656d7b956a2c93f76d260f',
remotePublicKey: '03008865d93c046129d65b8d68829e5a4757b3b24844bbd1d36d27fe1e683b59',
meta: {
role: 'couchdb',
version: '3.2.2',
_tags: {},
tags: [],
hash: 'couchdb|3.2.2|'
}
}
proxy from 5985 to p2p service abe213285052e5c2f2166d144afcd71e31aa5c7d72656d7b956a2c93f76d260f
Note: for service discovery, the version can be a semver range.
Now port 5985 is proxied to the remote service without knowing where it is. Magic. Call it normally
$ curl http://localhost:5985
{"couchdb":"Welcome","version":"3.2.2","git_sha":"d5b746b7c","uuid":"5e3ccc9fd986f473f182ce246c1e214c","features":["access-ready","partitioned","pluggable-storage-engines","reshard","scheduler"],"vendor":{"name":"The Apache Software Foundation"}}
const {Service, KeyPair} = require('hypersearport')
// Boilerplate node
const http = require('http')
const port = 8992
const requestListener = function (req, res) {
res.writeHead(200);
res.end('Hello, World!')
}
const server = http.createServer(requestListener)
server.listen(port)
// The intersting stuff!!
const registryPublicKey = process.argv[2] // we need one thing from the cli
console.log(registryPublicKey)
const role = '[email protected]'
const keyPair = KeyPair()
const service = Service({ registryPublicKey, role, port, keyPair })
await service.setup()
console.log('service connected')
process.once('SIGINT', function () {
service.destroy()
})
Consume the service from another process
import { Consumer } from 'hyperseaport'
const registryPublicKey = process.argv[2] // we need one thing from the cli
const roles = ['[email protected]']
const options = { registryPublicKey }
// notice we could pass in a bunch of roles we need in the array
const consumer = Consumer(roles, options)
const [helloworld] = await consumer.setup()
const resp = await fetch(helloworld.url)
const data = await resp.text()
console.log(data) // 'ollama is running'
// clean up when done
consumer.destroy().then(() => process.exit())
See more runnable examples.