From 1bfdfdeb23ca684b604bcea1e62ed25dfef08dda Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Tue, 19 Jul 2011 22:37:47 +0100 Subject: [PATCH 1/7] Modernised build system, adding dependencies on ujson and omnijson --- distribute_setup.py | 485 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 11 +- 2 files changed, 492 insertions(+), 4 deletions(-) create mode 100644 distribute_setup.py diff --git a/distribute_setup.py b/distribute_setup.py new file mode 100644 index 0000000..bbb6f3c --- /dev/null +++ b/distribute_setup.py @@ -0,0 +1,485 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import sys +import time +import fnmatch +import tempfile +import tarfile +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.6.19" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install'): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + finally: + os.chdir(old_wd) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>="+version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + +def _patch_file(path, content): + """Will backup the file then patch it""" + existing_content = open(path).read() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + +def _same_content(path, content): + return open(path).read() == content + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s into %s', path, new_name) + os.rename(path, new_name) + return new_name + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Removing elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info) + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install')+1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index+1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', + replacement=False)) + except TypeError: + # old distribute API + setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patched done.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def main(argv, version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + tarball = download_setuptools() + _install(tarball) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/setup.py b/setup.py index 49d30ec..a810320 100644 --- a/setup.py +++ b/setup.py @@ -13,12 +13,15 @@ limitations under the License. """ -import distutils.core +from distribute_setup import use_setuptools +use_setuptools() -distutils.core.setup( - name = "jsonrpclib", +from setuptools import setup, find_packages + +setup(name = "jsonrpclib", version = "0.1.3", - packages = ["jsonrpclib"], + packages = find_packages(), + install_requires=['ujson', 'omnijson'], author = "Josh Marshall", author_email = "catchjosh@gmail.com", url = "http://github.com/joshmarshall/jsonrpclib/", From 9d633d07e88b60b6d0d9e347b5ba2e9204edea41 Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Tue, 19 Jul 2011 22:38:21 +0100 Subject: [PATCH 2/7] Don't silently lose exceptions thrown during a notify RPC --- jsonrpclib/SimpleJSONRPCServer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py index d76da73..8a9ac6d 100644 --- a/jsonrpclib/SimpleJSONRPCServer.py +++ b/jsonrpclib/SimpleJSONRPCServer.py @@ -96,6 +96,8 @@ def _marshaled_single_dispatch(self, request): try: response = self._dispatch(method, params) except: + if 'id' not in request.keys() or request['id'] == None: + raise exc_type, exc_value, exc_tb = sys.exc_info() fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) return fault.response() From 7771c67ff0f63fda20a57cbdcfe7ea8d357185ee Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Tue, 19 Jul 2011 22:38:43 +0100 Subject: [PATCH 3/7] Use omnijson to import json --- jsonrpclib/jsonrpc.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/jsonrpclib/jsonrpc.py b/jsonrpclib/jsonrpc.py index e11939a..0f77d83 100644 --- a/jsonrpclib/jsonrpc.py +++ b/jsonrpclib/jsonrpc.py @@ -64,19 +64,7 @@ # JSON library importing cjson = None json = None -try: - import cjson -except ImportError: - try: - import json - except ImportError: - try: - import simplejson as json - except ImportError: - raise ImportError( - 'You must have the cjson, json, or simplejson ' + - 'module(s) available.' - ) +import omnijson as json IDCHARS = string.ascii_lowercase+string.digits From 87d9a0b3c49944c350450ea0e1f27b511298a345 Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Tue, 19 Jul 2011 22:53:53 +0100 Subject: [PATCH 4/7] Added a JSON-RPC v2 supporting client in Javascript --- js/v2client.js | 1475 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1475 insertions(+) create mode 100644 js/v2client.js diff --git a/js/v2client.js b/js/v2client.js new file mode 100644 index 0000000..e0ee33a --- /dev/null +++ b/js/v2client.js @@ -0,0 +1,1475 @@ +/* To whom it may concern .... + +I cobbled the following together as a quick and nasty JSON-RPC v2 client +for use with the Python jsonrpclib. Believe it or not, there aren't any +notify call supporting Javascript clients out there (that I could find at +least), so the below solves a useful problem. + +The below comes from json.org (for JSON.stringify and JSON.parse where the +browser does not natively support these). And a heavily modified JSON-RPC +client taken from http://www.jabsorb.org which cuts out the Java support, +uses native JSON instead of its own custom parser which used eval() (NAUGHTY!), +adds support for notify ops which are sent async, and says it supports +JSON-RPC v2. + +The below is fairly swift for very large data transfers. In fact, right now +it can easily overload Python's jsonrpclib even when it's using ujson :) +I'd say you might have to PyPy your Python to catch up. + +Best of luck to anyone using this. And no warranties express or implied! + +Niall Douglas +http://www.nedproductions.biz/ +July 2011 +*/ + + +/* +http://www.JSON.org/json2.js +2011-02-23 + +Public Domain. + +NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + +See http://www.JSON.org/js.html + + +This code should be minified before deployment. +See http://javascript.crockford.com/jsmin.html + +USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +NOT CONTROL. + + +This file creates a global JSON object containing two methods: stringify +and parse. + +JSON.stringify(value, replacer, space) +value any JavaScript value, usually an object or array. + +replacer an optional parameter that determines how object +values are stringified for objects. It can be a +function or an array of strings. + +space an optional parameter that specifies the indentation +of nested structures. If it is omitted, the text will +be packed without extra whitespace. If it is a number, +it will specify the number of spaces to indent at each +level. If it is a string (such as '\t' or ' '), +it contains the characters used to indent at each level. + +This method produces a JSON text from a JavaScript value. + +When an object value is found, if the object contains a toJSON +method, its toJSON method will be called and the result will be +stringified. A toJSON method does not serialize: it returns the +value represented by the name/value pair that should be serialized, +or undefined if nothing should be serialized. The toJSON method +will be passed the key associated with the value, and this will be +bound to the value + +For example, this would serialize Dates as ISO strings. + +Date.prototype.toJSON = function (key) { +function f(n) { +// Format integers to have at least two digits. +return n < 10 ? '0' + n : n; +} + +return this.getUTCFullYear() + '-' + +f(this.getUTCMonth() + 1) + '-' + +f(this.getUTCDate()) + 'T' + +f(this.getUTCHours()) + ':' + +f(this.getUTCMinutes()) + ':' + +f(this.getUTCSeconds()) + 'Z'; +}; + +You can provide an optional replacer method. It will be passed the +key and value of each member, with this bound to the containing +object. The value that is returned from your method will be +serialized. If your method returns undefined, then the member will +be excluded from the serialization. + +If the replacer parameter is an array of strings, then it will be +used to select the members to be serialized. It filters the results +such that only members with keys listed in the replacer array are +stringified. + +Values that do not have JSON representations, such as undefined or +functions, will not be serialized. Such values in objects will be +dropped; in arrays they will be replaced with null. You can use +a replacer function to replace those with JSON values. +JSON.stringify(undefined) returns undefined. + +The optional space parameter produces a stringification of the +value that is filled with line breaks and indentation to make it +easier to read. + +If the space parameter is a non-empty string, then that string will +be used for indentation. If the space parameter is a number, then +the indentation will be that many spaces. + +Example: + +text = JSON.stringify(['e', {pluribus: 'unum'}]); +// text is '["e",{"pluribus":"unum"}]' + + +text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); +// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +text = JSON.stringify([new Date()], function (key, value) { +return this[key] instanceof Date ? +'Date(' + this[key] + ')' : value; +}); +// text is '["Date(---current time---)"]' + + +JSON.parse(text, reviver) +This method parses a JSON text to produce an object or array. +It can throw a SyntaxError exception. + +The optional reviver parameter is a function that can filter and +transform the results. It receives each of the keys and values, +and its return value is used instead of the original value. +If it returns what it received, then the structure is not modified. +If it returns undefined then the member is deleted. + +Example: + +// Parse the text. Values that look like ISO date strings will +// be converted to Date objects. + +myData = JSON.parse(text, function (key, value) { +var a; +if (typeof value === 'string') { +a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +if (a) { +return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], ++a[5], +a[6])); +} +} +return value; +}); + +myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { +var d; +if (typeof value === 'string' && +value.slice(0, 5) === 'Date(' && +value.slice(-1) === ')') { +d = new Date(value.slice(5, -1)); +if (d) { +return d; +} +} +return value; +}); + + +This is a reference implementation. You are free to copy, modify, or +redistribute. +*/ + +/*jslint evil: true, strict: false, regexp: false */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, +call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, +getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, +lastIndex, length, parse, prototype, push, replace, slice, stringify, +test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON; +if (!JSON) { + JSON = {}; +} + +(function () { + "use strict"; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : gap ? + '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : gap ? + '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : + '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); + + + + + + + + + + + + + +/* + * jabsorb - a Java to JavaScript Advanced Object Request Broker + * http://www.jabsorb.org + * + * Copyright 2007-2009 The jabsorb team + * Copyright (c) 2005 Michael Clark, Metaparadigm Pte Ltd + * Copyright (c) 2003-2004 Jan-Klaas Kollhof + * + * This code is based on original code from the json-rpc-java library + * which was originally based on Jan-Klaas' JavaScript o lait library + * (jsolait). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * JSONRpcClient constructor + * + * @param callback|methods - the function to call once the rpc list methods has completed. + * if this argument is omitted completely, then the JSONRpcClient + * is constructed synchronously. + * if this arguement is an array then it is the list of methods + * that can be invoked on the server (and the server will not + * be queried for that information) + * + * @param serverURL - path to JSONRpcServlet on server. + * @param user + * @param pass + * @param objectID + * @param javaClass + * @param JSONRPCType + * + */ +function JSONRpcClient() +{ + var arg_shift = 0, req, _function, methods, self, name, arg0type= (typeof arguments[0]), doListMethods=true; + + //If a call back is being used grab it + if (arg0type === "function") + { + this.readyCB = arguments[0]; + arg_shift++; + } + // if it's an array then just do add methods directly + else if (arguments[0] && arg0type === "object" && arguments[0].length) + { + this._addMethods(arguments[0]); // go ahead and add the methods directly + arg_shift++; + doListMethods=false; + } + + //The next 3 args are passed to the http request + this.serverURL = arguments[arg_shift]; + this.user = arguments[arg_shift + 1]; + this.pass = arguments[arg_shift + 2]; + this.objectID=0; + + if (doListMethods) + { + //Add the listMethods system methods + this._addMethods(["system.listMethods"]); + //Make the call to list the methods + req = JSONRpcClient._makeRequest(this,"system.listMethods", []); + //If a callback was added to the constructor, call it + if(this.readyCB) + { + self = this; + req.cb = function(result, e) + { + if(!e) + { + self._addMethods(result); + } + self.readyCB(result, e); + }; + } + + if(!this.readyCB) + { + methods = JSONRpcClient._sendRequest(this,req); + this._addMethods(methods); + } + else + { + JSONRpcClient.async_requests.push(req); + JSONRpcClient.kick_async(); + } + } +} + +/** + * Creates a new callable proxy (reference). + * + * @param objectID The id of the object as determined by the server + * @param javaClass The package+classname of the object + * @return a new callable proxy object + */ +JSONRpcClient.prototype.createCallableProxy=function(objectID,javaClass) +{ + var cp,req,methodNames,name,i; + + cp = new JSONRPCCallableProxy(objectID,javaClass); + //Then add all the cached methods to it. + for (name in JSONRpcClient.knownClasses[javaClass]) + { + //Change the this to the object that will be calling it + cp[name]=JSONRpcClient.bind( + JSONRpcClient.knownClasses[javaClass][name],cp); + } + return cp; +}; + +/* JSONRpcClient constructor */ + +function JSONRPCCallableProxy() +{ + //A unique identifier which the identity hashcode of the object on the server, if this is a reference type + this.objectID = arguments[0]; + //The full package+classname of the object + this.javaClass = arguments[1]; + this.JSONRPCType = "CallableReference"; +} + +//This is a static variable that maps className to a map of functions names to +//calls, ie Map knownClasses> +JSONRpcClient.knownClasses = {}; + +/* JSONRpcCLient.Exception */ +JSONRpcClient.Exception = function (errorObject) +{ + var m; + for( var prop in errorObject) + { + if (errorObject.hasOwnProperty(prop)) + { + this[prop] = errorObject[prop]; + } + } + if (this.trace) + { + m = this.trace.match(/^([^:]*)/); + if (m) + { + this.name = m[0]; + } + } + if (!this.name) + { + this.name = "JSONRpcClientException"; + } +}; + +//Error codes that are the same as on the bridge +JSONRpcClient.Exception.CODE_REMOTE_EXCEPTION = 490; +JSONRpcClient.Exception.CODE_ERR_CLIENT = 550; +JSONRpcClient.Exception.CODE_ERR_PARSE = 590; +JSONRpcClient.Exception.CODE_ERR_NOMETHOD = 591; +JSONRpcClient.Exception.CODE_ERR_UNMARSHALL = 592; +JSONRpcClient.Exception.CODE_ERR_MARSHALL = 593; + +JSONRpcClient.Exception.prototype = new Error(); + +JSONRpcClient.Exception.prototype.toString = function (code, msg) +{ + var str=""; + if(this.name) + { + str+=this.name; + } + if(this.message) + { + str+=": "+this.message; + } + if(str.length==0) + { + str="no exception information given"; + } + return str; +}; + + +/* Default top level exception handler */ + +JSONRpcClient.default_ex_handler = function (e) +{ + // unhandled exception thrown in jsonrpc handler + var a,str=""; + for(a in e) + { + str+=a +"\t"+e[a]+"\n"; + } + alert(str); +}; + + +/* Client settable variables */ + +JSONRpcClient.toplevel_ex_handler = JSONRpcClient.default_ex_handler; +JSONRpcClient.profile_async = false; +JSONRpcClient.max_req_active = 1; +JSONRpcClient.requestId = 1; + +// if this is true, circular references in the object graph are fixed up +// if this is false, circular references cause an exception to be thrown +JSONRpcClient.fixupCircRefs = false; + +// if this is true, duplicate objects in the object graph are optimized +// if it's false, then duplicate objects are "re-serialized" +JSONRpcClient.fixupDuplicates = false; + +/** + * if true, java.util.Date object are unmarshalled to javascript dates + * if false, no customized unmarshalling for dates is done + */ +JSONRpcClient.transformDates = false; + +/** + * Used to bind the this of the serverMethodCaller() (see below) which is to be + * bound to the right object. This is needed as the serverMethodCaller is + * called only once in createMethod and is then assigned to multiple + * CallableReferences are created. + */ +JSONRpcClient.bind=function(functionName,context) +{ + return function() { + return functionName.apply(context, arguments); + }; +}; + +/* + * This creates a method that points to the serverMethodCaller and binds it + * with the correct methodName. + */ +JSONRpcClient._createMethod = function (client,methodName, isnotify) +{ + //This function is what the user calls. + //This function uses a closure on methodName to ensure that the function + //always has the same name, but can take different arguments each call. + //Each time it is added to an object this should be set with bind() + var serverMethodCaller= function() + { + var args = [], + callback; + for (var i = 0; i < arguments.length; i++) + { + args.push(arguments[i]); + } + if (typeof args[0] == "function") + { + callback = args.shift(); + } + //console.log("calling ", methodName, args, "callback=",callback, "isnotify=", isnotify); + var req = JSONRpcClient._makeRequest(this, methodName, args, this.objectID,callback, isnotify); + if (!callback) + { + return JSONRpcClient._sendRequest(client, req, isnotify); + } + else + { + //when there is a callback, add the req to the list + JSONRpcClient.async_requests.push(req); + JSONRpcClient.kick_async(); + return req.requestId; + } + }; + + return serverMethodCaller; +}; + +/** + * Creates a new object from the bridge. A callback may optionally be given as + * the first argument to make this an async call. + * + * @param callback (optional) + * @param constructorName The name of the class to create, which should be + * registered with JSONRPCBridge.registerClass() + * @param _args The arguments the constructor takes + * @return the new object if sync, the request id if async. + */ +JSONRpcClient.prototype.createObject = function () +{ + var args = [], + callback = null, + constructorName, + _args, + req; + for(var i=0;i 0) + { + res = JSONRpcClient.async_responses.shift(); + if (res.canceled) + { + continue; + } + if (res.profile) + { + res.profile.dispatch = new Date(); + } + try + { + res.cb(res.result, res.ex, res.profile); + } + catch(e) + { + JSONRpcClient.toplevel_ex_handler(e); + } + } + + while (JSONRpcClient.async_requests.length > 0 && + JSONRpcClient.num_req_active < JSONRpcClient.max_req_active) + { + req = JSONRpcClient.async_requests.shift(); + if (req.canceled) + { + continue; + } + JSONRpcClient._sendRequest(req.client, req); + } +}; + +JSONRpcClient.kick_async = function () +{ + if (!JSONRpcClient.async_timeout) + { + JSONRpcClient.async_timeout = setTimeout(JSONRpcClient._async_handler, 0); + } +}; + +JSONRpcClient.cancelRequest = function (requestId) +{ + /* If it is in flight then mark it as canceled in the inflight map + and the XMLHttpRequest callback will discard the reply. */ + if (JSONRpcClient.async_inflight[requestId]) + { + JSONRpcClient.async_inflight[requestId].canceled = true; + return true; + } + var i; + + /* If its not in flight yet then we can just mark it as canceled in + the the request queue and it will get discarded before being sent. */ + for (i in JSONRpcClient.async_requests) + { + if (JSONRpcClient.async_requests[i].requestId == requestId) + { + JSONRpcClient.async_requests[i].canceled = true; + return true; + } + } + + /* It may have returned from the network and be waiting for its callback + to be dispatched, so mark it as canceled in the response queue + and the response will get discarded before calling the callback. */ + for (i in JSONRpcClient.async_responses) + { + if (JSONRpcClient.async_responses[i].requestId == requestId) + { + JSONRpcClient.async_responses[i].canceled = true; + return true; + } + } + + return false; +}; + +JSONRpcClient._makeRequest = function (client,methodName, args,objectID,cb, isnotify) +{ + var req = {}; + req.client = client; + req.requestId = !isnotify ? JSONRpcClient.requestId++ : null; + + var obj = '{"jsonrpc":"2.0", "id":'+req.requestId+',"method":'; + + if ((objectID)&&(objectID>0)) + { + obj += "\".obj[" + objectID + "]." + methodName +"\""; + } + else + { + obj += "\"" + methodName + "\""; + } + +//TODO: i dont think this if works + if (cb) + { + req.cb = cb; + } + if (JSONRpcClient.profile_async) + { + req.profile = {submit: new Date() }; + } + + obj += ',"params":' + JSON.stringify(args)+'}'; + + req.data = obj; + + return req; +}; + +JSONRpcClient._sendRequest = function (client,req, isnotify) +{ + var http; + if (req.profile) + { + req.profile.start = new Date(); + } + + /* Get free http object from the pool */ + http = JSONRpcClient.poolGetHTTPRequest(); + JSONRpcClient.num_req_active++; + + /* Send the request */ + var isasync=(!!req.cb) || (!!isnotify); + //console.log("async post=",isasync, "req.cb", req.cb, "isnotify", isnotify); + http.open("POST", client.serverURL, isasync, client.user, client.pass); + + /* setRequestHeader is missing in Opera 8 Beta */ + try + { + http.setRequestHeader("Content-type", "application/json-rpc"); + } + catch(e) + { + } + + /* Construct call back if we have one */ + if (req.cb) + { + http.onreadystatechange = function() + { + var res; + if (http.readyState == 4) + { + http.onreadystatechange = function () + { + }; + res = {cb: req.cb, result: null, ex: null}; + if (req.profile) + { + res.profile = req.profile; + res.profile.end = new Date(); + } + else + { + res.profile = false; + } + try + { + res.result = client._handleResponse(http); + } + catch(e) + { + res.ex = e; + } + if (!JSONRpcClient.async_inflight[req.requestId].canceled) + { + JSONRpcClient.async_responses.push(res); + } + delete JSONRpcClient.async_inflight[req.requestId]; + JSONRpcClient.kick_async(); + } + }; + } + else + { + http.onreadystatechange = function() + { + }; + } + + if(req.requestId!=null) JSONRpcClient.async_inflight[req.requestId] = req; + + try + { + http.send(req.data); + } + catch(e) + { + JSONRpcClient.poolReturnHTTPRequest(http); + JSONRpcClient.num_req_active--; + throw new JSONRpcClient.Exception( + { + code: JSONRpcClient.Exception.CODE_ERR_CLIENT, + message: "Connection failed" + } ); + } + + if (!req.cb && req.requestId!=null) + { + delete JSONRpcClient.async_inflight[req.requestId]; + return client._handleResponse(http); + } + return null; +}; + +JSONRpcClient.prototype._handleResponse = function (http) +{ + /* Get the charset */ + if (!this.charset) + { + this.charset = JSONRpcClient._getCharsetFromHeaders(http); + } + + /* Get request results */ + var status, statusText, data; + try + { + status = http.status; + statusText = http.statusText; + data = http.responseText; + } + catch(e) + { +/* + todo: don't throw away the original error information here!! + todo: and everywhere else, as well! + if (e instanceof Error) + { + alert (e.name + ": " + e.message); + } +*/ + JSONRpcClient.poolReturnHTTPRequest(http); + JSONRpcClient.num_req_active--; + JSONRpcClient.kick_async(); + throw new JSONRpcClient.Exception( + { + code: JSONRpcClient.Exception.CODE_ERR_CLIENT, + message: "Connection failed" + }); + } + + /* Return http object to the pool; */ + JSONRpcClient.poolReturnHTTPRequest(http); + JSONRpcClient.num_req_active--; + + /* Unmarshall the response */ + if (status != 200) + { + throw new JSONRpcClient.Exception({ code: status, message: statusText }); + }; + return this.unmarshallResponse(data); +}; + +JSONRpcClient.prototype.unmarshallResponse=function(data) +{ + /** + * Apply fixups. + * @param obj root object to apply fixups against. + * @param fixups array of fixups to apply. each element of this array is a 2 element array, containing + * the array with the fixup location followed by an array with the original location to fix up into the fixup + * location. + */ + function applyFixups(obj, fixups) + { + function findOriginal(ob, original) + { + for (var i=0,j=original.length;i= JSONRpcClient.http_max_spare) + { + delete http; + } + else + { + JSONRpcClient.http_spare.push(http); + } +}; + +/* the search order here may seem strange, but it's + actually what Microsoft recommends */ +JSONRpcClient.msxmlNames = [ + "MSXML2.XMLHTTP.6.0", + "MSXML2.XMLHTTP.3.0", + "MSXML2.XMLHTTP", + "MSXML2.XMLHTTP.5.0", + "MSXML2.XMLHTTP.4.0", + "Microsoft.XMLHTTP" ]; + +JSONRpcClient.getHTTPRequest = function () +{ + /* Look for a browser native XMLHttpRequest implementation (Mozilla/IE7/Opera/Safari, etc.) */ + try + { + JSONRpcClient.httpObjectName = "XMLHttpRequest"; + return new XMLHttpRequest(); + } + catch(e) + { + } + + /* Microsoft MSXML ActiveX for IE versions < 7 */ + for (var i = 0; i < JSONRpcClient.msxmlNames.length; i++) + { + try + { + JSONRpcClient.httpObjectName = JSONRpcClient.msxmlNames[i]; + return new ActiveXObject(JSONRpcClient.msxmlNames[i]); + } + catch (e) + { + } + } + + /* None found */ + JSONRpcClient.httpObjectName = null; + throw new JSONRpcClient.Exception( + { + code: 0, + message: "Can't create XMLHttpRequest object" + }); +}; + From 710576bb614322a093dd10759c5158296ca142f5 Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Wed, 20 Jul 2011 00:45:12 +0100 Subject: [PATCH 5/7] Not losing exceptions during notify ops now actually works --- jsonrpclib/SimpleJSONRPCServer.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py index 8a9ac6d..33e0e25 100644 --- a/jsonrpclib/SimpleJSONRPCServer.py +++ b/jsonrpclib/SimpleJSONRPCServer.py @@ -53,7 +53,7 @@ def __init__(self, encoding=None): allow_none=True, encoding=encoding) - def _marshaled_dispatch(self, data, dispatch_method = None): + def _marshaled_dispatch(self, data, dispatch_method = None, isNotification = [False]): response = None try: request = jsonrpclib.loads(data) @@ -72,7 +72,8 @@ def _marshaled_dispatch(self, data, dispatch_method = None): if type(result) is Fault: responses.append(result.response()) continue - resp_entry = self._marshaled_single_dispatch(req_entry) + isNotification[0] = 'id' not in request.keys() or request['id'] == None + resp_entry = self._marshaled_single_dispatch(isNotification[0], req_entry) if resp_entry is not None: responses.append(resp_entry) if len(responses) > 0: @@ -83,10 +84,11 @@ def _marshaled_dispatch(self, data, dispatch_method = None): result = validate_request(request) if type(result) is Fault: return result.response() - response = self._marshaled_single_dispatch(request) + isNotification[0] = 'id' not in request.keys() or request['id'] == None + response = self._marshaled_single_dispatch(isNotification[0], request) return response - def _marshaled_single_dispatch(self, request): + def _marshaled_single_dispatch(self, isNotification, request): # TODO - Use the multiprocessing and skip the response if # it is a notification # Put in support for custom dispatcher here @@ -94,14 +96,13 @@ def _marshaled_single_dispatch(self, request): method = request.get('method') params = request.get('params') try: - response = self._dispatch(method, params) + response = self._dispatch(method, params, isNotification) except: - if 'id' not in request.keys() or request['id'] == None: - raise + if isNotification: raise exc_type, exc_value, exc_tb = sys.exc_info() fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) return fault.response() - if 'id' not in request.keys() or request['id'] == None: + if isNotification: # It's a notification return None try: @@ -115,7 +116,7 @@ def _marshaled_single_dispatch(self, request): fault = Fault(-32603, '%s:%s' % (exc_type, exc_value)) return fault.response() - def _dispatch(self, method, params): + def _dispatch(self, method, params, isNotification): func = None try: func = self.funcs[method] @@ -142,6 +143,7 @@ def _dispatch(self, method, params): except TypeError: return Fault(-32602, 'Invalid parameters.') except: + if isNotification: raise err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) fault = jsonrpclib.Fault(-32603, 'Server error: %s' % @@ -157,6 +159,7 @@ def do_POST(self): if not self.is_rpc_path_valid(): self.report_404() return + isNotification=[False] try: max_chunk_size = 10*1024*1024 size_remaining = int(self.headers["content-length"]) @@ -166,9 +169,13 @@ def do_POST(self): L.append(self.rfile.read(chunk_size)) size_remaining -= len(L[-1]) data = ''.join(L) - response = self.server._marshaled_dispatch(data) + response = self.server._marshaled_dispatch(data, None, isNotification) + if isNotification[0]: + self.connection.shutdown(1) + return self.send_response(200) - except Exception, e: + except: # Exception, e: + if isNotification[0]: raise self.send_response(500) err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) From c848923a1d9b5030b77d369b35aa27fcca0fba9b Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Wed, 20 Jul 2011 00:57:04 +0100 Subject: [PATCH 6/7] Return HTTP status code even with notify ops --- jsonrpclib/SimpleJSONRPCServer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py index 33e0e25..adc9a64 100644 --- a/jsonrpclib/SimpleJSONRPCServer.py +++ b/jsonrpclib/SimpleJSONRPCServer.py @@ -170,13 +170,13 @@ def do_POST(self): size_remaining -= len(L[-1]) data = ''.join(L) response = self.server._marshaled_dispatch(data, None, isNotification) + self.send_response(200) if isNotification[0]: self.connection.shutdown(1) return - self.send_response(200) except: # Exception, e: - if isNotification[0]: raise self.send_response(500) + if isNotification[0]: raise err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string) From 6280c72c946fd836e20cbc1adc866b5490e0d0cd Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Wed, 20 Jul 2011 01:06:40 +0100 Subject: [PATCH 7/7] Flush the HTTP status code before early outing under notify ops --- jsonrpclib/SimpleJSONRPCServer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jsonrpclib/SimpleJSONRPCServer.py b/jsonrpclib/SimpleJSONRPCServer.py index adc9a64..661fc11 100644 --- a/jsonrpclib/SimpleJSONRPCServer.py +++ b/jsonrpclib/SimpleJSONRPCServer.py @@ -172,11 +172,15 @@ def do_POST(self): response = self.server._marshaled_dispatch(data, None, isNotification) self.send_response(200) if isNotification[0]: + self.wfile.flush() self.connection.shutdown(1) return except: # Exception, e: self.send_response(500) - if isNotification[0]: raise + if isNotification[0]: + self.wfile.flush() + self.connection.shutdown(1) + raise err_lines = traceback.format_exc().splitlines() trace_string = '%s | %s' % (err_lines[-3], err_lines[-1]) fault = jsonrpclib.Fault(-32603, 'Server error: %s' % trace_string)