"""Component-based web application framework TODO: * http://java.sun.com/products/servlet/2.3/javadoc/index.html * make more Servlet like, javaCap all the method names * Implement ServletRequest/Response as well as HttpServletRequest/Response Copyright (C) 2002 l.m.orchard http://www.decafbad.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details, published at http://www.gnu.org/copyleft/gpl.html """ import os, sys, string, re import cgi import Cookie import xmlrpclib import ConfigParser #from types import * from Cheetah.Template import Template from xml.sax import saxutils # {{{ class RequestContext class RequestContext: """Encapsulated web request, contains parameters and CGI-style variables """ def __init__(self): self.__form = cgi.FieldStorage() self.__cookies = Cookie.Cookie() self.__env = os.environ # def __getattr__(self, name): # if name[0] == '_': # raise AttributeError # try: # value = self.__form[name].value # except (TypeError, KeyError): # value = '' # else: # value = string.strip(value) # setattr(self, name, value) # return value # def __getitem__(self, key): # return getattr(self, key) def is_ssl(self): return os.environ.get('SSL_PROTOCOL', '') != '' def get_cookie(self, name): return self.__cookies[name].value def get_env_parameter(self, name, default=""): ### TODO: make more like / merge with __getattr__ return self.__env[name] #.getvalue(name, default) def get_parameter(self, name, default=""): ### TODO: make more like / merge with __getattr__ return self.__form.getvalue(name, default) def get_parameter_names(self): return self.__form.keys() def get_parameter_values(self): return self.__form.values() def get_method(self): return os.environ.get('REQUEST_METHOD', '') def get_path_info(self): return os.environ.get('PATH_INFO', '') def get_path_translated(self): return os.environ.get('PATH_TRANSLATED', '') def get_query_string(self): return os.environ.get('QUERY_STRING', '') def get_remote_user(self): return os.environ.get('REMOTE_USER', '') def get_script_name(self): return os.environ.get('SCRIPT_NAME', '') def get_script_path(self): return os.environ.get('SCRIPT_PATH', '') def get_server_host(self): return os.environ.get('HTTP_HOST') def escape(self, s): s = string.replace(s, '&', '&') s = string.replace(s, '<', '<') s = string.replace(s, '>', '>') return s def escapeq(self, s): s = escape(s) s = string.replace(s, '"', '"') return s def get_qualified_url(self, uri = None): schema, stdport = (('http', '80'), ('https', '443'))[self.is_ssl()] host = os.environ.get('HTTP_HOST') if not host: host = os.environ.get('SERVER_NAME') port = os.environ.get('SERVER_PORT', '80') if port != stdport: host = host + ":" + port result = "%s://%s" % (schema, host) if uri: result = result + uri return result def get_base_url(self): return self.get_qualified_url(self.get_script_name()) # }}} # {{{ class ResponseContext class ResponseContext: """Encapsulates the response to a web request, uses the template engine """ def __init__(self): self.status = "200 OK" self.content_type = "text/html" self.error = 0 self.headers = [] self.ns = {} self.cookies = Cookie.Cookie() self.searchlist = [] self.template = "default.tmpl" self.template_path = "templates" self.sent_headers = 0 self.sent_response = 0 self.redirect_url = "" self.line_buf = [] def set_status(self, val): self.status = val self.error = 0 def set_content_type(self, val): self.content_type = val def add_header(self, val): self.headers.append(val) def add_cookie(self, name, val): self.cookies[name] = val def set_error(self, val): self.status = val self.error = 1 def add_namespace(self, val): self.searchlist.append(val) def set_template(self, val): self.template = val def write(self, str): self.line_buf.append(str) def send_headers(self): if self.sent_headers: return print "Status: %s" % self.status print "Content-type: %s" % self.content_type if len(self.cookies) > 0: print self.cookies if len(self.headers) > 0: print self.headers print self.sent_headers = 1 def send_redirect(self, loc): self.set_status("302 Moved") #self.add_header("Location: %s" % loc) self.headers = ["Location: %s" % loc] self.send_headers() def send_response(self): self.send_headers() if len(self.line_buf) > 0: print string.join(self.line_buf, "\n") else: print Template(file=os.path.join(self.template_path, self.template), searchList=self.searchlist) def print_test(self): self.send_headers() cgi.print_environ() cgi.print_environ_usage() # }}} # {{{ class ApplicationComponent class ApplicationComponent: """Basic web application component """ def __init__(self, conf_fn): self.conf_fn = conf_fn self.config = ConfigParser.ConfigParser() self.config.read(conf_fn) self.app_context = None def set_application_context(self, ctx): self.app_context = ctx def get_application_context(self): return self.app_context def get_config(self, key, section='global', default=None): app = self.get_application_context() if self.config.has_option(section, key): return self.config.get(section, key) elif self.config.has_option('global', key): return self.config.get('global', key) else: return app.get_config(key, section, default) # }}} # {{{ class ApplicationContext class ApplicationContext: """Container for web application components, routes web requests """ def __init__(self, conf_fn): self.config = ConfigParser.ConfigParser() self.config.read(conf_fn) self.config_path = self.get_config('config_path', 'global', 'conf') self.default_component = self.get_config('default_component') self.default_method = self.get_config('default_method') self.xmlrpc_path = self.get_config('xmlrpc_path', 'global', '/RPC2') self.components = {} self.load_components() def load_components(self): for comp_name in self.config.options('components'): comp_module = self.config.get('components', comp_name) exec "import %s" % comp_module comp = eval("%s.%s('%s')" % (comp_module, comp_module, os.path.join(self.config_path, "%s.conf" % comp_name))) self.add_component(comp_name, comp) def get_config(self, key, section='global', default=None): if self.config.has_option(section, key): return self.config.get(section, key) elif self.config.has_option('global', key): return self.config.get('global', key) else: return default def set_default_component(self, name): self.default_component = name def set_default_method(self, name): self.default_method = name def add_component(self, name, comp): self.components[name] = comp comp.set_application_context(self) def serve_request(self): if os.environ.has_key('PATH_INFO'): path_info = os.environ.get('PATH_INFO','') if path_info[0-len(self.xmlrpc_path):] == self.xmlrpc_path: xml_in = sys.stdin.read(int(os.environ.get('CONTENT_LENGTH', ''))) params, method = xmlrpclib.loads(xml_in) component_name, method_name = string.split(method, ".") if self.components.has_key(component_name): component = self.components[component_name] elif self.components.has_key(self.default_component): component = self.components[self.default_component] else: xml_out = xmlrpclib.dumps(xmlrpclib.Fault(1, "no such component")) comp_method_name = "xmlrpc_%s" % method_name try: method = getattr(component, comp_method_name) except AttributeError: xml_out = xmlrpclib.dumps(xmlrpclib.Fault(1, "no such method")) else: result = method(params) xml_out = xmlrpclib.dumps((result,), methodresponse=1) print "Content-type: text/xml" print print xml_out return req = RequestContext() res = ResponseContext() ### Try to get the component component_name = req.get_parameter('comp') if self.components.has_key(component_name): component = self.components[component_name] elif self.components.has_key(self.default_component): component = self.components[self.default_component] else: ### Need error handling pass ### Try to get the method method_name = "web_%s" % req.get_parameter('method') try: method = getattr(component, method_name) except AttributeError: try: method = getattr(component, "web_%s" % self.default_method) except AttributeError: ### Need error checking raise app_ns = { 'application' : self, 'request' : req, 'response' : res } res.add_namespace(app_ns) ### Run the app component method method(self, req, res) ### Finish by sending the response res.send_response() # }}}