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()