~increscent/kvs

e249c9ba2162d836114ab8565c0ac13b1f1122fa — Robert Williams 3 years ago
initial commit
6 files changed, 179 insertions(+), 0 deletions(-)

A README.md
A lib/http-server.js
A server.js
A setup/kvs_nginx
A setup/kvs_service
A setup/setup_freebsd.sh
A  => README.md +4 -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

A  => lib/http-server.js +93 -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)));
}

A  => server.js +32 -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);

A  => setup/kvs_nginx +19 -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;
    }
}

A  => setup/kvs_service +15 -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"

A  => setup/setup_freebsd.sh +16 -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