clortho

A very simple key/value server
git clone https://www.brianlane.com/git/clortho
Log | Files | Refs | README | LICENSE

commit c8ff26cbab05d6fb86ab3925773bdb3ba2bfb860
Author: Brian C. Lane <bcl@redhat.com>
Date:   Fri, 26 Dec 2014 15:15:28 -0800

Simple key server

Diffstat:
Asrc/clortho.py | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 110 insertions(+), 0 deletions(-)

diff --git a/src/clortho.py b/src/clortho.py @@ -0,0 +1,110 @@ +import os +import asyncio +import signal +import argparse +import pickle +from aiohttp import web + +args = None +keystore = {} + +@asyncio.coroutine +def get_key(request): + key = request.match_info.get('key') + + client = None + if "X-Forwarded-For" in request.headers: + client = request.headers["X-Forwarded-For"].split(",")[0] + else: + peername = request.transport.get_extra_info('peername') + if peername is not None: + client, _port = peername + + if client and client in keystore and key in keystore[client]: + text = keystore[client][key] + status = 200 + else: + text = "%s doesn't exist for %s" % (key, client) + status = 404 + return web.Response(body=text.encode('utf-8'), status=status) + +@asyncio.coroutine +def set_key(request): + key = request.match_info.get('key') + post_data = yield from request.post() + + client = None + if "X-Forwarded-For" in request.headers: + client = request.headers["X-Forwarded-For"].split(",")[0] + else: + peername = request.transport.get_extra_info('peername') + if peername is not None: + client, _port = peername + + if client and key and "value" in post_data: + if client not in keystore: + keystore[client] = {} + if post_data["value"]: + keystore[client][key] = post_data["value"] + else: + del keystore[client][key] + text = "OK" + status = 200 + else: + text = "ERROR" + status = 404 + + return web.Response(body=text.encode('utf-8'), status=status) + +@asyncio.coroutine +def init(loop, host, port): + app = web.Application(loop=loop) + app.router.add_route('GET', '/{key}', get_key) + app.router.add_route('POST', '/{key}', set_key) + + srv = yield from loop.create_server(app.make_handler(), host, port) + print("Server started at http://%s:%s" % (host, port)) + return srv + +@asyncio.coroutine +def clean_exit(signame): + print("got signal %s, exiting" % signame) + save_keystore(args.keystore) + + loop = asyncio.get_event_loop() + loop.stop() + +def setup_parser(): + parser = argparse.ArgumentParser(description="Clortho key server") + parser.add_argument("--host", default="127.0.0.1", help="Hostname or IP address to bind to") + parser.add_argument("--port", default="9001", help="Port number to listen to") + parser.add_argument("--keystore", default="clortho.dat", help="File to store keys in") + + return parser + +def read_keystore(filename): + if not os.path.exists(filename): + return + + global keystore + with open(filename, "rb") as f: + try: + keystore = pickle.load(f) + except EOFError: + keystore = {} + +def save_keystore(filename): + #TODO: Write to a tempfile first, rename to target + with open(filename, "wb") as f: + pickle.dump(keystore, f, pickle.HIGHEST_PROTOCOL) + +if __name__=='__main__': + parser = setup_parser() + args = parser.parse_args() + read_keystore(args.keystore) + + loop = asyncio.get_event_loop() + for signame in ('SIGINT', 'SIGTERM'): + loop.add_signal_handler(getattr(signal, signame), asyncio.async, clean_exit(signame)) + loop.run_until_complete(init(loop, args.host, int(args.port))) + loop.run_forever()