├── CHANGES.txt ├── LICENSE.txt ├── README.md ├── dn ├── dn-env.sh ├── dnlib ├── PyInline │ ├── C.py │ ├── C.pyc │ ├── __init__.cover │ ├── __init__.py │ ├── __init__.pyc │ ├── c_util.py │ └── c_util.pyc ├── __init__.py ├── jsapi.py ├── jshandler.py ├── jslogging.py ├── jsroute.py └── require.py ├── dnshell.py ├── dreadnought.py ├── favicon.ico ├── install.sh ├── requirements.txt ├── setup.py └── test ├── require_test.js └── trivial.c /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 2 | 0.1 alpha quality code, tested on my machine against basic 3 | usage cases. 4 | 5 | 0.1.1 Bug fixes, fixing the incomplete setup.py ect. 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The content listed here at https://github.com/dschere/dreadnought-js is 2 | released under the MIT License. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Please follow this link for documentation, build and install instructions: 3 | 4 | https://sites.google.com/site/dreadnoughtjs/ 5 | 6 | -------------------------------------------------------------------------------- /dn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # setup environment 4 | source dn-env.sh 5 | 6 | # execute file 7 | exec dreadnought.py $* 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /dn-env.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p egg-cache 4 | chmod 744 egg-cache/ 5 | 6 | export PYTHON_EGG_CACHE=$PWD/egg-cache 7 | export PYTHON_PATH=$PWD:$PYTHONPATH:. 8 | 9 | -------------------------------------------------------------------------------- /dnlib/PyInline/C.py: -------------------------------------------------------------------------------- 1 | # PyInline C Module 2 | # Copyright (c)2001 Ken Simpson. All Rights Reserved. 3 | 4 | 5 | class BuildError( RuntimeError ): 6 | pass 7 | 8 | import c_util 9 | import os, string, re 10 | 11 | from distutils.core import setup, Extension 12 | 13 | def log(message): 14 | print message 15 | 16 | class Builder: 17 | def __init__(self, **options): 18 | self._verifyOptions(options) 19 | self._options = options 20 | self._initDigest() 21 | self._initBuildNames() 22 | self._methods = [] 23 | 24 | def _verifyOptions(self, options): 25 | pass 26 | 27 | def _initDigest(self): 28 | import md5, os, sys 29 | digester = md5.new() 30 | digester.update(self._options.get('code')) 31 | self._digest = digester.hexdigest() 32 | 33 | def _initBuildNames(self): 34 | self._moduleName = "_PyInline_%s" % self._digest 35 | self._buildDir = self._moduleName 36 | self._srcFileName = "%s.c" % self._moduleName 37 | self._moduleVersion = "1.0" 38 | self._homeDir = os.getcwd() 39 | 40 | def build(self): 41 | "Build a chunk of C source code." 42 | self._parse() 43 | 44 | try: 45 | return self._import() 46 | except ImportError: 47 | self._writeModule() 48 | self._compile() 49 | 50 | try: 51 | return self._import() 52 | except ImportError: 53 | raise BuildError("Build failed") 54 | 55 | def _import(self): 56 | "Import the new extension module into our client's namespace" 57 | from distutils.util import get_platform 58 | import sys, os 59 | 60 | # Add the module's lib directory to the Python path. 61 | plat_specifier = ".%s-%s" % (get_platform(), sys.version[0:3]) 62 | build_platlib = os.path.join(self._buildDir, 63 | 'build', 64 | 'lib' + plat_specifier) 65 | sys.path.append(build_platlib) 66 | 67 | # Load the module. 68 | import imp 69 | fp, pathname, description = imp.find_module(self._moduleName) 70 | 71 | try: 72 | module = imp.load_module(self._moduleName, fp, 73 | pathname, description) 74 | finally: 75 | # Since we may exit via an exception, close fp explicitly. 76 | if fp: 77 | fp.close() 78 | 79 | if self._options.has_key('targetmodule'): 80 | # Load each of the module's methods into the caller's 81 | # global namespace. 82 | setattr(self._options.get('targetmodule'), self._moduleName, module) 83 | for method in self._methods: 84 | setattr(self._options.get('targetmodule'), method['name'], 85 | getattr(module, method['name'])) 86 | 87 | return module 88 | 89 | def _parse(self): 90 | code = c_util.preProcess(self._options.get('code')) 91 | 92 | defs = c_util.findFunctionDefs(code) 93 | for d in defs: 94 | d['params'] = self._parseParams(d['rawparams']) 95 | self._methods.append(d) 96 | 97 | _commaSpace = re.compile(",\s*") 98 | _space = re.compile("\s+") 99 | _spaceStars = re.compile("(?:\s*\*\s*)+") 100 | _void = re.compile("\s*void\s*") 101 | _blank = re.compile("\s+") 102 | 103 | def _parseParams(self, params): 104 | "Return a tuple of tuples describing a list of function params" 105 | import re, string 106 | rawparams = self._commaSpace.split(params) 107 | if self._void.match(params) or\ 108 | self._blank.match(params) or\ 109 | params == '': 110 | return [] 111 | 112 | return [self._parseParam(p) for p in rawparams] 113 | 114 | def _parseParam(self, p): 115 | param = {} 116 | 117 | # Grab the parameter name and its type. 118 | m = c_util.c_pandm.match(p) 119 | if not m: 120 | raise BuildError("Error parsing parameter %s" % p) 121 | 122 | type = self._parseType(m.group(1)) 123 | param['type'] = type['text'] 124 | param['const'] = type['const'] 125 | param['pointers'] = type['pointers'] 126 | param['name'] = m.group(2) 127 | 128 | return param 129 | 130 | def _parseType(self, typeString): 131 | type = {} 132 | # Remove const from the type. 133 | if c_util.const.search(typeString): 134 | typeString = c_util.const.sub(" ", typeString) 135 | type['const'] = 1 136 | else: 137 | type['const'] = 0 138 | 139 | # Reformat asterisks in the type. 140 | type['pointers'] = typeString.count('*') 141 | type['text'] = c_util.trimWhite(c_util.star.sub("", typeString) +\ 142 | ("*" * type['pointers'])) 143 | 144 | return type 145 | 146 | def _makeBuildDirectory(self): 147 | try: 148 | os.mkdir(self._buildDir) 149 | except OSError, e: 150 | # Maybe the build directory already exists? 151 | log("Couldn't create build directory %s" % self._buildDir) 152 | 153 | def _writeModule(self): 154 | self._makeBuildDirectory() 155 | try: 156 | srcFile = open(os.path.join(self._buildDir, self._srcFileName), 157 | "w") 158 | except IOError, e: 159 | raise BuildError("Couldn't open source file for writing: %s" % e) 160 | 161 | import time 162 | srcFile.write("// Generated by PyInline\n") 163 | srcFile.write("// At %s\n\n" %\ 164 | time.asctime(time.localtime(time.time()))) 165 | srcFile.write('#include "Python.h"\n\n') 166 | 167 | # First, write out the user's code. 168 | srcFile.write("/* User Code */\n") 169 | srcFile.write(self._options.get('code')) 170 | srcFile.write("\n\n") 171 | 172 | # Then add in marshalling methods. 173 | for method in self._methods: 174 | srcFile.write("static PyObject *\n") 175 | method['hashname'] = "_%s_%s" % (self._digest, method['name']) 176 | srcFile.write("%s(PyObject *self, PyObject *args)\n" %\ 177 | method['hashname']) 178 | self._writeMethodBody(srcFile, method) 179 | 180 | # Finally, write out the method table. 181 | moduleMethods = "%s_Methods" % self._moduleName 182 | srcFile.write("static PyMethodDef %s[] = {\n " %\ 183 | moduleMethods) 184 | table = string.join(map(lambda(x): '{"%s", %s, METH_VARARGS}' %\ 185 | (x['name'], x['hashname']), self._methods), ",\n ") 186 | srcFile.write(table + ",\n ") 187 | srcFile.write("{NULL, NULL}\n};\n\n") 188 | 189 | # And finally an initialization method... 190 | srcFile.write(""" 191 | DL_EXPORT(void) init%s(void) { 192 | Py_InitModule("%s", %s); 193 | } 194 | """ % (self._moduleName, self._moduleName, moduleMethods)) 195 | 196 | srcFile.close() 197 | 198 | def _writeMethodBody(self, srcFile, method): 199 | srcFile.write("{\n") 200 | 201 | # Don't write a return value for void functions. 202 | srcFile.write(" /* Return value */\n") 203 | if method['return_type'] != 'void': 204 | srcFile.write(" %s %s;\n\n" % (method['return_type'], "_retval")) 205 | 206 | srcFile.write(" /* Function parameters */\n") 207 | for param in method['params']: 208 | srcFile.write(" %s %s;\n" % (param['type'], param['name'])); 209 | srcFile.write("\n") 210 | 211 | # Now marshal the input parameters, if there are any. 212 | if method['params']: 213 | ptString = _buildPTString(method['params']) 214 | ptArgs = string.join( 215 | map(lambda(x): "&%s" % x['name'], 216 | method['params']), ", ") 217 | srcFile.write(' if(!PyArg_ParseTuple(args, "%s", %s))\n' %\ 218 | (ptString, ptArgs)) 219 | srcFile.write(' return NULL;\n'); 220 | 221 | # And fill in the return value by calling the user's code 222 | # and then filling in the Python return object. 223 | retvalString = "" 224 | if method['return_type'] != 'void': 225 | retvalString = "_retval = " 226 | 227 | srcFile.write(" %s%s(%s);\n" %\ 228 | (retvalString, 229 | method['name'], 230 | string.join(map(lambda(x): '%s' % (x['name']), 231 | method['params']), 232 | ', '))) 233 | 234 | if method['return_type'] == 'void': 235 | srcFile.write(" /* void function. Return None.*/\n") 236 | srcFile.write(" Py_INCREF(Py_None);\n") 237 | srcFile.write(" return Py_None;\n") 238 | elif method['return_type'] == 'PyObject*': 239 | srcFile.write(" return _retval;\n") 240 | else: 241 | try: 242 | rt = self._parseType(method['return_type']) 243 | srcFile.write(' return Py_BuildValue("%s", _retval);\n' %\ 244 | ptStringMap[rt['text']]) 245 | except KeyError: 246 | raise BuildError("Can't handle return type '%s' in function '%s'"%\ 247 | (method['return_type'], method['name'])) 248 | 249 | srcFile.write("}\n\n") 250 | 251 | def _compile(self): 252 | from distutils.core import setup, Extension 253 | os.chdir(self._buildDir) 254 | ext = Extension(self._moduleName, 255 | [self._srcFileName], 256 | library_dirs=self._options.get('library_dirs'), 257 | libraries=self._options.get('libraries'), 258 | define_macros=self._options.get('define_macros'), 259 | undef_macros=self._options.get('undef_macros')) 260 | try: 261 | setup(name = self._moduleName, 262 | version = self._moduleVersion, 263 | ext_modules = [ext], 264 | script_args = ["build"] + (self._options.get('distutils_args') or []), 265 | script_name="C.py", 266 | package_dir=self._buildDir) 267 | except SystemExit, e: 268 | raise BuildError(e) 269 | 270 | os.chdir(self._homeDir) 271 | 272 | ptStringMap = { 273 | 'unsigned': 'i', 274 | 'unsigned int': 'i', 275 | 'int': 'i', 276 | 'long': 'l', 277 | 'float': 'f', 278 | 'double': 'd', 279 | 'char': 'c', 280 | 'short': 'h', 281 | 'char*': 's', 282 | 'PyObject*': 'O'} 283 | 284 | def _buildPTString(params): 285 | ptString = "" 286 | for param in params: 287 | if ptStringMap.has_key(param['type']): 288 | ptString += ptStringMap[param['type']] 289 | else: 290 | raise BuildError("Cannot map argument type '%s' for argument '%s'" %\ 291 | (param['type'], param['name'])) 292 | 293 | return ptString 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /dnlib/PyInline/C.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschere/dreadnought-js/668affc5d1f3b37750c398aaaf9501b6e7e22698/dnlib/PyInline/C.pyc -------------------------------------------------------------------------------- /dnlib/PyInline/__init__.cover: -------------------------------------------------------------------------------- 1 | """PyInline 2 | 3 | The main package for the Inline for Python distribution. 4 | 5 | The PyInline module allows you to put source code from other 6 | programming languages directly "inline" in a Python script or 7 | module. The code is automatically compiled as needed, and then loaded 8 | for immediate access from Python. PyInline is the Python equivalent of 9 | Brian Ingerson's Inline module for Perl (http://inline.perl.org); 10 | indeed, this README file plagerizes Brian's documentation almost 11 | verbatim. 12 | 1: """ 13 | 14 | 1: __revision__ = "$Id: __init__.py,v 1.3 2001/08/29 18:27:25 ttul Exp $" 15 | 1: __version__ = "0.03" 16 | 17 | 2: class BuildError(Exception): 18 | 1: pass 19 | 20 | 1: def build(**args): 21 | """ 22 | Build a chunk of code, returning an object which contains 23 | the code's methods and/or classes. 24 | """ 25 | 26 | # Try to import a PyInline module for the specified language. 27 | try: 28 | m = __import__("%s.%s" %(__name__, args['language'])) 29 | m = getattr(m, args['language']) 30 | except ImportError: 31 | raise BuildError("Failed to find module for language %s") 32 | 33 | # Create a Builder object to build the chunk of code. 34 | b = m.Builder(**args) 35 | 36 | # Build the code and return an object which contains whatever 37 | # resulted from the build. 38 | return b.build() 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /dnlib/PyInline/__init__.py: -------------------------------------------------------------------------------- 1 | """PyInline 2 | 3 | The main package for the Inline for Python distribution. 4 | 5 | The PyInline module allows you to put source code from other 6 | programming languages directly "inline" in a Python script or 7 | module. The code is automatically compiled as needed, and then loaded 8 | for immediate access from Python. PyInline is the Python equivalent of 9 | Brian Ingerson's Inline module for Perl (http://inline.perl.org); 10 | indeed, this README file plagerizes Brian's documentation almost 11 | verbatim. 12 | """ 13 | 14 | __revision__ = "$Id: __init__.py,v 1.3 2001/08/29 18:27:25 ttul Exp $" 15 | __version__ = "0.03" 16 | 17 | import C 18 | 19 | def build(**args): 20 | """ 21 | Build a chunk of code, returning an object which contains 22 | the code's methods and/or classes. 23 | """ 24 | 25 | 26 | 27 | # Create a Builder object to build the chunk of code. 28 | b = C.Builder(**args) 29 | 30 | # Build the code and return an object which contains whatever 31 | # resulted from the build. 32 | return b.build() 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /dnlib/PyInline/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschere/dreadnought-js/668affc5d1f3b37750c398aaaf9501b6e7e22698/dnlib/PyInline/__init__.pyc -------------------------------------------------------------------------------- /dnlib/PyInline/c_util.py: -------------------------------------------------------------------------------- 1 | # Utility Functions and Classes for the PyInline.C module. 2 | 3 | import re 4 | c_directive = '\s*#.*' 5 | c_cppcomment = '//.*' 6 | c_simplecomment = '/\*[^*]*\*+([^/*][^*]*\*+)*/' 7 | c_doublequote = r'(?:"(?:\\.|[^"\\])*")' 8 | c_singlequote = r'(?:\'(?:\\.|[^\'\\])*\')' 9 | c_comment = re.compile("(%s|%s)|(?:%s|%s|%s)" % (c_doublequote, 10 | c_singlequote, 11 | c_cppcomment, 12 | c_simplecomment, 13 | c_directive)) 14 | 15 | const = re.compile('\s*const\s*') 16 | star = re.compile('\s*\*\s*') 17 | _c_pandn = "((?:(?:[\w*]+)\s+)+\**)(\w+)" 18 | c_pandm = re.compile(_c_pandn) 19 | _c_function = _c_pandn + "\s*\(([^\)]*)\)" 20 | c_function_def = re.compile("(?:%s|%s)|(%s)" % (c_doublequote, 21 | c_singlequote, 22 | _c_function + "\s*(?:\{|;)")) 23 | c_function_decl = re.compile(_c_function + "\s*;") 24 | 25 | trimwhite = re.compile("\s*(.*)\s*") 26 | 27 | def preProcess(code): 28 | return c_comment.sub(lambda(match): match.group(1) or "", code) 29 | 30 | def findFunctionDefs(code): 31 | functionDefs = [] 32 | for match in c_function_def.findall(code): 33 | if match[0]: 34 | functionDefs.append({'return_type': trimWhite(match[1]), 35 | 'name': trimWhite(match[2]), 36 | 'rawparams': trimWhite(match[3])}) 37 | return functionDefs 38 | 39 | 40 | _wsLeft = re.compile("^\s*") 41 | _wsRight = re.compile("\s*$") 42 | def trimWhite(str): 43 | str = _wsLeft.sub("", str) 44 | str = _wsRight.sub("", str) 45 | return str 46 | 47 | if __name__ == '__main__': 48 | x = """#include 49 | const char* foo = "long int x(int a) {"; 50 | long int barf(int a, char *b) { 51 | int x, y; 52 | int x[24]; 53 | } 54 | 55 | long int *** fkfkfk(char * sdfkj, int a, char *b) { 56 | int x, y; 57 | } 58 | 59 | """ 60 | print findFunctionDefs(x) 61 | 62 | 63 | -------------------------------------------------------------------------------- /dnlib/PyInline/c_util.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschere/dreadnought-js/668affc5d1f3b37750c398aaaf9501b6e7e22698/dnlib/PyInline/c_util.pyc -------------------------------------------------------------------------------- /dnlib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschere/dreadnought-js/668affc5d1f3b37750c398aaaf9501b6e7e22698/dnlib/__init__.py -------------------------------------------------------------------------------- /dnlib/jsapi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Defines the api used by the javascript program to tie in directly 3 | into python. 4 | """ 5 | 6 | import PyV8 7 | import require 8 | import jshandler 9 | import cherrypy 10 | import jsroute 11 | import logging 12 | import traceback 13 | import os 14 | import types 15 | import json 16 | import sys 17 | from jslogging import RootLogger 18 | 19 | Version = "Version %(major)d.%(minor)d [%(name)s]" % { 20 | 'major': 0, 21 | 'minor': 1, 22 | 'name' : 'Ironclad' 23 | } 24 | 25 | 26 | def _init_jscontext( c, scriptfile ): 27 | c.locals.exports = {} 28 | c.locals.id = '__main__' 29 | c.locals.filename = scriptfile 30 | c.locals.loaded = False 31 | c.locals.parent= None 32 | c.locals.children = [] 33 | 34 | 35 | 36 | def expand(obj): 37 | isObject = str(type(obj)).find('PyV8.JSObject') != -1 38 | isArray = str(type(obj)).find('PyV8.JSArray') != -1 39 | 40 | if isObject or type(obj) == types.DictType: 41 | n = dict(obj) 42 | for k, v in n.items(): 43 | n[k] = expand(v) 44 | elif isArray or type(obj) in (types.ListType,types.TupleType): 45 | n = list(obj) 46 | n = map(expand, n) 47 | else: 48 | n = repr(obj) 49 | return n 50 | 51 | 52 | class HandlerAPI(PyV8.JSClass): 53 | """ 54 | API used by HTTP handlers. 55 | """ 56 | 57 | def __init__(self): 58 | PyV8.JSClass.__init__(self) 59 | 60 | for k,v in __builtins__.items(): 61 | if k not in ("chr","ord","map","filter","reduce",\ 62 | "int","str","len","open","close","sum","getattr","buffer",\ 63 | "hasattr"): 64 | continue 65 | 66 | class pyfuncobj: 67 | def __init__(self, v): 68 | self.__doc__ = v.__doc__ 69 | self.v = v 70 | def __call__(self, *args ): 71 | return self.v( *args ) 72 | f = pyfuncobj(v) 73 | setattr(self,k,f) 74 | 75 | def isObject(self, obj): return str(type(obj)).find('PyV8.JSObject') != -1 76 | def isNULL(self, obj): return str(type(obj)).find('PyV8.JSNULL') != -1 77 | def isUndefined(self, obj): return str(type(obj)).find('PyV8.JSUndefined') != -1 78 | def isArray(self, obj): return str(type(obj)).find('PyV8.JSArray') != -1 79 | def isFunction(self, obj): return str(type(obj)).find('PyV8.JSFunction') != -1 80 | def isInt(self, obj): return type(obj) == type(0) 81 | def isFloat(self, obj): return type(obj) == type(0.0) 82 | 83 | def sprintf(self, fmt, *args): 84 | """ 85 | Return a formatted string like the C style sprintf 86 | 87 | :Parameters: 88 | - `fmt`: string containing format information. This format has two 89 | types. The first type is normal C style format string containing 90 | %d,%f,%s ect. The second type is used if a javascript object is 91 | passed in as an argument. In this type the format string is mapped 92 | to key value pairs like this: 93 | sprintf("%(name)s is awake.", {name:'Bob'}) -> "Bob is awake." 94 | - `args`: Variable length arguments, or a single javascript object as an 95 | argument. 96 | """ 97 | if len(args) == 1 and self.isObject(args[0]): 98 | return fmt % dict(args[0]) 99 | else: 100 | return fmt % args 101 | 102 | def addRequirePath(self, path): 103 | """ 104 | Add a specified path to the RequirePath array used by the require function. 105 | 106 | :Parameters: 107 | - `path`: A valid directory to be added to the RequirePath internal variable. 108 | """ 109 | require.addPath( path ) 110 | 111 | def removeRequirePath(self, path): 112 | """ 113 | Remove the specified path to the RequirePath array used by the require function. 114 | 115 | :Parameters: 116 | - `path`: A valid directory to be removed from the RequirePath internal variable. 117 | """ 118 | require.removePath( path ) 119 | 120 | def require(self, spec, options={'language':'javascript'} ): 121 | "%s" % require.DocString 122 | return require.require( spec, options ) 123 | 124 | 125 | def show(self, obj): 126 | print type(obj) == type(""), type(obj), ":", str(obj) 127 | 128 | def vardump(self, obj, display=True): 129 | n = expand(obj) 130 | text = json.dumps(n,sort_keys=True,indent=4)+"\n" 131 | if display: 132 | sys.stdout.write(text) 133 | sys.stdout.flush() 134 | return text 135 | 136 | def showApi(self): 137 | names = dir(self) 138 | names.sort() 139 | for k in names: 140 | 141 | if not k or (len(k) > 2 and k[:2] == "__"): 142 | continue 143 | 144 | v = getattr(self,k) 145 | if not v: 146 | continue 147 | 148 | if callable(v) and type(k) == type("") and v.__doc__: 149 | print "%-32s" % k 150 | print " ",v.__doc__ 151 | print "-" * 32 152 | 153 | 154 | 155 | 156 | class RootAPI(HandlerAPI): 157 | "Used by the root script, to setup handlers and system settings" 158 | 159 | def __init__(self): 160 | HandlerAPI.__init__(self) 161 | 162 | self.logger = RootLogger() 163 | self.registry = jsroute.RouteRegistry( HandlerAPI() ) 164 | 165 | 166 | self._settings = { 167 | 'host' :"0.0.0.0", 168 | 'port' : 8080, 169 | 'thread_pool' : 20, 170 | 'js_pool' : 20, 171 | 'favicon' : os.environ['HOME']+"/.dreadnought-js/favicon.ico" 172 | } 173 | 174 | self._config = { 175 | '/': { 176 | 'request.dispatch': self.registry.dispatch, 177 | 'tools.sessions.on': True 178 | } 179 | } 180 | 181 | def register(self, path, jscb, options = {} ): 182 | self.logger.debug("register('%s',%s,%s)" % (path, jscb, options )) 183 | try: 184 | self.registry.register( path, jscb, options ) 185 | except: 186 | self.logger.error( traceback.format_exc() ) 187 | raise 188 | 189 | def getLogger(self): 190 | return self.logger 191 | 192 | 193 | 194 | def mainloop(self): 195 | "Interface to cherrypy's mainloop" 196 | 197 | # launch a set of child processes to handle each request 198 | # within a child process. 199 | self.registry.start_processes( self._settings.get('js_pool',20) ) 200 | 201 | self._config['global'] = { 202 | 'server.socket_host' : self._settings.get('host',"0.0.0.0"), 203 | 'server.socket_port' : self._settings.get('port',8080), 204 | 'server.thread_pool' : self._settings.get('thread_pool',20) 205 | } 206 | 207 | sdir = self._settings.get('static_dir',None) 208 | if sdir: 209 | self.logger.debug("adding static content directory %s" % sdir) 210 | self._config["/"]['tools.staticdir.on'] = True 211 | self._config["/"]['tools.staticdir.dir'] = sdir 212 | 213 | self._config['/favicon.ico']={ 214 | 'tools.staticfile.on': len(self._settings.get('favicon','')) > 0, 215 | 'tools.staticfile.filename': self._settings.get('favicon','') 216 | } 217 | 218 | 219 | cherrypy.tree.mount(root=None, config=self._config ) 220 | 221 | # block forever servicing requests. 222 | cherrypy.quickstart( script_name="/", config=self._config ) 223 | 224 | 225 | def settings(self, s): 226 | for k,v in dict(s).items(): 227 | self._settings[k] = v 228 | 229 | 230 | 231 | 232 | def run( scriptfile, opts ): 233 | """ Execute javascript, setup cherrypy and route url paths to 234 | registered callbacks. 235 | """ 236 | levelname = opts.get('loglevel','DEBUG').upper() 237 | 238 | if not hasattr(logging,levelname): 239 | return "Unknown log level %s, must be DEBUG|INFO|WARN" % levelname 240 | 241 | logging.basicConfig(flename=opts.get('logfile','/dev/stdout'), 242 | level=getattr(logging,levelname), format="%(message)s" ) 243 | 244 | basedir = os.path.dirname(os.path.abspath(scriptfile)) 245 | require.RequirePath = [basedir+"/", basedir+"/.d-mods/"] + \ 246 | require.RequirePath 247 | 248 | code = open(scriptfile).read() 249 | api = RootAPI() 250 | root_context = PyV8.JSContext( api ) 251 | root_context.enter() 252 | 253 | _init_jscontext( root_context, scriptfile ) 254 | try: 255 | root_context.eval( code ) 256 | except KeyboardInterrupt: 257 | pass 258 | root_context.leave() 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /dnlib/jshandler.py: -------------------------------------------------------------------------------- 1 | import PyV8 2 | import os 3 | import traceback 4 | try: 5 | import cPickle as pickle 6 | except: 7 | import pickle 8 | import threading 9 | import logging 10 | import select 11 | import tempfile 12 | import struct 13 | import jsapi 14 | from jslogging import PipeLogger 15 | 16 | 17 | class IOChannel(object): 18 | """ Interprocess communication using a pipe to exchange serialized python objects 19 | between a child process and the main process running cherrypy. 20 | """ 21 | def __init__(self): 22 | self.r, self.w = os.pipe() 23 | 24 | self.reader = os.fdopen( self.r, "r" ) 25 | self.writer = os.fdopen( self.w, "w" ) 26 | 27 | def fileno(self): 28 | return self.r 29 | 30 | def recv(self): 31 | "un-serialize an incoming object" 32 | return pickle.load(self.reader) 33 | 34 | def send(self, obj): 35 | "serialize and send an object" 36 | pickle.dump(obj, self.writer) 37 | self.writer.flush() 38 | 39 | 40 | 41 | class NpWriter: 42 | "Manage the writing side of a named pipe" 43 | def __init__(self,fn): 44 | self.fn = fn 45 | self.f = open(fn,'w') 46 | self.lock = threading.RLock() 47 | 48 | def send(self, obj): 49 | data = pickle.dumps(obj) 50 | hdr = struct.pack('>i', len(data)) 51 | self.lock.acquire() 52 | self.f.write(hdr+data) 53 | self.f.flush() 54 | self.lock.release() 55 | 56 | 57 | 58 | class NpReader: 59 | "Manage the reading side of a named pipe" 60 | def __init__(self, fn): 61 | self.f = open(fn,'r') 62 | self.fn = fn 63 | 64 | def fileno(self): 65 | return self.f.fileno() 66 | 67 | def _read_bytes(self, n): 68 | buf = '' 69 | while len(buf) < n: 70 | t = n - len(buf) 71 | buf += self.f.read(t) 72 | return buf 73 | 74 | def __del__(self): 75 | self.f.close() 76 | os.remove( self.fn ) 77 | 78 | def recv(self): 79 | hdr = self._read_bytes(struct.calcsize('i')) 80 | size = struct.unpack(">i",hdr)[0] 81 | data = self._read_bytes(size) 82 | return pickle.loads(data) 83 | 84 | 85 | class NamedPipeFactory: 86 | """ Replaces IOChannel if processes are forked on demand 87 | """ 88 | 89 | def __init__(self): 90 | fd, self.filename = tempfile.mkstemp() 91 | os.close(fd) 92 | if os.access(self.filename,os.F_OK): 93 | os.remove(self.filename) 94 | os.mkfifo(self.filename) 95 | 96 | def getWriter(self): 97 | return NpWriter( self.filename ) 98 | 99 | def getReader(self): 100 | return NpReader(self.filename) 101 | 102 | 103 | 104 | 105 | 106 | 107 | # global lookup table that maps an numeric identfier 108 | # to tuple containing: 109 | # (javascript callback, callback argument, logger, options ) 110 | JsCbLookup = [] 111 | 112 | def AddJsCb( path, jscb, options ): 113 | 114 | # create a logger for this callback 115 | logger = PipeLogger( "[%s]%s" % (options.get('method','http'), path) ) 116 | 117 | # get optional callback user arguments 118 | jsargs = options.get('userdata',None) 119 | 120 | # Append javascript callbacks to cherrypy routes to this table. 121 | r = len(JsCbLookup) 122 | JsCbLookup.append( (jscb, jsargs, logger, options,) ) 123 | return r 124 | 125 | 126 | 127 | 128 | class JSHandler(object): 129 | """ This object represents the child process that handles incoming 130 | web requests and sends responses back to cherrypy. 131 | """ 132 | 133 | def __init__(self, api, np_channels=None, set_context=True): 134 | if set_context: 135 | self.context = PyV8.JSContext( api ) 136 | 137 | if not np_channels: 138 | self.fault_detector = select.poll() 139 | self.req_chan = IOChannel() 140 | self.res_chan = IOChannel() 141 | 142 | # used to detect a terminated child process 143 | error = select.POLLNVAL | select.POLLERR | select.POLLHUP 144 | self.fault_detector.register( self.req_chan.fileno(), error ) 145 | self.fault_detector.register( self.res_chan.fileno(), error ) 146 | else: 147 | self.req_chan, self.res_chan = np_channels 148 | self.fault_detector = None # Not needed since this process dies after 149 | # after the request is complete 150 | self.lock = threading.RLock() 151 | 152 | def detectChildFault(self): 153 | result = False 154 | if self.fault_detector: 155 | result = len(self.fault_detector.poll(0)) > 0 156 | return result 157 | 158 | def start(self): 159 | pid = os.fork() 160 | if pid == 0: 161 | # double fork to prevent potential zombie processes. 162 | if os.fork() == 0: 163 | self.run() 164 | logging.info("child process exiting") 165 | 166 | # both child and grandchild terminate here. 167 | os._exit(0) 168 | 169 | def transaction(self, req): 170 | # Send a request to the javascript callback, return the response along with 171 | # extra options associated with this callback. 172 | 173 | 174 | self.lock.acquire() 175 | try: 176 | (jscb, jsargs, logger, options) = JsCbLookup[ req['ident'] ] 177 | res = self._transaction(logger, req) 178 | except: 179 | res = {'exc': traceback.format_exc() } 180 | self.lock.release() 181 | 182 | if "exc" in res: 183 | raise RuntimeError, res['exc'] 184 | return res 185 | 186 | 187 | def _transaction(self, logger, req): 188 | p = select.poll() 189 | p.register( logger.fileno(), select.POLLIN ) 190 | p.register( self.res_chan.fileno(), select.POLLIN ) 191 | 192 | self.req_chan.send( req ) 193 | while True: 194 | for (fd,evt) in p.poll(-1): 195 | if logger.fileno() == fd and evt & select.POLLIN: 196 | # Note: root logger is configured as a passthrough with no 197 | # formatting except %(message)s, this allows it to be a 198 | # collection point for the route loggers. 199 | logging.info(logger.read()[:-1]) 200 | 201 | elif self.res_chan.fileno() == fd and evt & select.POLLIN: 202 | 203 | # completed transaction 204 | res = self.res_chan.recv() 205 | return res 206 | 207 | else: 208 | # unexpected error, we should never get this but we 209 | # need to handle it anyway. 210 | n = { 211 | logger.fileno(): 'logger', 212 | self.res_chan.fileno(): 'response_pipe' 213 | } 214 | return {"exc": "%s error event=%x" % ( n[fd], evt )} 215 | 216 | 217 | def _handle_streaming(self, req): 218 | # streaming request controls the context and calls this 219 | # processes over and over until done. 220 | if req.get('start-streaming',False): 221 | self.context.enter() 222 | return { 223 | 'success': True 224 | } 225 | elif req.get('end-streaming',False): 226 | self.context.leave() 227 | return { 228 | 'success': True 229 | } 230 | else: 231 | return self._jsexec( req ) 232 | 233 | def _jsexec( self, req ): 234 | # execute javascript command 235 | (jscb, jsargs, pipe_logger, options) = JsCbLookup[ req['ident'] ] 236 | 237 | 238 | self.context.locals.jscb = jscb 239 | self.context.locals.jsargs = jsargs 240 | self.context.locals.logger = pipe_logger.getLogger() #pipe_logger.logger 241 | self.context.locals.req = req 242 | self.context.eval("var res = jscb(logger,req,jsargs);") 243 | return dict(self.context.locals.res) 244 | 245 | 246 | def run(self): 247 | p = select.poll() 248 | p.register(self.req_chan.fileno(), select.POLLIN) 249 | while True: 250 | # detect a pipe closure 251 | for (fd,evt) in p.poll(-1): 252 | if (evt & select.POLLIN) == 0: 253 | # exit run loop, kill process 254 | return 255 | 256 | try: 257 | req = self.req_chan.recv() 258 | if req.get('streaming',False): 259 | res = self._handle_streaming( req ) 260 | else: 261 | self.context.enter() 262 | res = self._jsexec( req ) 263 | self.context.leave() 264 | except: 265 | res = {"exc": traceback.format_exc() } 266 | 267 | self.res_chan.send( res ) 268 | 269 | 270 | 271 | class JsHandlerControl: 272 | """ 273 | Creates an array of child process handlers for all HTTP requests in the system. 274 | Each incoming request checks out a handler from the pool and returns it when it 275 | is done. 276 | """ 277 | 278 | # identify a special type of http handler 279 | OVERFLOW_HANDLER_IDX = -1 280 | 281 | def _overflow_handler_controller(self, api): 282 | """ 283 | This process handles incoming http requests if no preforked processes are 284 | available. It uses named pipes and forks on demand which is less efficient 285 | """ 286 | p = select.poll() 287 | p.register( self.overflow_chan.fileno(), select.POLLIN ) 288 | hangup = False 289 | while not hangup: 290 | for (fd,evt) in p.poll(-1): 291 | if evt & select.POLLIN: 292 | npf_pair = self.overflow_chan.recv() 293 | if os.fork() == 0: 294 | if os.fork() == 0: 295 | # block until http handler creates the writer 296 | req_chan = npf_pair[0].getReader() 297 | # block until the http handler creates the reader 298 | res_chan = npf_pair[1].getWriter() 299 | npipes = (req_chan,res_chan) 300 | try: 301 | # service http request using named pipes 302 | # for transit instead of anonymous pipes. 303 | JSHandler( api, npipes ).run() 304 | except: 305 | res_chan.send({ 306 | 'exc': traceback.format_exc() 307 | }) 308 | os._exit(0) 309 | else: 310 | hangup = True 311 | 312 | def _overflow_iface(self): 313 | idx = self.OVERFLOW_HANDLER_IDX 314 | npf_pair = [ NamedPipeFactory(), NamedPipeFactory() ] 315 | 316 | # send message to overflow control process, create a child process 317 | # based on this named pipe pair, the child process will create the 318 | # reader/writer compliments of the named pipes on the other end. 319 | self.overflow_chan.send( npf_pair ) 320 | 321 | # block until child opens reader 322 | req_chan = npf_pair[0].getWriter() 323 | 324 | # block until child opens writer 325 | res_chan = npf_pair[1].getReader() 326 | 327 | jsh = JSHandler( self.api, (req_chan,res_chan), set_context=False ) 328 | 329 | return (jsh,idx) 330 | 331 | def __init__(self): 332 | self.handlers = [] 333 | self.idx = 0 334 | self.lock = threading.RLock() 335 | self.overflow_chan = IOChannel() 336 | self.api = None 337 | 338 | def setup(self, api, cache_size): 339 | """ 340 | Setup an array of pre-forked processes to handle incoming requests 341 | along with a process to handle overflow conditions were we have to 342 | fork on demand. 343 | """ 344 | 345 | self.api = api 346 | for i in range(0,cache_size): 347 | jsh = JSHandler(api) 348 | 349 | # (handler, in_use) 350 | self.handlers.append( (jsh,False) ) 351 | 352 | # start cheild process to service requests in javascript. 353 | jsh.start() 354 | 355 | # launch child process to handle overflow conditions when we 356 | # have no spare processes to service a request 357 | if os.fork() == 0: 358 | if os.fork() == 0: 359 | self._overflow_handler_controller(api) 360 | os._exit(0) 361 | 362 | 363 | def checkout(self): 364 | """ 365 | Return a preallocated javascript handler to service an incoming request. The js 366 | handler contains a child process which routes the request to a javascript 367 | interpreter. 368 | 369 | If no preforked processes are available then pass to the overflow handler which 370 | will fork a handler on demand. 371 | """ 372 | 373 | self.lock.acquire() 374 | result = None 375 | count = 0 376 | while not result: 377 | if count == len(self.handlers): 378 | result = self._overflow_iface() 379 | else: 380 | (jsh,inuse) = self.handlers[self.idx] 381 | if (not inuse) and (not jsh.detectChildFault()): 382 | result = (jsh,self.idx) 383 | self.handlers[self.idx] = (jsh,True) 384 | 385 | self.idx = (self.idx + 1) % len(self.handlers) 386 | count += 1 387 | self.lock.release() 388 | 389 | return result 390 | 391 | def checkin(self, jsh, idx): 392 | if idx != self.OVERFLOW_HANDLER_IDX: 393 | # check handler back into cache 394 | self.lock.acquire() 395 | self.handlers[self.idx] = (jsh,False) 396 | self.lock.release() 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | -------------------------------------------------------------------------------- /dnlib/jslogging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import jsapi 3 | import os 4 | 5 | 6 | def format_args( args ): 7 | "Support formatting log messages" 8 | 9 | def _stringify( msg): 10 | if type(msg) == type(""): 11 | return msg 12 | return jsapi.expand(msg) 13 | 14 | np = len(args) 15 | if np == 0: 16 | return "" 17 | elif np == 1: 18 | return _stringify(args[0]) 19 | else: 20 | if np == 2 and type(args[0]) == type("") and \ 21 | str(type(args[1])).find('PyV8.JSObject') != -1: 22 | # func("%(param1)s ..." % {param1:'hello'}) 23 | return args[0] % dict(args[1]) 24 | else: 25 | return args[0] % tuple(args[1:]) 26 | 27 | 28 | class jslogger(object): 29 | def __init__(self, logger): 30 | self.logger = logger 31 | 32 | def wrap(self, func ): 33 | class _func_wrapper: 34 | def __init__(self, f): 35 | self.func = f 36 | def __call__(self, *args): 37 | self.func( format_args( args ) ) 38 | return _func_wrapper( func ) 39 | 40 | def setLevel(self, n): 41 | if n not in ('debug','info','warn','error','critical'): 42 | raise RuntimeError, "log level must be either debug,info,warn,error,critical" 43 | v = getattr(logging,n.upper()) 44 | self.logger.setLevel( v ) 45 | 46 | def __getattr__(self, n): 47 | "wrap the traditional log functions to support formatted logging and object introspection " 48 | if n in ('debug','info','warn','error','critical'): 49 | return self.wrap( getattr(self.logger,n) ) 50 | elif hasattr(self.logger,n): 51 | return getattr(self.logger,n) 52 | 53 | 54 | 55 | 56 | class RootLogger(): 57 | def __init__(self): 58 | self.fmt = logging.Formatter( 59 | fmt='%(asctime)s %(name)s [%(levelname)s] %(message)s') 60 | 61 | def _log(self, level, args): 62 | message = format_args( args ) 63 | 64 | msg = self.fmt.format(logging.LogRecord(\ 65 | "root",level,"/",1,"%s",message,None,None)) 66 | # no format for the global logger so the log level doesn't matter 67 | # its just a way to get to the log file 68 | logging.info( msg ) 69 | 70 | def debug(self, *args): 71 | self._log(logging.DEBUG, args) 72 | def info(self, *args): 73 | self._log(logging.INFO, args) 74 | def warning(self, *args): 75 | self._log(logging.WARING, args ) 76 | def error(self, *args): 77 | self._log(logging.ERROR, args) 78 | def critical(self, *args): 79 | self._log(logging.CRITICAL, args) 80 | 81 | 82 | 83 | class PipeLogger(): 84 | """ Private logger for the javascript callbacks, this object routes log 85 | messages back to the parent process (cherrypy) for logging. 86 | """ 87 | def __init__(self, logger_name, logLevel=logging.DEBUG): 88 | 89 | self.r,self.w = os.pipe() 90 | self.rr = os.fdopen(self.r,"r") 91 | self.ww = os.fdopen(self.w,"w") 92 | 93 | # create logger 94 | self.logger = logging.getLogger(logger_name) 95 | self.logger.setLevel(logLevel) 96 | 97 | # create console handler and set level to debug 98 | ch = logging.StreamHandler(self.ww) 99 | ch.setLevel(logging.DEBUG) 100 | 101 | # create formatter 102 | formatter = logging.Formatter(\ 103 | '%(asctime)s %(name)s [%(levelname)s] %(message)s') 104 | 105 | # add formatter to ch 106 | ch.setFormatter(formatter) 107 | 108 | # add ch to logger 109 | self.logger.addHandler(ch) 110 | 111 | def getLogger(self): 112 | return jslogger( self.logger ) 113 | 114 | 115 | def fileno(self): 116 | return self.r 117 | 118 | def read(self, n=0xffff): 119 | return os.read( self.r, n ) 120 | 121 | def close(self): 122 | self.rr.close() 123 | self.ww.close() 124 | 125 | 126 | -------------------------------------------------------------------------------- /dnlib/jsroute.py: -------------------------------------------------------------------------------- 1 | import jshandler 2 | import json 3 | import cherrypy 4 | import logging 5 | 6 | 7 | # Pool of child processes running javascript 8 | JsPool = jshandler.JsHandlerControl() 9 | 10 | 11 | 12 | class RouteController: 13 | 14 | 15 | 16 | """ 17 | Cherrpy mixes the post and get parameters into a single 18 | dictionary as a result there could be collissions, this 19 | class separates the two. 20 | """ 21 | def __init__(self, path, ident, options): 22 | self.ident = ident 23 | self.options = options 24 | self.path = path 25 | 26 | def _process_response(self, res): 27 | "Process response from child process" 28 | 29 | try: 30 | dict(res) 31 | except: 32 | res = {'data': res} 33 | 34 | if res.get('error',False): 35 | status = res.get('status',500) 36 | raise cherrypy.HTTPError(status,res['error']) 37 | 38 | if res.get('exc',False): 39 | status = res.get('status',500) 40 | raise cherrypy.HTTPError(status,res['exc']) 41 | 42 | if self.options.get('json',False): 43 | res['data'] = json.dumps(res['data']) 44 | 45 | if 'permissionLevel' in res: 46 | pl = int(res['permissionLevel']) 47 | cherrypy.session[ cherrypy.session.id ] = pl 48 | 49 | logging.debug("res = %s" % str(res)) 50 | return res['data'] 51 | 52 | 53 | # generator used for streaming. 54 | def generator(self, jsh, idx, req, options): 55 | global JsPool 56 | 57 | error = None 58 | 59 | # explicitly enter javascript context 60 | jsh.transaction({ 61 | 'start-streaming':True, 62 | 'streaming': True, 63 | 'ident': req['ident'] 64 | }) 65 | 66 | # set the streaming flag so we follow a different 67 | # control path in the js handler. 68 | req['streaming'] = True 69 | req['bytes_read'] = 0 70 | while True: 71 | res = jsh.transaction(req) 72 | if 'success' in res: 73 | continue 74 | 75 | if 'error' in res: 76 | error = res['error'] 77 | break 78 | 79 | # fetch streaming data 80 | data = res.get('data',None) 81 | if not data or len(data) == 0: 82 | # no more data we're done. 83 | break 84 | 85 | req['bytes_read'] += len(data) 86 | 87 | # return a generator to cherrypy, have it call 88 | # the next method until there is no more data. 89 | yield data 90 | 91 | # explicitly leave javascript context 92 | jsh.transaction({ 93 | 'stop-streaming':True, 94 | 'streaming': True, 95 | 'ident': req['ident'] 96 | }) 97 | 98 | # free this child process to work on other requests. 99 | JsPool.checkin(jsh, idx) 100 | 101 | if error: 102 | raise RuntimeError, error 103 | 104 | 105 | 106 | def __handler(self, method, qs_params ): 107 | # see if there is post data. 108 | try: 109 | post_data = cherrypy.request.body.params 110 | except: 111 | post_data = {} 112 | 113 | # separate the query parameters from the post data 114 | for k in set(qs_params.keys()).intersection(set(post_data.keys())): 115 | if type(qs_params[k]) == type([]): 116 | # remove collision array, the first element is the query string 117 | # parameter 118 | qs_params[k] = qs_params[k][0] 119 | else: 120 | # this is really post data that was mixed in. 121 | del qs_params[k] 122 | 123 | req = { 124 | "path": self.path, 125 | "method": method, 126 | "qs_params": qs_params, 127 | "post_data": post_data, 128 | "ident": self.ident 129 | } 130 | 131 | # checkout a javascript sub process from the pool 132 | # to use. 133 | jsh, idx = JsPool.checkout() 134 | if 'stream' in self.options and self.options['stream']: 135 | return self.generator(jsh,idx,req,self.options) 136 | else: 137 | # not-streaming, ajax request or a dynamic web page. 138 | res = jsh.transaction(req) 139 | JsPool.checkin(jsh, idx) 140 | return self._process_response(res) 141 | 142 | __handler._cp_config = {'response.stream': True} 143 | 144 | 145 | 146 | def __call__(self, **params): 147 | 148 | # if configured check permissions based on our session id 149 | if 'permissionLevel' in self.options: 150 | r_pl = int(self.options['permissionLevel']) 151 | if cherrypy.session.id in cherrypy.session: 152 | user_pl = cherrypy.session[cherrypy.session.id] 153 | if r_pl >= user_pl: 154 | raise cherrypy.HTTPError(403,"Unauthorized") 155 | 156 | return self.__handler("unknown", params ) 157 | 158 | """ 159 | @cherrypy.expose 160 | def GET(self, **params): 161 | print "GET", self.path 162 | return self.__handler("GET", params ) 163 | @cherrypy.expose 164 | def POST(self, **params): 165 | print "POST", self.path 166 | return self.__handler("POST", params ) 167 | @cherrypy.expose 168 | def PUT(self, **params): 169 | return self.__handler("PUT", params ) 170 | @cherrypy.expose 171 | def DELETE(self, **params): 172 | return self.__handler("DELETE", params ) 173 | """ 174 | 175 | 176 | class RouteRegistry: 177 | def __init__(self, api): 178 | self.api = api 179 | self.dispatch = cherrypy.dispatch.RoutesDispatcher() 180 | 181 | def start_processes(self, cache_size): 182 | global JsPool 183 | 184 | JsPool.setup( self.api, cache_size ) 185 | 186 | 187 | def register(self, path, jscb, options ): 188 | opt = dict(options) 189 | ident = jshandler.AddJsCb( path, jscb, opt ) 190 | control = RouteController( path, ident, opt ) 191 | 192 | # are we filtering for a particular url method ? 193 | method = opt.get('method',None) 194 | 195 | if method: 196 | method = str(method.upper()) 197 | methods = ['GET','POST','PUT','DELETE'] 198 | if method not in methods: 199 | raise ValueError, \ 200 | "options.method must be one of %s" % ",".join(methods) 201 | self.dispatch.connect( 202 | name=path, 203 | route=path, 204 | controller=control, 205 | conditions=dict(method=[method]) 206 | ) 207 | else: 208 | self.dispatch.connect(name=path, 209 | route=path, controller=control ) 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /dnlib/require.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements dreadnought's version of require to load modules that 3 | can be written in javascript, python or C. In the case of C it 4 | uses either PyInline to compile/link a C file and expose its methods to 5 | javascript or ctypesgen which generates a python file using a C header 6 | file along with a set of libraries. 7 | 8 | """ 9 | import sys 10 | import requests 11 | import urlparse 12 | import os 13 | import PyInline 14 | import traceback 15 | import PyV8 16 | import jsapi 17 | 18 | 19 | 20 | RequirePath = ['%s/.dreadnought-js/modules/' % os.environ['HOME'],\ 21 | '/usr/local/share/dreadnought-js/modules'] 22 | sys.path += RequirePath 23 | 24 | CTYPES_MODULES_PATH = "%s/.dreadnought-js/.python-ctypes-modules" % os.environ['HOME'] 25 | os.system("mkdir -p %s" % CTYPES_MODULES_PATH) 26 | 27 | 28 | sys.path.append( CTYPES_MODULES_PATH ) 29 | 30 | 31 | DocString = \ 32 | """ 33 | This is a multipurpose function for importing code into a module. It 34 | can work as like the require function in nodejs when passed in a javascript 35 | module or it can import a python module, or compile/link/import a C module. 36 | 37 | :Parameters: 38 | - `spec`: This is a string containing the path of a file. If javascript it 39 | may contain the .js extension but it is not manditory. For python 40 | it is the module name to be passed into the python __import__ function 41 | containing the name of the module without the .py suffix. For a C 42 | module it is the path of the C module to be compiled/linked/imported 43 | into this module. 44 | 45 | - `options`: 46 | +--------------+--------------------------------------------------+ 47 | | Name | Description | 48 | +==============+==================================================+ 49 | | language | Maybe one of: javascript (default), python, c | 50 | +--------------+--------------------------------------------------+ 51 | | c_tool | Used only for language=c, defaults to PyInline | 52 | | | for simple C optimizations, for more full | 53 | | | featured C integrations use tool='ctypesgen'. | 54 | | | | 55 | | | tool='PyInline' compiles/links code so spec | 56 | | | should be a *.c file | 57 | | | tool='ctypesgen' is for existing libraries so | 58 | | | spec should be a *.h file | 59 | +--------------+--------------------------------------------------+ 60 | | libs | Used only for language=c, set this to an array | 61 | | | of dependancy libraries such as libs=['c','m'] | 62 | | | for -libc -libm ect. | 63 | +--------------+--------------------------------------------------+ 64 | | libdirs | Used only for language=c, set this to an array | 65 | | | of dependancy library directories such as | 66 | | | libdirs=['/usr/local/lib'] | 67 | +--------------+--------------------------------------------------+ 68 | | includes | Used only for language=c, set this to an array | 69 | | | of dependancy include directory files such as | 70 | | | includes=['/usr/local/include'] | 71 | +--------------+--------------------------------------------------+ 72 | 73 | 74 | 75 | 76 | """ 77 | 78 | 79 | class RequireError( RuntimeError ): 80 | pass 81 | 82 | def addPath( path ): 83 | global RequirePath 84 | if path not in RequirePath: 85 | RequirePath.append( path ) 86 | if path not in sys.path: 87 | sys.path.append( path ) 88 | 89 | def removePath( path ): 90 | global RequirePath 91 | if path in RequirePath: 92 | RequirePath.remove(path) 93 | if path in sys.path: 94 | sys.path.remove( path ) 95 | 96 | # Cache modules 97 | __ModuleCache = {} 98 | 99 | def require( spec, options ): 100 | global __ModuleCache 101 | 102 | if spec in __ModuleCache: 103 | return __ModuleCache[spec] 104 | 105 | lang = "javascript" 106 | if hasattr(options,"language"): 107 | lang = options.language.lower() 108 | 109 | if lang == 'javascript': 110 | mod = _require_js( spec, options ) 111 | elif lang == 'python': 112 | mod = _require_py( spec, options ) 113 | elif lang == 'c': 114 | mod = _require_c( spec, options ) 115 | else: 116 | raise RequireError, "options.language must be one of [javascript,python,c]" 117 | 118 | __ModuleCache[spec] = mod 119 | return mod 120 | 121 | 122 | JsCode = {} 123 | def _file_data( filename ): 124 | global JsCode 125 | if os.access( filename, os.R_OK ): 126 | modtime = os.stat(filename).st_mtime 127 | if filename in JsCode: 128 | (mt, code) = JsCode[filename] 129 | if mt == modtime: 130 | return code 131 | code = open(filename).read() 132 | JsCode[filename] = (modtime,code) 133 | return code 134 | 135 | 136 | 137 | def _require_c( filename, options ): 138 | 139 | def _get(opt,k,defval): 140 | if hasattr(opt,k): 141 | return getattr(opt,k) 142 | return defval 143 | 144 | tool = _get(options, 'c_tool','PyInline') 145 | library_dirs = _get(options, 'libdirs',None) 146 | libraries = _get(options,'libs',None) 147 | includes = _get(options, 'includes',None) 148 | 149 | if tool == 'PyInline': 150 | extras = {} 151 | if library_dirs: 152 | extras['library_dirs'] = library_dirs 153 | if libraries: 154 | extras['libraries'] = libraries 155 | if includes: 156 | if os.name == 'posix': 157 | os.environ['GCC_INCLUDE_DIR'] = ':'.join(includes) 158 | else: 159 | os.environ['GCC_INCLUDE_DIR'] = ';'.join(includes) 160 | mod = PyV8.JSClass() 161 | data = _file_data( filename ) 162 | if data: 163 | 164 | PyInline.build(code=data, targetmodule=mod, language="C", **extras) 165 | return mod 166 | else: 167 | raise RequireError, "Unable to locate or read %s" % filename 168 | 169 | elif tool == 'ctypesgen': 170 | cmd = "ctypesgen.py %s " % filename 171 | if library_dirs: 172 | for x in library_dirs: 173 | cmd += " --libdir=%s " % x 174 | if libraries: 175 | for x in libraries: 176 | cmd += " --library=%s " % x 177 | if includes: 178 | for x in includes: 179 | cmd += " --includedir=%s " % x 180 | 181 | modname = filename.split(os.sep)[-1].split('.')[0] 182 | modfile = CTYPES_MODULES_PATH+"/%s.py" % modname 183 | 184 | cmd += " -o %s " % modfile 185 | logging.info( cmd ) 186 | os.system(cmd) 187 | 188 | m = __import__(modname) 189 | return m 190 | 191 | 192 | 193 | def _require_py( filename, options ): 194 | # behavior is the same as python import only we are using the RequiredPath 195 | # in conjuncture with sys.path 196 | if filename[-3:] == ".py": 197 | filename = filename[:-3] 198 | 199 | if len(filename.split(os.sep)) > 1: 200 | 201 | oldpath = sys.path 202 | p = filename.split(os.sep) 203 | sys.path = [os.sep.join(p[:-1])] + sys.path 204 | m = __import__( p[-1] ) 205 | sys.path = oldpath 206 | 207 | return m 208 | else: 209 | return __import__( filename ) 210 | 211 | 212 | def _require_js( filename, options ): 213 | # check search path for 214 | global RequirePath 215 | 216 | if filename[-3:] != ".js": 217 | filename += ".js" 218 | 219 | 220 | if filename[0] == '/': 221 | # Then this is an absolute path .. 222 | pathname = filename 223 | data = _file_data( pathname ) 224 | if not data: 225 | raise RequireError, \ 226 | "Path %s either does not exist or not readable" % pathname 227 | else: 228 | data = None 229 | # search for filename within predefined search path 230 | for path in RequirePath: 231 | pathname = path + filename 232 | data = _file_data( pathname ) 233 | if data: 234 | break # terminate search 235 | 236 | if data: 237 | # evaluate module and move variables/functions to a 238 | # object named the same as the file without the extension. 239 | # So if fubar.js contains foo(), then we define 240 | # a global object fubar and add foo as a member 241 | # like this: fubar.foo 242 | 243 | # local context used as a sandbox for evaluating modules. 244 | with PyV8.JSContext( jsapi.HandlerAPI() ) as context: 245 | # inject 'module' object into javascript. 246 | context.locals.module = { 247 | 'exports': {}, 248 | 'id': filename, 249 | 'filename': filename, 250 | 'loaded': False, 251 | 'parent': None, 252 | 'children': [] 253 | } 254 | prev_namespace = set(dir(context.locals)) 255 | 256 | try: 257 | # execute javascript code in module. 258 | context.eval( data ) 259 | except: 260 | et, ev, e_tb = sys.exc_info() 261 | msg = "[%s]\n\t %s" % ( pathname, ev ) 262 | raise RequireError, msg 263 | 264 | mod = PyV8.JSClass() 265 | if len(context.locals.module.exports) > 0: 266 | for (n,v) in dict(context.locals.module.exports).items(): 267 | setattr(mod,n,v) 268 | else: 269 | curr_namespace = set(dir(context.locals)) 270 | for n in list(curr_namespace - prev_namespace): 271 | v = getattr(context.locals,n) 272 | setattr(mod,n,v) 273 | delattr(context.locals,n) 274 | return mod 275 | else: 276 | msgfmt = "Unable to find %s or was not readable in any search path %s" 277 | msg = msgfmt % (filename,str(RequirePath)) 278 | raise RequireError, msg 279 | 280 | 281 | 282 | 283 | def unittest(): 284 | options = PyV8.JSClass() 285 | 286 | """ 287 | try: 288 | m = require( "../../test/data/bad" , options ) 289 | except: 290 | print "got expected error", sys.exc_info() 291 | 292 | options.language = "python" 293 | m = require("../../test/data/junk.py", options ) 294 | assert 11 == m.pyfunc(10) 295 | 296 | m = require("os", options) 297 | assert '.' == m.curdir 298 | 299 | options.language = "c" 300 | m = require("../../test/data/test.c",options) 301 | print m.my_add( 10, 20.0 ) 302 | 303 | print "JsCode:", 304 | print JsCode 305 | """ 306 | 307 | """ 308 | options.language = "c" 309 | options.c_tool = "ctypesgen" 310 | options.libs = ["/usr/lib/x86_64-linux-gnu/libglib-2.0.so"] 311 | 312 | m = require("/usr/include/stdio.h", options) 313 | m.printf("--- hello from libc") 314 | """ 315 | 316 | 317 | 318 | 319 | if __name__ == '__main__': 320 | unittest() 321 | -------------------------------------------------------------------------------- /dnshell.py: -------------------------------------------------------------------------------- 1 | import PyV8 2 | import sys 3 | from dnlib.jsapi import RootAPI 4 | import os 5 | import readline 6 | 7 | histfile = os.path.join(os.path.expanduser("~"), ".dn_shell_hist") 8 | try: 9 | readline.read_history_file(histfile) 10 | except IOError: 11 | pass 12 | import atexit 13 | atexit.register(readline.write_history_file, histfile) 14 | del os, histfile 15 | 16 | Version = "0.1" 17 | 18 | class Shell(object): 19 | def __init__(self): 20 | self.ctxt = PyV8.JSContext( RootAPI() ) 21 | self.ctxt.enter() 22 | 23 | def evaluate(self, line): 24 | return self.ctxt.eval( line ) 25 | 26 | def __del__(self): 27 | self.ctxt.leave() 28 | 29 | def run(): 30 | sh = Shell() 31 | print """ 32 | Welcome to the Interactive dreadnought-js shell version %s" 33 | 34 | This is a simple javascript shell that lets you explore the dreadnought 35 | API. 36 | 37 | 38 | To enter multiline statements end the line with '\\', so 39 | var x = \\ 40 | {\\ 41 | a: 10\\ 42 | }; 43 | gets evaluated as 'var x = { a: 10 };' 44 | 45 | 46 | To exit the shell press Control-D: 47 | 48 | 49 | """ % Version 50 | 51 | buf = "" 52 | while True: 53 | try: 54 | if len(buf) == 0: 55 | prompt = ">" 56 | else: 57 | prompt = " " 58 | 59 | line = raw_input( prompt ) 60 | except EOFError: 61 | break 62 | 63 | if len(line) == 0: 64 | continue 65 | 66 | elif line[-1] == '\\': 67 | buf += line[:-1] 68 | elif len(buf) > 0: 69 | buf += line 70 | sh.evaluate(buf) 71 | buf = "" 72 | else: 73 | try: 74 | sh.evaluate(line) 75 | except: 76 | print sys.exc_info()[1] 77 | 78 | if __name__ == '__main__': 79 | run() 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /dreadnought.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from dnlib import jsapi 4 | import dnshell 5 | import argparse 6 | import logging 7 | 8 | DnDescription = \ 9 | """ 10 | Dreadnought is a synthesis of Python, Cherrypy, and the V8 javascript engine 11 | allowing javascript developers to built web applications that can incorporate 12 | the 1000's of mature libraries written in Python and C over the decades in 13 | a seamless fashion. 14 | """ 15 | 16 | if __name__ == '__main__': 17 | parser = argparse.ArgumentParser( 18 | description=DnDescription, 19 | epilog="Version %s" % jsapi.Version 20 | ) 21 | parser.add_argument("--logfile", 22 | help="the pathname of the log file; the default is stdout", 23 | default="/dev/stdout" 24 | ) 25 | parser.add_argument("--loglevel", 26 | help="logfilename, default is stdout", 27 | default="DEBUG" 28 | ) 29 | 30 | parser.add_argument("execute", 31 | nargs="*", 32 | help="file to be executed, if not provided then it invokes an interactive shell", 33 | default=None 34 | ) 35 | 36 | 37 | 38 | args = vars(parser.parse_args()) 39 | if args['execute']: 40 | jsapi.run( args['execute'][0], args ) 41 | else: 42 | dnshell.run() 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschere/dreadnought-js/668affc5d1f3b37750c398aaaf9501b6e7e22698/favicon.ico -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # install dependancies 4 | sudo pip install -r ./requirements.txt 5 | 6 | # setup directory structures 7 | mkdir -p $HOME/.dreadnought-js/modules 8 | mkdir -p $HOME/.dreadnought-js/.python-ctypes-modules 9 | cp favicon.ico $HOME/.dreadnought-js 10 | 11 | # build app 12 | python ./setup.py build 13 | 14 | # install 15 | sudo python ./setup.py install && sudo cp dn-env.sh dn dreadnought.py /usr/local/bin 16 | 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | CherryPy>=3.6.0 2 | PyV8>=1.0-dev 3 | Routes>=2.1 4 | ctypesgen>=0.0 5 | coloredlogs>=0.8 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | import sys 5 | import os 6 | 7 | 8 | 9 | setup( 10 | name="dreadnought-js", 11 | description = 'Dreadnought is a synthesis of cherrypy and JavaScript to create a full featured web server for JavaScript applications', 12 | version="0.1.1", 13 | author="dschere", 14 | author_email="dave.avantgarde@gmail.com", 15 | url="https://github.com/dschere/dreadnought-js", 16 | download_url = "https://github.com/dschere/dreadnought -js/tarball/0.1", 17 | packages =["dnlib","dnlib/PyInline"] , 18 | py_modules=['dnshell','dreadnought'], 19 | package_data={'': ['CHANGES.txt','LICENSE.txt','dn-env.sh','install.sh','favicon.ico','dn']}, 20 | keywords = ['webserver', 'JavaScript','cherrypy'] 21 | ) 22 | 23 | -------------------------------------------------------------------------------- /test/require_test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var c = require("test/trivial.c", { language: "C" } ); 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/trivial.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | int mult(int x, int y) { return x * y; } 4 | 5 | 6 | --------------------------------------------------------------------------------