From e249c9ba2162d836114ab8565c0ac13b1f1122fa Mon Sep 17 00:00:00 2001 From: Robert Williams Date: Tue, 30 Mar 2021 20:34:02 +0000 Subject: [PATCH] initial commit --- README.md | 4 ++ lib/http-server.js | 93 ++++++++++++++++++++++++++++++++++++++++++ server.js | 32 +++++++++++++++ setup/kvs_nginx | 19 +++++++++ setup/kvs_service | 15 +++++++ setup/setup_freebsd.sh | 16 ++++++++ 6 files changed, 179 insertions(+) create mode 100644 README.md create mode 100644 lib/http-server.js create mode 100755 server.js create mode 100644 setup/kvs_nginx create mode 100755 setup/kvs_service create mode 100755 setup/setup_freebsd.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..4140cfc --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Key/Value Store + +* To store a value POST to the server at /[key] with the data in the body +* To retrieve a value GET from the server at /[key] and the data will be returned in the body diff --git a/lib/http-server.js b/lib/http-server.js new file mode 100644 index 0000000..2f7ccf2 --- /dev/null +++ b/lib/http-server.js @@ -0,0 +1,93 @@ +/* + * How to use: + * + * const server = require('./http_server.js'); + * + * "path" is an array of path components + * e.g. "/hello/world" -> ['hello', 'world'] + * + * "query" is an object of query keys/values + * + * "body" is the request body in bytes + * + * server.get(({path, query, body}, {send, redirect}) => send(data, statusCode, contentType)); + * server.post(({path, query, body}, {send, redirect}) => redirect('/')); + * + * server.listen(port); + */ + +const http = require('http'); + +const methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']; +const handlers = {}; + +const server = http.createServer((req, res) => { + let {path, query} = parseUrl(req.url); + + let send = (data, statusCode, contentType) => { + res.statusCode = statusCode || 200; + res.setHeader('Content-Type', contentType || 'text/plain'); + res.end(data); + }; + + let redirect = (url) => { + res.statusCode = 303; + res.setHeader('Location', url); + res.end(); + }; + + getBody(req, (body) => { + try { + let handler = handlers[req.method]; + if (handler) { + handler({path, query, body}, {send, redirect}); + } else { + send('Method Not Allowed', 405); + } + } catch (e) { + console.log(e); + send('Internal server error', 500); + } + }); +}); + +const export_obj = { + listen: (port) => server.listen(port, '127.0.0.1', + () => console.log(`HTTP server listening on port ${port}`)), + getContentType: (filepath) => { + if (filepath.includes('.html')) return 'text/html'; + if (filepath.includes('.css')) return 'text/css'; + if (filepath.includes('.js')) return 'application/javascript'; + return 'text/plain'; + }, +}; + +for (let method of methods) { + export_obj[method.toLowerCase()] = (handler) => handlers[method] = handler; +} + +module.exports = export_obj; + +function parseUrl(url) { + let [pathStr, queryStr] = url.split('?', 2); + + let query = {}; + if (queryStr) queryStr + .split('&') + .map(x => x.split('=')) + .map(x => x.map(y => decodeURIComponent(y))) + .map(([x, y]) => query[x] = y); + + let path = pathStr + .replace(/\.\./g, '') // remove upward relative paths + .split('/') + .filter(x => x); + + return {path, query}; +} + +function getBody(req, callback) { + let body = []; + req.on('data', (chunk) => body.push(chunk)); + req.on('end', () => callback(Buffer.concat(body))); +} diff --git a/server.js b/server.js new file mode 100755 index 0000000..d78a86c --- /dev/null +++ b/server.js @@ -0,0 +1,32 @@ +#!/usr/local/bin/node + +const server = require('./lib/http-server.js'); + +const PORT = 24673; + +let store = new Map(); + +server.get(({path}, {send}) => { + if (!path.length) + return send('Bad Request -- No key specified', 400, 'text/plain'); + + let key = path[0]; + + if (store.has(key)) { + return send(store.get(key), 200, 'text/plain'); + } else { + return send('Bad Request -- Key not found', 404, 'text/plain'); + } +}); + +server.post(({path, body}, {send}) => { + if (!path.length) + return send('Bad Request -- No key specified', 400, 'text/plain'); + + let key = path[0]; + + store.set(key, String.fromCharCode(...body)); + return send('OK -- Value saved', 200, 'text/plain'); +}); + +server.listen(PORT); diff --git a/setup/kvs_nginx b/setup/kvs_nginx new file mode 100644 index 0000000..6cd40af --- /dev/null +++ b/setup/kvs_nginx @@ -0,0 +1,19 @@ +server { + listen 80; + server_name kvs.rjava.net; + + location / { + proxy_pass http://localhost:24673; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + # Enables WS support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_redirect off; + } +} diff --git a/setup/kvs_service b/setup/kvs_service new file mode 100755 index 0000000..e52bf9c --- /dev/null +++ b/setup/kvs_service @@ -0,0 +1,15 @@ +#!/bin/sh +# + +# PROVIDE: kvs + +. /etc/rc.subr + +name="kvs" +rcvar="${name}_enable" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/daemon" +command_args="-c -f -P ${pidfile} -r /home/robert/kvs/server.js" + +load_rc_config $name +run_rc_command "$1" diff --git a/setup/setup_freebsd.sh b/setup/setup_freebsd.sh new file mode 100755 index 0000000..4110c38 --- /dev/null +++ b/setup/setup_freebsd.sh @@ -0,0 +1,16 @@ +#/bin/sh + +# Requires root privileges + +cd /home/robert/kvs + +cp ./setup/kvs_service /usr/local/etc/rc.d/kvs + +printf '\nkvs_enable="YES"\n' >> /etc/rc.conf + +mkdir -p /usr/local/etc/nginx/sites-enabled + +cp ./setup/kvs_nginx /usr/local/etc/nginx/sites-enabled/kvs + +service kvs start +service nginx restart -- 2.45.2