Viewing file: apache.py (29.15 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# # Copyright 2004 Apache Software Foundation # # 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. # # Originally developed by Gregory Trubetskoy. # # $Id: apache.py 102649 2004-02-16 19:47:28Z grisha $
import sys import traceback import time import os import pdb import stat import imp import types import _apache
# a small hack to improve PythonPath performance. This # variable stores the last PythonPath in raw (unevaled) form. _path = None
class CallBack: """ A generic callback object. """
class HStack: """ The actual stack string lives in the request object so it can be manipulated by both apache.py and mod_python.c """ def __init__(self, req): self.req = req
def pop(self):
handlers = self.req.hstack.split()
if not handlers: return None else: self.req.hstack = " ".join(handlers[1:]) return handlers[0]
def ConnectionDispatch(self, conn):
# config config = conn.base_server.get_config() debug = int(config.get("PythonDebug", 0))
try:
handler = conn.hlist.handler
# split module::handler l = handler.split('::', 1) module_name = l[0] if len(l) == 1: # no oject, provide default object_str = "connectionhandler" else: object_str = l[1]
# add the directory to pythonpath if # not there yet, or pythonpath specified
if config.has_key("PythonPath"): # we want to do as little evaling as possible, # so we remember the path in un-evaled form and # compare it global _path pathstring = config["PythonPath"] if pathstring != _path: _path = pathstring newpath = eval(pathstring) if sys.path != newpath: sys.path[:] = newpath
# import module module = import_module(module_name, autoreload=int(config.get("PythonAutoReload", 1)), log=debug) # find the object object = resolve_object(module, object_str, arg=conn, silent=0)
if object:
# call the object if config.has_key("PythonEnablePdb"): result = pdb.runcall(object, conn) else: result = object(conn)
assert (type(result) == type(int())), \ "ConnectionHandler '%s' returned invalid return code." % handler
except PROG_TRACEBACK, traceblock: # Program run-time error try: (etype, value, traceback) = traceblock result = self.ReportError(etype, value, traceback, srv=conn.base_server, phase="ConnectionHandler", hname=handler, debug=debug) finally: traceback = None
except: # Any other rerror (usually parsing) try: exc_type, exc_value, exc_traceback = sys.exc_info() result = self.ReportError(exc_type, exc_value, exc_traceback, srv=conn.base_server, phase="ConnectionHandler", hname=handler, debug=debug) finally: exc_traceback = None
return result
def FilterDispatch(self, filter):
req = filter.req
# config config = req.get_config() debug = int(config.get("PythonDebug", 0))
try:
# split module::handler l = filter.handler.split('::', 1) module_name = l[0] if len(l) == 1: # no oject, provide default if filter.is_input: object_str = "inputfilter" else: object_str = "outputfilter" else: object_str = l[1]
# add the directory to pythonpath if # not there yet, or pythonpath specified if config.has_key("PythonPath"): # we want to do as little evaling as possible, # so we remember the path in un-evaled form and # compare it global _path pathstring = config["PythonPath"] if pathstring != _path: _path = pathstring newpath = eval(pathstring) if sys.path != newpath: sys.path[:] = newpath else: if filter.dir and (filter.dir not in sys.path): sys.path[:0] = [filter.dir]
# import module module = import_module(module_name, autoreload=int(config.get("PythonAutoReload", 1)), log=debug)
# find the object object = resolve_object(module, object_str, arg=filter, silent=0)
if object:
# call the object if config.has_key("PythonEnablePdb"): pdb.runcall(object, filter) else: object(filter)
# always flush the filter. without a FLUSH or EOS bucket, # the content is never written to the network. # XXX an alternative is to tell the user to flush() always filter.flush()
except SERVER_RETURN, value: # SERVER_RETURN indicates a non-local abort from below # with value as (result, status) or (result, None) or result try: if len(value.args) == 2: (result, status) = value.args if status: req.status = status else: result = value.args[0] if type(result) != type(7): s = "Value raised with SERVER_RETURN is invalid. It is a " s = s + "%s, but it must be a tuple or an int." % type(result) _apache.log_error(s, APLOG_NOERRNO|APLOG_ERR, req.server)
return
except: pass
except PROG_TRACEBACK, traceblock: # Program run-time error try: (etype, value, traceback) = traceblock filter.disable() result = self.ReportError(etype, value, traceback, req=req, filter=filter, phase="Filter: " + filter.name, hname=filter.handler, debug=debug) finally: traceback = None
except: # Any other rerror (usually parsing) try: exc_type, exc_value, exc_traceback = sys.exc_info() filter.disable() result = self.ReportError(exc_type, exc_value, exc_traceback, req=req, filter=filter, phase=filter.name, hname=filter.handler, debug=debug) finally: exc_traceback = None
return OK
def HandlerDispatch(self, req): """ This is the handler dispatcher. """
# be cautious result = HTTP_INTERNAL_SERVER_ERROR
# config config = req.get_config() debug = int(config.get("PythonDebug", 0))
try: hlist = req.hlist
while hlist.handler:
# split module::handler l = hlist.handler.split('::', 1)
module_name = l[0] if len(l) == 1: # no oject, provide default object_str = req.phase[len("python"):].lower() else: object_str = l[1]
# add the directory to pythonpath if # not there yet, or pythonpath specified if config.has_key("PythonPath"): # we want to do as little evaling as possible, # so we remember the path in un-evaled form and # compare it global _path pathstring = config["PythonPath"] if pathstring != _path: _path = pathstring newpath = eval(pathstring) if sys.path != newpath: sys.path[:] = newpath else: dir = hlist.directory if dir and (dir not in sys.path): sys.path[:0] = [dir]
# import module module = import_module(module_name, autoreload=int(config.get("PythonAutoReload", 1)), log=debug)
# find the object object = resolve_object(module, object_str, arg=req, silent=hlist.silent)
if object:
# call the object if config.has_key("PythonEnablePdb"): result = pdb.runcall(object, req) else: result = object(req)
assert (type(result) == type(int())), \ "Handler '%s' returned invalid return code." % hlist.handler
# stop cycling through handlers if result != OK: break elif hlist.silent: result = DECLINED
hlist.next()
except SERVER_RETURN, value: # SERVER_RETURN indicates an abort from below # with value as (result, status) or (result, None) or result try: if len(value.args) == 2: (result, status) = value.args if status: req.status = status else: result = value.args[0] if type(result) != type(7): s = "Value raised with SERVER_RETURN is invalid. It is a " s = s + "%s, but it must be a tuple or an int." % type(result) _apache.log_error(s, APLOG_NOERRNO|APLOG_ERR, req.server) return HTTP_INTERNAL_SERVER_ERROR
except: pass
except PROG_TRACEBACK, traceblock: # Program run-time error try: (etype, value, traceback) = traceblock result = self.ReportError(etype, value, traceback, req=req, phase=req.phase, hname=hlist.handler, debug=debug) finally: traceback = None
except: # Any other rerror (usually parsing) try: exc_type, exc_value, exc_traceback = sys.exc_info() result = self.ReportError(exc_type, exc_value, exc_traceback, req=req, phase=req.phase, hname=hlist.handler, debug=debug) finally: exc_traceback = None
return result
def ReportError(self, etype, evalue, etb, req=None, filter=None, srv=None, phase="N/A", hname="N/A", debug=0): """ This function is only used when debugging is on. It sends the output similar to what you'd see when using Python interactively to the browser """
try: # try/finally try: # try/except
if str(etype) == "exceptions.IOError" \ and str(evalue)[:5] == "Write": # if this is an IOError while writing to client, # it is probably better not to try to write to the cleint # even if debug is on. debug = 0
# write to log for e in traceback.format_exception(etype, evalue, etb): s = "%s %s: %s" % (phase, hname, e[:-1]) if req: req.log_error(s, APLOG_NOERRNO|APLOG_ERR) else: _apache.log_error(s, APLOG_NOERRNO|APLOG_ERR, srv)
if not debug or not req: return HTTP_INTERNAL_SERVER_ERROR else: # write to client req.content_type = 'text/html'
s = '\n<pre>\nMod_python error: "%s %s"\n\n' % (phase, hname) for e in traceback.format_exception(etype, evalue, etb): s = s + e + '\n' s = s + "</pre>\n" if filter: filter.write(s) filter.flush() else: req.write(s)
return DONE except: # last try traceback.print_exc()
finally: # erase the traceback etb = None # we do not return anything
def import_module(module_name, autoreload=1, log=0, path=None): """ Get the module to handle the request. If autoreload is on, then the module will be reloaded if it has changed since the last import. """
# (Re)import if sys.modules.has_key(module_name):
# The module has been imported already module = sys.modules[module_name]
# but is it in the path? file = module.__dict__.get("__file__")
# the "and not" part of this condition is to prevent execution # of arbitrary already imported modules, such as os. The # reason we use startswith as opposed to exact match is that # modules inside packages are actually in subdirectories.
if not file or (path and not filter(file.startswith, path)): # there is a script by this name already imported, but it's in # a different directory, therefore it's a different script mtime, oldmtime = 0, -1 elif autoreload: oldmtime = module.__dict__.get("__mtime__", 0) mtime = module_mtime(module) else: mtime, oldmtime = 0, 0
else: mtime, oldmtime = 0, -1
if mtime > oldmtime:
# Import the module if log: if path: s = "mod_python: (Re)importing module '%s' with path set to '%s'" % (module_name, path) else: s = "mod_python: (Re)importing module '%s'" % module_name _apache.log_error(s, APLOG_NOERRNO|APLOG_NOTICE)
parts = module_name.split('.') for i in range(len(parts)): f, p, d = imp.find_module(parts[i], path) try: mname = ".".join(parts[:i+1]) module = imp.load_module(mname, f, p, d) finally: if f: f.close() if hasattr(module, "__path__"): path = module.__path__
if mtime == 0: mtime = module_mtime(module)
module.__mtime__ = mtime
return module
def module_mtime(module): """Get modification time of module""" mtime = 0 if module.__dict__.has_key("__file__"): filepath = module.__file__ try: # this try/except block is a workaround for a Python bug in # 2.0, 2.1 and 2.1.1. See # http://sourceforge.net/tracker/?group_id=5470&atid=105470&func=detail&aid=422004
if os.path.exists(filepath): mtime = os.path.getmtime(filepath)
if os.path.exists(filepath[:-1]) : mtime = max(mtime, os.path.getmtime(filepath[:-1]))
except OSError: pass
return mtime
def resolve_object(module, object_str, arg=None, silent=0): """ This function traverses the objects separated by . (period) to find the last one we're looking for:
From left to right, find objects, if it is an unbound method of a class, instantiate the class passing the request as single argument
'arg' is sometimes req, sometimes filter, sometimes connection """
obj = module
for obj_str in object_str.split('.'):
parent = obj
# don't throw attribute errors when silent if silent and not hasattr(obj, obj_str): return None
# this adds a little clarity if we have an attriute error if obj == module and not hasattr(module, obj_str): if hasattr(module, "__file__"): s = "module '%s' contains no '%s'" % (module.__file__, obj_str) raise AttributeError, s
obj = getattr(obj, obj_str)
if hasattr(obj, "im_self") and not obj.im_self: # this is an unbound method, its class # needs to be instantiated instance = parent(arg) obj = getattr(instance, obj_str)
return obj
def build_cgi_env(req): """ Utility function that returns a dictionary of CGI environment variables as described in http://hoohoo.ncsa.uiuc.edu/cgi/env.html """
req.add_common_vars() env = req.subprocess_env.copy() if len(req.path_info) > 0: env["SCRIPT_NAME"] = req.uri[:-len(req.path_info)] else: env["SCRIPT_NAME"] = req.uri
env["GATEWAY_INTERFACE"] = "Python-CGI/1.1"
# you may want to comment this out for better security if req.headers_in.has_key("authorization"): env["HTTP_AUTHORIZATION"] = req.headers_in["authorization"]
return env
class NullIO: """ Abstract IO """ def tell(self): return 0 def read(self, n = -1): return "" def readline(self, length = None): return "" def readlines(self): return [] def write(self, s): pass def writelines(self, list): self.write("".join(list)) def isatty(self): return 0 def flush(self): pass def close(self): pass def seek(self, pos, mode = 0): pass
class CGIStdin(NullIO):
def __init__(self, req): self.pos = 0 self.req = req self.BLOCK = 65536 # 64K # note that self.buf sometimes contains leftovers # that were read, but not used when readline was used self.buf = ""
def read(self, n = -1): if n == 0: return "" if n == -1: s = self.req.read(self.BLOCK) while s: self.buf = self.buf + s self.pos = self.pos + len(s) s = self.req.read(self.BLOCK) result = self.buf self.buf = "" return result else: if self.buf: s = self.buf[:n] n = n - len(s) else: s = "" s = s + self.req.read(n) self.pos = self.pos + len(s) return s
def readlines(self): s = (self.buf + self.read()).split('\n') return map(lambda s: s + '\n', s)
def readline(self, n = -1):
if n == 0: return ""
# fill up the buffer self.buf = self.buf + self.req.read(self.BLOCK)
# look for \n in the buffer i = self.buf.find('\n') while i == -1: # if \n not found - read more if (n != -1) and (len(self.buf) >= n): # we're past n i = n - 1 break x = len(self.buf) self.buf = self.buf + self.req.read(self.BLOCK) if len(self.buf) == x: # nothing read, eof i = x - 1 break i = self.buf.find('\n', x) # carve out the piece, then shorten the buffer result = self.buf[:i+1] self.buf = self.buf[i+1:] self.pos = self.pos + len(result) return result
class CGIStdout(NullIO):
""" Class that allows writing to the socket directly for CGI. """ def __init__(self, req): self.pos = 0 self.req = req self.headers_sent = 0 self.headers = "" def write(self, s):
if not s: return
if not self.headers_sent: self.headers = self.headers + s
# are headers over yet? headers_over = 0
# first try RFC-compliant CRLF ss = self.headers.split('\r\n\r\n', 1) if len(ss) < 2: # second try with \n\n ss = self.headers.split('\n\n', 1) if len(ss) >= 2: headers_over = 1 else: headers_over = 1 if headers_over: # headers done, process them ss[0] = ss[0].replace('\r\n', '\n') lines = ss[0].split('\n') for line in lines: h, v = line.split(":", 1) v = v.strip() if h.lower() == "status": status = int(v.split()[0]) self.req.status = status elif h.lower() == "content-type": self.req.content_type = v self.req.headers_out[h] = v else: self.req.headers_out.add(h, v)
self.headers_sent = 1
# write the body if any at this point self.req.write(ss[1]) else: self.req.write(str(s)) self.pos = self.pos + len(s)
def tell(self): return self.pos
def setup_cgi(req): """ Replace sys.stdin and stdout with an objects that read/write to the socket, as well as substitute the os.environ. Returns (environ, stdin, stdout) which you must save and then use with restore_nocgi(). """
# save env save_env = os.environ.copy() si = sys.stdin so = sys.stdout
os.environ.update(build_cgi_env(req)) sys.stdout = CGIStdout(req) sys.stdin = CGIStdin(req)
sys.argv = [] # keeps cgi.py happy
return save_env, si, so def restore_nocgi(sav_env, si, so): """ see setup_cgi() """
osenv = os.environ
# restore env for k in osenv.keys(): del osenv[k] for k in sav_env: osenv[k] = sav_env[k]
sys.stdout = si sys.stdin = so
def init(): """ This function is called by the server at startup time """
return CallBack()
## Some functions made public make_table = _apache.table log_error = _apache.log_error table = _apache.table config_tree = _apache.config_tree server_root = _apache.server_root mpm_query = _apache.mpm_query
## Some constants
HTTP_CONTINUE = 100 HTTP_SWITCHING_PROTOCOLS = 101 HTTP_PROCESSING = 102 HTTP_OK = 200 HTTP_CREATED = 201 HTTP_ACCEPTED = 202 HTTP_NON_AUTHORITATIVE = 203 HTTP_NO_CONTENT = 204 HTTP_RESET_CONTENT = 205 HTTP_PARTIAL_CONTENT = 206 HTTP_MULTI_STATUS = 207 HTTP_MULTIPLE_CHOICES = 300 HTTP_MOVED_PERMANENTLY = 301 HTTP_MOVED_TEMPORARILY = 302 HTTP_SEE_OTHER = 303 HTTP_NOT_MODIFIED = 304 HTTP_USE_PROXY = 305 HTTP_TEMPORARY_REDIRECT = 307 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 HTTP_PAYMENT_REQUIRED = 402 HTTP_FORBIDDEN = 403 HTTP_NOT_FOUND = 404 HTTP_METHOD_NOT_ALLOWED = 405 HTTP_NOT_ACCEPTABLE = 406 HTTP_PROXY_AUTHENTICATION_REQUIRED= 407 HTTP_REQUEST_TIME_OUT = 408 HTTP_CONFLICT = 409 HTTP_GONE = 410 HTTP_LENGTH_REQUIRED = 411 HTTP_PRECONDITION_FAILED = 412 HTTP_REQUEST_ENTITY_TOO_LARGE = 413 HTTP_REQUEST_URI_TOO_LARGE = 414 HTTP_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_RANGE_NOT_SATISFIABLE = 416 HTTP_EXPECTATION_FAILED = 417 HTTP_UNPROCESSABLE_ENTITY = 422 HTTP_LOCKED = 423 HTTP_FAILED_DEPENDENCY = 424 HTTP_INTERNAL_SERVER_ERROR = 500 HTTP_NOT_IMPLEMENTED = 501 HTTP_BAD_GATEWAY = 502 HTTP_SERVICE_UNAVAILABLE = 503 HTTP_GATEWAY_TIME_OUT = 504 HTTP_VERSION_NOT_SUPPORTED = 505 HTTP_VARIANT_ALSO_VARIES = 506 HTTP_INSUFFICIENT_STORAGE = 507 HTTP_NOT_EXTENDED = 510
# The APLOG constants in Apache are derived from syslog.h # constants, so we do same here.
try: import syslog APLOG_EMERG = syslog.LOG_EMERG # system is unusable APLOG_ALERT = syslog.LOG_ALERT # action must be taken immediately APLOG_CRIT = syslog.LOG_CRIT # critical conditions APLOG_ERR = syslog.LOG_ERR # error conditions APLOG_WARNING = syslog.LOG_WARNING # warning conditions APLOG_NOTICE = syslog.LOG_NOTICE # normal but significant condition APLOG_INFO = syslog.LOG_INFO # informational APLOG_DEBUG = syslog.LOG_DEBUG # debug-level messages except ImportError: APLOG_EMERG = 0 APLOG_ALERT = 1 APLOG_CRIT = 2 APLOG_ERR = 3 APLOG_WARNING = 4 APLOG_NOTICE = 5 APLOG_INFO = 6 APLOG_DEBUG = 7 APLOG_NOERRNO = 8
OK = REQ_PROCEED = 0 DONE = -2 DECLINED = REQ_NOACTION = -1
# constants for get_remote_host REMOTE_HOST = 0 REMOTE_NAME = 1 REMOTE_NOLOOKUP = 2 REMOTE_DOUBLE_REV = 3
# legacy/mod_python things REQ_ABORTED = HTTP_INTERNAL_SERVER_ERROR REQ_EXIT = "REQ_EXIT" SERVER_RETURN = _apache.SERVER_RETURN PROG_TRACEBACK = "PROG_TRACEBACK"
# the req.finfo tuple FINFO_MODE = 0 FINFO_INO = 1 FINFO_DEV = 2 FINFO_NLINK = 3 FINFO_UID = 4 FINFO_GID = 5 FINFO_SIZE = 6 FINFO_ATIME = 7 FINFO_MTIME = 8 FINFO_CTIME = 9 FINFO_FNAME = 10 FINFO_NAME = 11 #FINFO_FILEHAND = 14
# the req.parsed_uri URI_SCHEME = 0 URI_HOSTINFO = 1 URI_USER = 2 URI_PASSWORD = 3 URI_HOSTNAME = 4 URI_PORT = 5 URI_PATH = 6 URI_QUERY = 7 URI_FRAGMENT = 8
# for req.proxyreq PROXYREQ_NONE = 0 # No proxy PROXYREQ_PROXY = 1 # Standard proxy PROXYREQ_REVERSE = 2 # Reverse proxy
# methods for req.allow_method() M_GET = 0 # RFC 2616: HTTP M_PUT = 1 M_POST = 2 M_DELETE = 3 M_CONNECT = 4 M_OPTIONS = 5 M_TRACE = 6 # RFC 2616: HTTP M_PATCH = 7 M_PROPFIND = 8 # RFC 2518: WebDAV M_PROPPATCH = 9 M_MKCOL = 10 M_COPY = 11 M_MOVE = 12 M_LOCK = 13 M_UNLOCK = 14 # RFC2518: WebDAV M_VERSION_CONTROL = 15 # RFC3253: WebDAV Versioning M_CHECKOUT = 16 M_UNCHECKOUT = 17 M_CHECKIN = 18 M_UPDATE = 19 M_LABEL = 20 M_REPORT = 21 M_MKWORKSPACE = 22 M_MKACTIVITY = 23 M_BASELINE_CONTROL = 24 M_MERGE = 25 M_INVALID = 26 # RFC3253: WebDAV Versioning
# for req.used_path_info AP_REQ_ACCEPT_PATH_INFO = 0 # Accept request given path_info AP_REQ_REJECT_PATH_INFO = 1 # Send 404 error if path_info was given AP_REQ_DEFAULT_PATH_INFO = 2 # Module's choice for handling path_info
# for mpm_query AP_MPMQ_NOT_SUPPORTED = 0 # This value specifies whether # an MPM is capable of # threading or forking. AP_MPMQ_STATIC = 1 # This value specifies whether # an MPM is using a static # of # threads or daemons. AP_MPMQ_DYNAMIC = 2 # This value specifies whether # an MPM is using a dynamic # of # threads or daemons.
AP_MPMQ_MAX_DAEMON_USED = 1 # Max # of daemons used so far AP_MPMQ_IS_THREADED = 2 # MPM can do threading AP_MPMQ_IS_FORKED = 3 # MPM can do forking AP_MPMQ_HARD_LIMIT_DAEMONS = 4 # The compiled max # daemons AP_MPMQ_HARD_LIMIT_THREADS = 5 # The compiled max # threads AP_MPMQ_MAX_THREADS = 6 # # of threads/child by config AP_MPMQ_MIN_SPARE_DAEMONS = 7 # Min # of spare daemons AP_MPMQ_MIN_SPARE_THREADS = 8 # Min # of spare threads AP_MPMQ_MAX_SPARE_DAEMONS = 9 # Max # of spare daemons AP_MPMQ_MAX_SPARE_THREADS = 10 # Max # of spare threads AP_MPMQ_MAX_REQUESTS_DAEMON= 11 # Max # of requests per daemon AP_MPMQ_MAX_DAEMONS = 12 # Max # of daemons by config
|