Skip to content

Instantly share code, notes, and snippets.

@tj
Last active October 4, 2021 19:38
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tj/d5f76c598e354845249621ad295af5f7 to your computer and use it in GitHub Desktop.
Save tj/d5f76c598e354845249621ad295af5f7 to your computer and use it in GitHub Desktop.
const http = require('http')
const rpc = require('./rpc')
/**
* Pets service.
*/
class Pets {
constructor(db = []) {
this.db = db // pretend it's a database :)
}
async add_pet({ name, species }) {
// pretend to do some validation here blah blah
this.db.push({
name,
species
})
}
async remove_pet({ name }) {
this.db = this.db.filter(pet => pet.name != name)
}
async get_pets() {
return this.db
}
}
const pets = new Pets([
{ name: 'Tobi', species: 'Ferret' },
{ name: 'Loki', species: 'Ferret' },
{ name: 'Jane', species: 'Ferret' }
])
console.log('starting on port 3000')
http.createServer(rpc(pets)).listen(3000, _ => console.log('listening for requests'))

Add a pet:

$ curl -H "Content-Type: application/json" -d '{ "name": "Manny", "species": "Cat" }' http://localhost:3000/add_pet

List pets

$ curl http://localhost:3000/get_pets
[
  {
    "name": "Tobi",
    "species": "Ferret"
  },
  {
    "name": "Loki",
    "species": "Ferret"
  },
  {
    "name": "Jane",
    "species": "Ferret"
  },
  {
    "name": "Manny",
    "species": "Cat"
  }
]

Remove a bunch:

$ curl -H "Content-Type: application/json" -d '{ "name": "Tobi" }' http://localhost:3000/remove_pet
$ curl -H "Content-Type: application/json" -d '{ "name": "Loki" }' http://localhost:3000/remove_pet
$ curl -H "Content-Type: application/json" -d '{ "name": "Jane" }' http://localhost:3000/remove_pet

List again:

$ curl -H "Content-Type: application/json" -d '{ "name": "Manny", "species": "Cat" }' http://localhost:3000/add_pet
[
  {
    "name": "Manny",
    "species": "Cat"
  }
]

Invalid method:

$ curl http://localhost:3000/hello
Method is not defined on the service, must be one of: add_pet, remove_pet, get_pets
// real thing would need more validation, some try/catching etc
module.exports = function rpc(service) {
return async (req, res) => {
const method = req.url.slice(1)
// no method
if (typeof service[method] != 'function') {
res.statusCode = 400
res.end(`Method is not defined on the service, must be one of: ${methods(service).join(', ')}`)
return
}
// invoke method
const output = await invoke(req, service, method)
// no output
if (output == null) {
res.statusCode = 204
res.end()
return
}
// respond with json output
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify(output, null, 2))
}
}
/**
* invoke a method with request body when present.
*/
async function invoke(req, service, method) {
// no getHeader()? haha... consistency :D
if (req.method == 'POST' && req.headers['content-type'] == 'application/json') {
const body = await read(req)
const input = JSON.parse(body.toString())
return await service[method](input)
}
return await service[method]()
}
/**
* methods returns the methods provided by service.
*/
function methods(service) {
return Object.getOwnPropertyNames(Object.getPrototypeOf(service))
.filter(name => name != 'constructor')
}
/**
* read returns the contents of the stream,
* not a real implementation but hey.
*/
function read(stream) {
// not really real thing but whatever
const chunks = []
return new Promise((resolve, reject) => {
stream.on('data', c => chunks.push(c))
stream.on('end', _ => resolve(Buffer.concat(chunks)))
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment