public code v1

This commit is contained in:
2026-05-22 11:14:29 +02:00
parent 427197ec5a
commit b8141736eb
28859 changed files with 575079 additions and 0 deletions
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
__all__ = ['modjy', 'modjy_exceptions', 'modjy_impl', 'modjy_log', 'modjy_params', 'modjy_publish', 'modjy_response', 'modjy_write', 'modjy_wsgi',]
Binary file not shown.
+121
View File
@@ -0,0 +1,121 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import jarray
import synchronize
import sys
import types
sys.add_package("javax.servlet")
sys.add_package("javax.servlet.http")
sys.add_package("org.python.core")
from modjy_exceptions import *
from modjy_log import *
from modjy_params import modjy_param_mgr, modjy_servlet_params
from modjy_wsgi import modjy_wsgi
from modjy_response import start_response_object
from modjy_impl import modjy_impl
from modjy_publish import modjy_publisher
from javax.servlet.http import HttpServlet
class modjy_servlet(HttpServlet, modjy_publisher, modjy_wsgi, modjy_impl):
def __init__(self):
HttpServlet.__init__(self)
def do_param(self, name, value):
if name[:3] == 'log':
getattr(self.log, "set_%s" % name)(value)
else:
self.params[name] = value
def process_param_container(self, param_container):
param_enum = param_container.getInitParameterNames()
while param_enum.hasMoreElements():
param_name = param_enum.nextElement()
self.do_param(param_name, param_container.getInitParameter(param_name))
def get_params(self):
self.process_param_container(self.servlet_context)
self.process_param_container(self.servlet)
def init(self, delegator):
self.servlet = delegator
self.servlet_context = self.servlet.getServletContext()
self.servlet_config = self.servlet.getServletConfig()
self.log = modjy_logger(self.servlet_context)
self.params = modjy_param_mgr(modjy_servlet_params)
self.get_params()
self.init_impl()
self.init_publisher()
import modjy_exceptions
self.exc_handler = getattr(modjy_exceptions, '%s_handler' % self.params['exc_handler'])()
def service (self, req, resp):
wsgi_environ = {}
try:
self.dispatch_to_application(req, resp, wsgi_environ)
except ModjyException, mx:
self.log.error("Exception servicing request: %s" % str(mx))
typ, value, tb = sys.exc_info()[:]
self.exc_handler.handle(req, resp, wsgi_environ, mx, (typ, value, tb) )
def get_j2ee_ns(self, req, resp):
return {
'servlet': self.servlet,
'servlet_context': self.servlet_context,
'servlet_config': self.servlet_config,
'request': req,
'response': resp,
}
def dispatch_to_application(self, req, resp, environ):
app_callable = self.get_app_object(req, environ)
self.set_wsgi_environment(req, resp, environ, self.params, self.get_j2ee_ns(req, resp))
response_callable = start_response_object(req, resp)
try:
app_return = self.call_application(app_callable, environ, response_callable)
if app_return is None:
raise ReturnNotIterable("Application returned None: must return an iterable")
self.deal_with_app_return(environ, response_callable, app_return)
except ModjyException, mx:
self.raise_exc(mx.__class__, str(mx))
except Exception, x:
self.raise_exc(ApplicationException, str(x))
def call_application(self, app_callable, environ, response_callable):
if self.params['multithread']:
return app_callable.__call__(environ, response_callable)
else:
return synchronize.apply_synchronized( \
app_callable, \
app_callable, \
(environ, response_callable))
def expand_relative_path(self, path):
if path.startswith("$"):
return self.servlet.getServletContext().getRealPath(path[1:])
return path
def raise_exc(self, exc_class, message):
self.log.error(message)
raise exc_class(message)
Binary file not shown.
@@ -0,0 +1,91 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import sys
import StringIO
import traceback
from java.lang import IllegalStateException
from java.io import IOException
from javax.servlet import ServletException
class ModjyException(Exception): pass
class ModjyIOException(ModjyException): pass
class ConfigException(ModjyException): pass
class BadParameter(ConfigException): pass
class ApplicationNotFound(ConfigException): pass
class NoCallable(ConfigException): pass
class RequestException(ModjyException): pass
class ApplicationException(ModjyException): pass
class StartResponseNotCalled(ApplicationException): pass
class StartResponseCalledTwice(ApplicationException): pass
class ResponseCommitted(ApplicationException): pass
class HopByHopHeaderSet(ApplicationException): pass
class WrongLength(ApplicationException): pass
class BadArgument(ApplicationException): pass
class ReturnNotIterable(ApplicationException): pass
class NonStringOutput(ApplicationException): pass
class exception_handler:
def handle(self, req, resp, environ, exc, exc_info):
pass
def get_status_and_message(self, req, resp, exc):
return resp.SC_INTERNAL_SERVER_ERROR, "Server configuration error"
#
# Special exception handler for testing
#
class testing_handler(exception_handler):
def handle(self, req, resp, environ, exc, exc_info):
typ, value, tb = exc_info
err_msg = StringIO.StringIO()
err_msg.write("%s: %s\n" % (typ, value,) )
err_msg.write(">Environment\n")
for k in environ.keys():
err_msg.write("%s=%s\n" % (k, repr(environ[k])) )
err_msg.write("<Environment\n")
err_msg.write(">TraceBack\n")
for line in traceback.format_exception(typ, value, tb):
err_msg.write(line)
err_msg.write("<TraceBack\n")
try:
status, message = self.get_status_and_message(req, resp, exc)
resp.setStatus(status)
resp.setContentLength(len(err_msg.getvalue()))
resp.getOutputStream().write(err_msg.getvalue())
except IllegalStateException, ise:
raise exc # Let the container deal with it
#
# Standard exception handler
#
class standard_handler(exception_handler):
def handle(self, req, resp, environ, exc, exc_info):
raise exc_info[0], exc_info[1], exc_info[2]
Binary file not shown.
+101
View File
@@ -0,0 +1,101 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import types
import sys
from modjy_exceptions import *
class modjy_impl:
def deal_with_app_return(self, environ, start_response_callable, app_return):
self.log.debug("Processing app return type: %s" % str(type(app_return)))
if isinstance(app_return, types.StringTypes):
raise ReturnNotIterable("Application returned object that was not an iterable: %s" % str(type(app_return)))
if type(app_return) is types.FileType:
pass # TBD: What to do here? can't call fileno()
if hasattr(app_return, '__len__') and callable(app_return.__len__):
expected_pieces = app_return.__len__()
else:
expected_pieces = -1
try:
try:
ix = 0
for next_piece in app_return:
if not isinstance(next_piece, types.StringTypes):
raise NonStringOutput("Application returned iterable containing non-strings: %s" % str(type(next_piece)))
if ix == 0:
# The application may have called start_response in the first iteration
if not start_response_callable.called:
raise StartResponseNotCalled("Start_response callable was never called.")
if not start_response_callable.content_length \
and expected_pieces == 1 \
and start_response_callable.write_callable.num_writes == 0:
# Take the length of the first piece
start_response_callable.set_content_length(len(next_piece))
start_response_callable.write_callable(next_piece)
ix += 1
if ix == expected_pieces:
break
if expected_pieces != -1 and ix != expected_pieces:
raise WrongLength("Iterator len() was wrong. Expected %d pieces: got %d" % (expected_pieces, ix) )
except AttributeError, ax:
if str(ax) == "__getitem__":
raise ReturnNotIterable("Application returned object that was not an iterable: %s" % str(type(app_return)))
else:
raise ax
except TypeError, tx:
raise ReturnNotIterable("Application returned object that was not an iterable: %s" % str(type(app_return)))
except ModjyException, mx:
raise mx
except Exception, x:
raise ApplicationException(x)
finally:
if hasattr(app_return, 'close') and callable(app_return.close):
app_return.close()
def init_impl(self):
self.do_j_env_params()
def add_packages(self, package_list):
packages = [p.strip() for p in package_list.split(';')]
for p in packages:
self.log.info("Adding java package %s to jython" % p)
sys.add_package(p)
def add_classdirs(self, classdir_list):
classdirs = [cd.strip() for cd in classdir_list.split(';')]
for cd in classdirs:
self.log.info("Adding directory %s to jython class file search path" % cd)
sys.add_classdir(cd)
def add_extdirs(self, extdir_list):
extdirs = [ed.strip() for ed in extdir_list.split(';')]
for ed in extdirs:
self.log.info("Adding directory %s for .jars and .zips search path" % ed)
sys.add_extdir(self.expand_relative_path(ed))
def do_j_env_params(self):
if self.params['packages']:
self.add_packages(self.params['packages'])
if self.params['classdirs']:
self.add_classdirs(self.params['classdirs'])
if self.params['extdirs']:
self.add_extdirs(self.params['extdirs'])
Binary file not shown.
+167
View File
@@ -0,0 +1,167 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
#
# This code adapted from the socket._fileobject class
#
import jarray
class modjy_input_object(object):
def __init__(self, servlet_inputstream, bufsize=8192):
self.istream = servlet_inputstream
self.buffer_size = bufsize
self.buffer = ""
def istream_read(self, n):
data = jarray.zeros(n, 'b')
m = self.istream.read(data)
if m == -1: # indicates EOF has been reached, so we just return the empty string
return ""
elif m <= 0:
return ""
if m < n:
data = data[:m]
return data.tostring()
def read(self, size=-1):
data = self.buffer
if size < 0:
# Read until EOF
buffers = []
if data:
buffers.append(data)
self.buffer = ""
recv_size = self.buffer_size
while True:
data = self.istream_read(recv_size)
if not data:
break
buffers.append(data)
return "".join(buffers)
else:
# Read until size bytes or EOF seen, whichever comes first
buf_len = len(data)
if buf_len >= size:
self.buffer = data[size:]
return data[:size]
buffers = []
if data:
buffers.append(data)
self.buffer = ""
while True:
left = size - buf_len
recv_size = max(self.buffer_size, left)
data = self.istream_read(recv_size)
if not data:
break
buffers.append(data)
n = len(data)
if n >= left:
self.buffer = data[left:]
buffers[-1] = data[:left]
break
buf_len += n
return "".join(buffers)
def readline(self, size=-1):
data = self.buffer
if size < 0:
# Read until \n or EOF, whichever comes first
nl = data.find('\n')
if nl >= 0:
nl += 1
self.buffer = data[nl:]
return data[:nl]
buffers = []
if data:
buffers.append(data)
self.buffer = ""
while True:
data = self.istream_read(self.buffer_size)
if not data:
break
buffers.append(data)
nl = data.find('\n')
if nl >= 0:
nl += 1
self.buffer = data[nl:]
buffers[-1] = data[:nl]
break
return "".join(buffers)
else:
# Read until size bytes or \n or EOF seen, whichever comes first
nl = data.find('\n', 0, size)
if nl >= 0:
nl += 1
self.buffer = data[nl:]
return data[:nl]
buf_len = len(data)
if buf_len >= size:
self.buffer = data[size:]
return data[:size]
buffers = []
if data:
buffers.append(data)
self.buffer = ""
while True:
data = self.istream_read(self.buffer_size)
if not data:
break
buffers.append(data)
left = size - buf_len
nl = data.find('\n', 0, left)
if nl >= 0:
nl += 1
self.buffer = data[nl:]
buffers[-1] = data[:nl]
break
n = len(data)
if n >= left:
self.buffer = data[left:]
buffers[-1] = data[:left]
break
buf_len += n
return "".join(buffers)
def readlines(self, sizehint=0):
total = 0
list = []
while True:
line = self.readline()
if not line:
break
list.append(line)
total += len(line)
if sizehint and total >= sizehint:
break
return list
# Iterator protocols
def __iter__(self):
return self
def next(self):
line = self.readline()
if not line:
raise StopIteration
return line
Binary file not shown.
+80
View File
@@ -0,0 +1,80 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import java
import sys
DEBUG = 'debug'
INFO = 'info'
WARN = 'warn'
ERROR = 'error'
FATAL = 'fatal'
levels_dict = {}
ix = 0
for level in [DEBUG, INFO, WARN, ERROR, FATAL, ]:
levels_dict[level]=ix
ix += 1
class modjy_logger:
def __init__(self, context):
self.log_ctx = context
self.format_str = "%(lvl)s:\t%(msg)s"
self.log_level = levels_dict[DEBUG]
def _log(self, level, level_str, msg, exc):
if level >= self.log_level:
msg = self.format_str % {'lvl': level_str, 'msg': msg, }
if exc:
# java.lang.System.err.println(msg, exc)
self.log_ctx.log(msg, exc)
else:
# java.lang.System.err.println(msg)
self.log_ctx.log(msg)
def debug(self, msg, exc=None):
self._log(0, DEBUG, msg, exc)
def info(self, msg, exc=None):
self._log(1, INFO, msg, exc)
def warn(self, msg, exc=None):
self._log(2, WARN, msg, exc)
def error(self, msg, exc=None):
self._log(3, ERROR, msg, exc)
def fatal(self, msg, exc=None):
self._log(4, FATAL, msg, exc)
def set_log_level(self, level_string):
try:
self.log_level = levels_dict[level_string]
except KeyError:
raise BadParameter("Invalid log level: '%s'" % level_string)
def set_log_format(self, format_string):
# BUG! Format string never actually used in this function.
try:
self._log(debug, "This is a log formatting test", None)
except KeyError:
raise BadParameter("Bad format string: '%s'" % format_string)
Binary file not shown.
+84
View File
@@ -0,0 +1,84 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
from UserDict import UserDict
BOOLEAN = ('boolean', int)
INTEGER = ('integer', int)
FLOAT = ('float', float)
STRING = ('string', None)
modjy_servlet_params = {
'multithread': (BOOLEAN, 1),
'cache_callables': (BOOLEAN, 1),
'reload_on_mod': (BOOLEAN, 0),
'app_import_name': (STRING, None),
'app_directory': (STRING, None),
'app_filename': (STRING, 'application.py'),
'app_callable_name': (STRING, 'handler'),
'callable_query_name': (STRING, None),
'exc_handler': (STRING, 'standard'),
'log_level': (STRING, 'info'),
'packages': (STRING, None),
'classdirs': (STRING, None),
'extdirs': (STRING, None),
'initial_env': (STRING, None),
}
class modjy_param_mgr(UserDict):
def __init__(self, param_types):
UserDict.__init__(self)
self.param_types = param_types
for pname in self.param_types.keys():
typ, default = self.param_types[pname]
self.__setitem__(pname, default)
def __getitem__(self, name):
return self._get_defaulted_value(name)
def __setitem__(self, name, value):
self.data[name] = self._convert_value(name, value)
def _convert_value(self, name, value):
if self.param_types.has_key(name):
typ, default = self.param_types[name]
typ_str, typ_func = typ
if typ_func:
try:
return typ_func(value)
except ValueError:
raise BadParameter("Illegal value for %s parameter '%s': %s" % (typ_str, name, value) )
return value
def _get_defaulted_value(self, name):
if self.data.has_key(name):
return self.data[name]
if self.param_types.has_key(name):
typ, default = self.param_types[name]
return default
raise KeyError(name)
Binary file not shown.
+143
View File
@@ -0,0 +1,143 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import sys
import synchronize
from java.io import File
from modjy_exceptions import *
class modjy_publisher:
def init_publisher(self):
self.cache = None
if self.params['app_directory']:
self.app_directory = self.expand_relative_path(self.params['app_directory'])
else:
self.app_directory = self.servlet_context.getRealPath('/')
self.params['app_directory'] = self.app_directory
if self.app_directory is not None and not self.app_directory in sys.path:
sys.path.append(self.app_directory)
def map_uri(self, req, environ):
source_uri = '%s%s%s' % (self.app_directory, File.separator, self.params['app_filename'])
callable_name = self.params['app_callable_name']
if self.params['callable_query_name']:
query_string = req.getQueryString()
if query_string:
for name_val in query_string.split('&'):
if name_val.find('=') != -1:
name, value = name_val.split('=', 1)
else:
name, value = name_val, ''
if name == self.params['callable_query_name']:
callable_name = value
else:
callable_name = ''
return source_uri, callable_name
def get_app_object(self, req, environ):
environ["SCRIPT_NAME"] = "%s%s" % (req.getContextPath(), req.getServletPath())
path_info = req.getPathInfo() or ""
environ["PATH_INFO"] = path_info
environ["PATH_TRANSLATED"] = File(self.app_directory, path_info).getPath()
if self.params['app_import_name']:
return self.get_app_object_importable(self.params['app_import_name'])
else:
if self.cache is None:
self.cache = {}
return self.get_app_object_old_style(req, environ)
get_app_object = synchronize.make_synchronized(get_app_object)
def get_app_object_importable(self, importable_name):
self.log.debug("Attempting to import application callable '%s'\n" % (importable_name, ))
# Under the importable mechanism, the cache contains a single object
if self.cache is None:
application, instantiable, method_name = self.load_importable(importable_name.strip())
if instantiable and self.params['cache_callables']:
application = application()
self.cache = application, instantiable, method_name
application, instantiable, method_name = self.cache
self.log.debug("Application is " + str(application))
if instantiable and not self.params['cache_callables']:
application = application()
self.log.debug("Instantiated application is " + str(application))
if method_name is not None:
if not hasattr(application, method_name):
self.log.fatal("Attribute error application callable '%s' as no method '%s'" % (application, method_name))
self.raise_exc(ApplicationNotFound, "Attribute error application callable '%s' as no method '%s'" % (application, method_name))
application = getattr(application, method_name)
self.log.debug("Application method is " + str(application))
return application
def load_importable(self, name):
try:
instantiable = False ; method_name = None
importable_name = name
if name.find('()') != -1:
instantiable = True
importable_name, method_name = name.split('()')
if method_name.startswith('.'):
method_name = method_name[1:]
if not method_name:
method_name = None
module_path, from_name = importable_name.rsplit('.', 1)
imported = __import__(module_path, globals(), locals(), [from_name])
imported = getattr(imported, from_name)
return imported, instantiable, method_name
except (ImportError, AttributeError), aix:
self.log.fatal("Import error import application callable '%s': %s\n" % (name, str(aix)))
self.raise_exc(ApplicationNotFound, "Failed to import app callable '%s': %s" % (name, str(aix)))
def get_app_object_old_style(self, req, environ):
source_uri, callable_name = self.map_uri(req, environ)
source_filename = source_uri
if not self.params['cache_callables']:
self.log.debug("Caching of callables disabled")
return self.load_object(source_filename, callable_name)
if not self.cache.has_key( (source_filename, callable_name) ):
self.log.debug("Callable object not in cache: %s#%s" % (source_filename, callable_name) )
return self.load_object(source_filename, callable_name)
app_callable, last_mod = self.cache.get( (source_filename, callable_name) )
self.log.debug("Callable object was in cache: %s#%s" % (source_filename, callable_name) )
if self.params['reload_on_mod']:
f = File(source_filename)
if f.lastModified() > last_mod:
self.log.info("Source file '%s' has been modified: reloading" % source_filename)
return self.load_object(source_filename, callable_name)
return app_callable
def load_object(self, path, callable_name):
try:
app_ns = {} ; execfile(path, app_ns)
app_callable = app_ns[callable_name]
f = File(path)
self.cache[ (path, callable_name) ] = (app_callable, f.lastModified())
return app_callable
except IOError, ioe:
self.raise_exc(ApplicationNotFound, "Application filename not found: %s" % path)
except KeyError, k:
self.raise_exc(NoCallable, "No callable named '%s' in %s" % (callable_name, path))
except Exception, x:
self.raise_exc(NoCallable, "Error loading jython callable '%s': %s" % (callable_name, str(x)) )
Binary file not shown.
+113
View File
@@ -0,0 +1,113 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import types
from java.lang import System
from modjy_exceptions import *
from modjy_write import write_object
# From: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
hop_by_hop_headers = {
'connection': None,
'keep-alive': None,
'proxy-authenticate': None,
'proxy-authorization': None,
'te': None,
'trailers': None,
'transfer-encoding': None,
'upgrade': None,
}
class start_response_object:
def __init__(self, req, resp):
self.http_req = req
self.http_resp = resp
self.write_callable = None
self.called = 0
self.content_length = None
# I'm doing the parameters this way to facilitate porting back to java
def __call__(self, *args, **keywords):
if len(args) < 2 or len(args) > 3:
raise BadArgument("Start response callback requires either two or three arguments: got %s" % str(args))
if len(args) == 3:
exc_info = args[2]
try:
try:
self.http_resp.reset()
except IllegalStateException, isx:
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None
else:
if self.called > 0:
raise StartResponseCalledTwice("Start response callback may only be called once, without exception information.")
status_str = args[0]
headers_list = args[1]
if not isinstance(status_str, types.StringType):
raise BadArgument("Start response callback requires string as first argument")
if not isinstance(headers_list, types.ListType):
raise BadArgument("Start response callback requires list as second argument")
try:
status_code, status_message_str = status_str.split(" ", 1)
self.http_resp.setStatus(int(status_code))
except ValueError:
raise BadArgument("Status string must be of the form '<int> <string>'")
self.make_write_object()
try:
for header_name, header_value in headers_list:
header_name_lower = header_name.lower()
if hop_by_hop_headers.has_key(header_name_lower):
raise HopByHopHeaderSet("Under WSGI, it is illegal to set hop-by-hop headers, i.e. '%s'" % header_name)
if header_name_lower == "content-length":
try:
self.set_content_length(int(header_value))
except ValueError, v:
raise BadArgument("Content-Length header value must be a string containing an integer, not '%s'" % header_value)
else:
final_value = header_value.encode('latin-1')
# Here would be the place to check for control characters, whitespace, etc
self.http_resp.addHeader(header_name, final_value)
except (AttributeError, TypeError), t:
raise BadArgument("Start response callback headers must contain a list of (<string>,<string>) tuples")
except UnicodeError, u:
raise BadArgument("Encoding error: header values may only contain latin-1 characters, not '%s'" % repr(header_value))
except ValueError, v:
raise BadArgument("Headers list must contain 2-tuples")
self.called += 1
return self.write_callable
def set_content_length(self, length):
if self.write_callable.num_writes == 0:
self.content_length = length
self.http_resp.setContentLength(length)
else:
raise ResponseCommitted("Cannot set content-length: response is already commited.")
def make_write_object(self):
try:
self.write_callable = write_object(self.http_resp.getOutputStream())
except IOException, iox:
raise IOError(iox)
return self.write_callable
Binary file not shown.
+43
View File
@@ -0,0 +1,43 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
import types
from modjy_exceptions import *
class write_object:
def __init__(self, ostream):
self.ostream = ostream
self.num_writes = 0
def __call__(self, *args, **keywords):
if len(args) != 1 or not isinstance(args[0], types.StringTypes):
raise NonStringOutput("Invocation of write callable requires exactly one string argument")
try:
self.ostream.write(args[0]) # Jython implicitly converts the (binary) string to a byte array
# WSGI requires that all output be flushed before returning to the application
# According to the java docs: " The flush method of OutputStream does nothing."
# Still, leave it in place for now: it's in the right place should this
# code ever be ported to another platform.
self.ostream.flush()
self.num_writes += 1
except Exception, x:
raise ModjyIOException(x)
Binary file not shown.
+157
View File
@@ -0,0 +1,157 @@
###
#
# Copyright Alan Kennedy.
#
# You may contact the copyright holder at this uri:
#
# http://www.xhaus.com/contact/modjy
#
# The licence under which this code is released is the Apache License v2.0.
#
# The terms and conditions of this license are listed in a file contained
# in the distribution that also contained this file, under the name
# LICENSE.txt.
#
# You may also read a copy of the license at the following web address.
#
# http://modjy.xhaus.com/LICENSE.txt
#
###
from java.lang import System
try:
from org.python.core.util import FileUtil
create_py_file = FileUtil.wrap
except ImportError:
from org.python.core import PyFile
create_py_file = PyFile
from modjy_exceptions import *
from modjy_input import modjy_input_object
server_name = "modjy"
server_param_prefix = "%s.param" % server_name
j2ee_ns_prefix = "j2ee"
cgi_var_char_encoding = "iso-8859-1"
class modjy_wsgi:
#
# WSGI constants
#
empty_pystring = u""
wsgi_version = (1,0)
#
# Container-specific constants
#
modjy_version = (0, 25, 3)
def set_string_envvar(self, dict, name, value):
if value is None:
value = self.empty_pystring
value = value.encode(cgi_var_char_encoding)
dict[name] = value
def set_string_envvar_optional(self, dict, name, value, default_value):
if value != default_value:
self.set_string_envvar(dict, name, value)
def set_int_envvar(self, dict, name, value, default_value):
if value == default_value:
value = self.empty_pystring
else:
value = unicode(value)
self.set_string_envvar(dict, name, value)
def set_container_specific_wsgi_vars(self, req, resp, dict, params):
dict["%s.version" % server_name] = self.modjy_version
for pname in params.keys():
dict["%s.%s" % (server_param_prefix, pname)] = params[pname]
def set_j2ee_specific_wsgi_vars(self, dict, j2ee_ns):
for p in j2ee_ns.keys():
dict["%s.%s" % (j2ee_ns_prefix, p)] = j2ee_ns[p]
def set_required_cgi_environ (self, req, resp, dict):
self.set_string_envvar(dict, "REQUEST_METHOD", req.getMethod())
self.set_string_envvar(dict, "QUERY_STRING", req.getQueryString())
self.set_string_envvar(dict, "CONTENT_TYPE", req.getContentType())
self.set_int_envvar(dict, "CONTENT_LENGTH", req.getContentLength(), -1)
self.set_string_envvar(dict, "SERVER_NAME", req.getLocalName())
self.set_int_envvar(dict, "SERVER_PORT", req.getLocalPort(), 0)
def set_other_cgi_environ (self, req, resp, dict):
if req.isSecure():
self.set_string_envvar(dict, "HTTPS", 'on')
else:
self.set_string_envvar(dict, "HTTPS", 'off')
self.set_string_envvar(dict, "SERVER_PROTOCOL", req.getProtocol())
self.set_string_envvar(dict, "REMOTE_HOST", req.getRemoteHost())
self.set_string_envvar(dict, "REMOTE_ADDR", req.getRemoteAddr())
self.set_int_envvar(dict, "REMOTE_PORT", req.getRemotePort(), -1)
self.set_string_envvar_optional(dict, "AUTH_TYPE", req.getAuthType(), None)
self.set_string_envvar_optional(dict, "REMOTE_USER", req.getRemoteUser(), None)
def set_http_header_environ(self, req, resp, dict):
header_name_enum = req.getHeaderNames()
while header_name_enum.hasMoreElements():
curr_header_name = header_name_enum.nextElement()
values = None
values_enum = req.getHeaders(curr_header_name)
while values_enum.hasMoreElements():
next_value = values_enum.nextElement().encode(cgi_var_char_encoding)
if values is None:
values = next_value
else:
if isinstance(values, list):
values.append(next_value)
else:
values = [values]
dict["HTTP_%s" % str(curr_header_name).replace('-', '_').upper()] = values
def set_required_wsgi_vars(self, req, resp, dict):
dict["wsgi.version"] = self.wsgi_version
dict["wsgi.url_scheme"] = req.getScheme()
dict["wsgi.multithread"] = \
int(dict["%s.cache_callables" % server_param_prefix]) \
and \
int(dict["%s.multithread" % server_param_prefix])
dict["wsgi.multiprocess"] = self.wsgi_multiprocess = 0
dict["wsgi.run_once"] = not(dict["%s.cache_callables" % server_param_prefix])
def set_wsgi_streams(self, req, resp, dict):
try:
dict["wsgi.input"] = modjy_input_object(req.getInputStream())
dict["wsgi.errors"] = create_py_file(System.err)
except IOException, iox:
raise ModjyIOException(iox)
def set_wsgi_classes(self, req, resp, dict):
# dict["wsgi.file_wrapper"] = modjy_file_wrapper
pass
def set_user_specified_environment(self, req, resp, wsgi_environ, params):
if not params.has_key('initial_env') or not params['initial_env']:
return
user_env_string = params['initial_env']
for l in user_env_string.split('\n'):
l = l.strip()
if l:
name, value = l.split(':', 1)
wsgi_environ[name.strip()] = value.strip()
def set_wsgi_environment(self, req, resp, wsgi_environ, params, j2ee_ns):
self.set_container_specific_wsgi_vars(req, resp, wsgi_environ, params)
self.set_j2ee_specific_wsgi_vars(wsgi_environ, j2ee_ns)
self.set_required_cgi_environ(req, resp, wsgi_environ)
self.set_other_cgi_environ(req, resp, wsgi_environ)
self.set_http_header_environ(req, resp, wsgi_environ)
self.set_required_wsgi_vars(req, resp, wsgi_environ)
self.set_wsgi_streams(req, resp, wsgi_environ)
self.set_wsgi_classes(req, resp, wsgi_environ)
self.set_user_specified_environment(req, resp, wsgi_environ, params)