Mercurial > sjsbox
changeset 0:ab0b7b80832a
origination
author | Atul Varma <avarma@mozilla.com> |
---|---|
date | Mon, 31 May 2010 04:49:41 -0700 |
parents | |
children | c5d1a2d60e45 |
files | boxes/foo.js example.py sjsbox/__init__.py sjsbox/box.py sjsbox/js.py sjsbox/server.py |
diffstat | 5 files changed, 199 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/boxes/foo.js Mon May 31 04:49:41 2010 -0700 @@ -0,0 +1,7 @@ +var i = 0; + +function handle(request) { + if (request.path == '/boop') + return {'hi': 1}; + return {'no': i++}; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/example.py Mon May 31 04:49:41 2010 -0700 @@ -0,0 +1,14 @@ +import os +import logging +from wsgiref.simple_server import make_server + +import sjsbox.server + +if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG) + boxes = sjsbox.server.Boxes(os.path.abspath('./boxes')) + app = sjsbox.server.App(boxes) + port = 8000 + httpd = make_server('', port, app) + print "serving on port %d" % port + httpd.serve_forever()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sjsbox/box.py Mon May 31 04:49:41 2010 -0700 @@ -0,0 +1,63 @@ +import logging +import multiprocessing as mproc + +class BoxChild(object): + def __init__(self, filename, fullpath, pipe): + if filename.endswith('.js'): + import sjsbox.js + self.__impl = sjsbox.js.JsBox(open(fullpath).read(), + fullpath) + else: + raise ValueError('unknown box type: %s' % filename) + self.pipe = pipe + + def run(self): + while True: + cmd, args = self.pipe.recv() + if cmd == 'shutdown': + self.__impl.shutdown() + return + elif cmd == 'handle': + self.pipe.send(self.__impl.handle(*args)) + + @classmethod + def start(klass, *args, **kwargs): + obj = klass(*args, **kwargs) + obj.run() + +class BoxParent(object): + TIMEOUT = 3.0 + + def __init__(self, filename, fullpath): + self.filename = filename + self.fullpath = fullpath + self.child = None + self.child_pipe = None + self.restart() + + def restart(self): + if self.child: + self.shutdown() + self.child_pipe, pipe = mproc.Pipe() + kwargs = dict(filename=self.filename, + fullpath=self.fullpath, + pipe=pipe) + self.child = mproc.Process(target=BoxChild.start, + kwargs=kwargs) + self.child.start() + + def handle(self, method, path): + self.child_pipe.send(('handle', (method, path))) + return self.child_pipe.recv() + + def shutdown(self): + self.child_pipe.send(('shutdown', None)) + self.child.join(self.TIMEOUT) + if self.child.is_alive(): + logging.warn('terminating child process: %s' % self.filename) + self.child_pipe.close() + self.child.terminate() + else: + logging.info('child process shut down: %s' % self.filename) + self.child_pipe = None + self.child = None
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sjsbox/js.py Mon May 31 04:49:41 2010 -0700 @@ -0,0 +1,33 @@ +from StringIO import StringIO + +try: + import json +except ImportError: + import simplejson as json + +import pydermonkey +from pydermonkey import ScriptError +import pydertron +from pydertron import jsexposed, JsExposedObject + +class JsBox(object): + def __init__(self, code, filename): + self.sandbox = pydertron.JsSandbox(pydertron.NullFileSystem()) + self.sandbox.set_globals() + self.stderr = StringIO() + retval = self.sandbox.run_script(code, filename=filename, + stderr=self.stderr) + if retval: + print self.stderr.getvalue() + + def handle(self, method, path): + request = json.dumps(dict(method=method, path=path)) + request = self.sandbox.root.JSON.parse(request) + result = self.sandbox.root.handle(request) + if isinstance(result, pydertron.SafeJsObjectWrapper): + result = self.sandbox.root.JSON.stringify(result) + return json.loads(result) + return result + + def shutdown(self): + self.sandbox.finish()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sjsbox/server.py Mon May 31 04:49:41 2010 -0700 @@ -0,0 +1,82 @@ +import os +import logging + +try: + import json +except ImportError: + import simplejson as json + +from sjsbox.box import BoxParent + +def is_readable(path): + try: + os.stat(path) + except Exception: + return False + return True + +class Boxes(object): + def __init__(self, rootdir): + self.rootdir = rootdir + self.boxes = {} + self.box_mtimes = {} + self.update() + + def update(self): + visited = {} + for filename in os.listdir(self.rootdir): + fullpath = os.path.join(self.rootdir, filename) + boxname = os.path.splitext(filename)[0] + if boxname not in self.boxes: + if not is_readable(fullpath): + continue + logging.info('creating box %s' % boxname) + self.box_mtimes[boxname] = os.stat(fullpath).st_mtime + self.boxes[boxname] = BoxParent(filename, fullpath) + else: + box_mtime = os.stat(fullpath).st_mtime + if box_mtime > self.box_mtimes[boxname]: + self.box_mtimes[boxname] = box_mtime + self.boxes[boxname].restart() + logging.info('updated box %s' % boxname) + visited[boxname] = True + to_destroy = [boxname for boxname in self.boxes + if boxname not in visited] + for boxname in to_destroy: + logging.info('destroying box %s' % boxname) + self.boxes[boxname].shutdown() + del self.boxes[boxname] + + def __contains__(self, name): + return name in self.boxes + + def __getitem__(self, name): + return self.boxes[name] + +class App(object): + JSON_TYPE = 'text/plain' + + def __init__(self, boxes): + self.boxes = boxes + + def __call__(self, environ, start_response): + self.boxes.update() + + def error_404(): + start_response('404 Not Found', + [('Content-Type', 'text/plain')]) + return ['Not Found: %s' % environ['PATH_INFO']] + + path_parts = environ['PATH_INFO'].split('/')[1:] + if path_parts: + boxname = path_parts[0] + if boxname in self.boxes: + kwargs = dict(method=environ['REQUEST_METHOD'], + path='/%s' % '/'.join(path_parts[1:])) + retval = self.boxes[boxname].handle(**kwargs) + if retval == 404: + return error_404() + start_response('200 OK', + [('Content-Type', self.JSON_TYPE)]) + return [json.dumps(retval)] + return error_404()