Skip to content

Commit

Permalink
chore(example): http server example
Browse files Browse the repository at this point in the history
Adds an example project in ./examples that demonstrates hosting an
instance of Hold This as a HTTP server.
  • Loading branch information
gregdaynes committed May 24, 2024
1 parent 5691d1f commit a374f67
Show file tree
Hide file tree
Showing 6 changed files with 679 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ It is designed to be used in a single-threaded synchronous environment.
[Examples](#examples)
- [No Config](#no-config-setup)
- [File Backed](#file-backed-store)
- [HTTP Server](#http-server)
- [WebSocket Server](#websocket-server)
- [Data Serialization](#data-serialization)
- [TTL / Expiring Records](#ttl-%2F-expiring-records)
Expand Down Expand Up @@ -103,6 +104,42 @@ const holder = holder({ location: './holder.sqlite', enableWAL: false })
_Performed on Macbook Pro M1 with 16 GB Memory_


### HTTP Server

Because Hold This is based on SQLite3, it does not support a native network connection.
We can achieve a networked, multi-connection instance by wrapping Hold This in a HTTP Server.

>[!CAUTION]
>This is not a production ready example. Security, and failure modes must be considered, but are outside the scope of the example.
```js
import { createServer } from 'node:http'
import Hold from 'hold-this'

const server = createServer()
server.holder = Hold()

server.on('request', (req, res) => {
let body = ''
req.on('data', (chunk) => { body += chunk })

req.on('end', () => {
const parsedBody = JSON.parse(body)

const { cmd, topic, key, value, options } = parsedBody
const data = server.holder[cmd](topic, key, value, options)

res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(data))
})
})

server.listen(3000)
```

_The complete example including client and benchmarks can be found in [examples/http](examples/http)_


### WebSocket Server

Because Hold This is based on SQLite3, it does not support a native network connection.
Expand Down
28 changes: 28 additions & 0 deletions examples/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Hold This Example: HTTP
=======================

This is an example application that runs Hold This behind an HTTP server.

>[!CAUTION]
>This is not a production ready example. Security, and failure modes must be considered, but are outside the scope of the example.
>[!NOTE]
>This example depends on the package `autocannon` to benchmark the server.
>
>Requires Node 22.0.0
>- import.meta.filename
Getting Started
---------------

1. Install dependency

npm install

2. Run Example

npm start

3. Run Benchmark

npm run test:bench
27 changes: 27 additions & 0 deletions examples/http/bench.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { stdout } from 'node:process'
import autocannon from 'autocannon'
import { Server } from './index.js'

const server = Server()

server.holder.set('http', 'foo', 'bar')

autocannon({
url: 'http://localhost:3000',
connections: 10,
pipelining: 10,
duration: 10,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
cmd: 'get', topic: 'http', key: 'test'
})

}, (err, result) => {
if (err) throw err

stdout.write(autocannon.printResult(result))
server.close()
})
78 changes: 78 additions & 0 deletions examples/http/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { createServer } from 'node:http'
import Hold from 'hold-this'

/**
* Creates a HTTP server decorated with an instance of the data store
*
* @param {Object} options - server options
* @param {number} options.port - port number to listen on
* @returns {http.Server}
* @example
* const server = Server()
*/
export function Server ({ port = 3000 }) {
const server = createServer()
server.holder = Hold()

server.on('request', (req, res) => {
if (req.method !== 'POST') {
res.writeHead(405, { 'Content-Type': 'application/json' })
res.end('Method Not Allowed\n')
return false
}

if (req.headers['content-type'] !== 'application/json') {
res.writeHead(415, { 'Content-Type': 'application/json' })
res.end('Unsupported Media Type\n')
return false
}

let body = ''
req.on('data', (chunk) => { body += chunk })

req.on('end', () => {
const parsedBody = JSON.parse(body)

const { cmd, topic, key, value, options } = parsedBody
const data = server.holder[cmd](topic, key, value, options)

res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(data))
})
})

server.listen(port)

return server
}

// When this file is run directly from node (main module)
// setup a server, and run a test demonstrating using fetch to set and
// get data from the server
if (process.argv[1] === import.meta.filename) {
Server()

// Write the record that we'll be fetching
await fetch('http://localhost:3000', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
cmd: 'set', topic: 'http', key: 'test', value: 'test1'
})
})

// Fetch the record previously set
const response = await fetch('http://localhost:3000', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
cmd: 'get', topic: 'http', key: 'test'
})
}).then(response => response.json())

console.log(response)
}
Loading

0 comments on commit a374f67

Please sign in to comment.