Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

...
  • 2 commits
  • 8 files changed
  • 0 commit comments
  • 2 contributors
49 COPYRIGHT
View
@@ -0,0 +1,49 @@
+/* ====================================================================
+ * Copyright (c) 2000 Gregory Trubetskoy. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY GREGORY TRUBETSKOY ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GREGORY TRUBETSKOY OR
+ * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software is based on the original concept
+ * as published in the book "Internet Programming with Python"
+ * by Aaron Watters, Guido van Rossum and James C. Ahlstrom,
+ * 1996 M&T Books, ISBN: 1-55851-484-8. The book and original
+ * software is Copyright 1996 by M&T Books.
+ *
+ * This software consists of an extension to the Apache http server.
+ * More information about Apache may be found at
+ *
+ * http://www.apache.org/
+ *
+ * More information on Python language can be found at
+ *
+ * http://www.python.org/
+ *
+ */
+
+
+
+
378 README
View
@@ -0,0 +1,378 @@
+Httpdapy 1.7b - Dec 1999
+
+Copyright Gregory Trubetskoy <grisha@ispol.com>
+
+Original concept and first code by Aaron Watters from "Internet
+Programming with Python" by Aaron Watters, Guido Van Rossum and James
+C. Ahlstrom, ISBN 1-55851-484-8
+
+*** If you are impatient, skip on to INSTALLATION! ***
+
+OVERVIEW
+
+Httpdapy allows embedding Python within a webserver for a considerable
+boost in performance and added flexibility in designing web based
+applications.
+
+Currently the only supported http server is Apache.
+
+DOCUMENTATION
+
+At the very least browse through this file, and make sure to read the doc
+string at the beginning of httpdapi.py module. The reason documentation is
+in separate places is that httpdapi.py is not meant to be server
+indepedent. All Apache-specific (installation and Apache options)
+information is in this README file.
+
+HISTORY
+
+While developing my first WWW applications a few years back, I found that
+using CGI for programs that need to connect to relational databases
+(commercial or not) is too slow because every hit requires loading of the
+interpreter executable which can be megabytes in size, any database
+libraries that can themselves be pretty big, plus, the database
+connection/authentication process carries a very significant overhead
+because it involves things like DNS resolutions, encryption, memory
+allocation, etc.. Under pressure to speed up the application, I nearly
+gave up the idea of using Python for the project and started researching
+other tools that claimed to specialize in www database integration. I did
+not have any faith in MS's ASP; was quite frustrated by Netscape
+LiveWire's slow performance and bugginess; Cold Fusion seemed promissing,
+but I soon learned that writing in html-like tags makes programs as
+readable as assembly. Same is true for PHP. Besides, I *really* wanted to
+write things in Python.
+
+Around the same time the IPWP book came out and the chapter describing how
+to embed Python within Netscape server immediately caught my attention.
+I used the example in my project, and developed an improved version of
+what I later called Nsapy that compiled on both Windows NT and Solaris.
+
+Although Nsapy only worked with Netscape servers, it was a very
+intelligent generic OO design that, in the spririt of Python, lended
+itself for easy portability to other web servers.
+
+Incidently, the popularity of Netscape's servers was taking a turn
+south, and so I set out to port Nsapy to other servers starting with
+the most popular one, Apache. And so from Nsapy was born Httpdapy.
+
+Don't ask me how to pronounce it. I don't know, I never had to. If you
+have ideas of a catchy and phonetically sensible name, e-mail me.
+
+WHAT'S NEW SINCE THE 0.x VERSIONS
+
+1. ZPublisher (formerly Bobo) support. The httpdapi_publisher module
+allows the use of ZPublisher (http://www.digicool.com/site/Bobo/) with
+httpdapi. See the module comments to find out how. Note that Zope
+(which is a development environment which includes ZPublisher) does
+not work reliably with Httpdapy yet. Zope does not have a good locking
+mechanism for its persistent object storage which creates a problem
+with Apache since apache runs as several separate processes any one of
+which can try to modify to the storage. I expect this will be resolved
+in the near future by producers of Zope.
+
+2. This version makes a major leap forward by introducing multiple
+interpreters. Scripts running in different directories will run in
+(almost) completely separate and clean namespace. At (apache) module
+initialization, a dictionary of interpreters keyed by interpreter name
+is created. Interpreter name can be any string. For every hit,
+Httpdapy will use the file parent directory path from the URI as
+interpreter name. If an interpreter by this name already exists, it
+will be used, else it is created. You can also force an interpreter
+name with PythonInterpreter directive (whose effects recurse into
+subdirectories). This is useful if you want to share an interpreter
+between separate directories.
+
+3. Autoreload mode. It is on by default. It makes the server keep
+track of module import time and forces a reload when the script file
+change time is later than the time of last import. (Don't confuse this
+with Python's default behaviour. In Python, once the module has been
+imported, nothing but the "reload" command will make the interpreter
+read the file again.)
+
+3. mod_python.c will cd into the directory to which the URL points and
+httpdapi.py will prepend a '.' to PYTHONPATH. This means that scripts
+can be imported from the current directory. This is more intuitive in
+my opinion than knowing that scripts are imported from somewhere in
+PYTHONPATH.
+
+For authentication, the current directory is the directory in which
+AuthPythonModule was last encountered.
+
+4. URL's no longer have to end with .pye. Httpdapy will now simply cut
+off the file extention to obtain the module name.
+
+5. To ease migration from CGI programs which usually have a lot of
+print statements, there are two new functions allowing redirection of
+stdout to the socket a la CGI. After a call self.hook_stdout() all
+stdout will be sent to the browser. A call to self.unhook_stdout()
+restores the old sys.stdout. Note that the first write to stdout will
+cause all the headers to be sent out, and therefore any header
+manupulation (e.g. by code in an overriden Headers() method) is
+meaningless. Also note that this is only a hack. I do not recommend
+you rely on this feature unless you absolutely have to.
+
+6. PythonInitFunction directive is now obsolete. It still works for
+backwards compatibility, but it forces the interpreter to function in
+"single interpreter" mode - i.e. all scripts share the same global
+namespace. Note that you can still use separate interpreters in single
+mode by forcing an interpreter name with PythonInterpreter directive.
+
+IS THIS SAME AS PYAPACHE?
+
+No, but they both attempt to solve the same problem - the inefficiency
+of CGI. Here are some specific differences:
+
+1. The development process for PyApache and Httpdapy is different. For
+PyApache you write CGI scripts. You get your info from the environment
+and write to stdout. With Httpdapy you have to inherit from the
+httpdapi.RequestHandler() class, and the content is sent by returning
+a string rather than writing to stdout.
+
+2. Httpdapy takes advantage of a new featrue in Python (since 1.5 I
+think) that allows creation of multiple sub-interpreters each having
+its own versions of imported modules, separate sys.modules,
+__builtin__, __main__, stdin, stdout, etc. The Python C API
+documentation describes it better here:
+
+http://www.python.org/doc/api/initialization.html#l2h-2379
+
+Httpdapy creates separate sub-interpreters for different directories
+in which scripts are located. So scripts /mydir/myscript.py and
+/hisdir/hisscript.py will run in separate interpreters. (There is also
+a way to override this and share sub-interpreters between
+directories.)
+
+As far as I understand mod_perl does something similar. PyApache does
+not do this. In PyApache, the sub-interpreter is reset (destroyed and
+recreated) for every hit, so you don't have different interpreters
+running in parallel.
+
+2. PyApache creates a sub-interpreter (Py_NewInterpreter()) for every
+request and destroys it when done. This means that if your script
+begins with "import HTMLgen", HTMLgen is imported (bytecode read from
+file) for every hit.
+
+Httpdapy keeps the interpreter around from the first hit and until the
+process dies. So only the first hit will actually read HTMLgen.py(c),
+all the subsequent won't.
+
+3. While PyApache is written in its entirety in C, Httpdapy only uses
+enough C to provide a "link" between python and the web server. Most
+of the actual functionality of Httpdapy is implemented in Python.
+Httpdapy's C code imports the module, instantiates a Python objects
+and from then on delegates handling all of the requests to that Python
+object.
+
+This is probably a tad slower than pure C, but it is more flexible this
+way and allows for a tighter integration with the user scripts I think.
+
+4. Httpdapy has a couple of features convenient for developers. It can
+write python traceback prints to the browser and will also re-import
+(using "reload" statement) scripts whose file change date is newer
+than the time of last import. Before I introduced this feature, one
+had to restart the server every time a change to a script was made.
+
+5. The httpdapi_publisher module provides plumbing for Zope. This
+still needs some work, but I think this is a very exciting
+feature. While Zope provides it's own tool for maintaining interpreter
+persistance that does not use embedding but instead requires you to
+run a separate "server" written in Python, I think embedding the
+interpreter within the http server is a better solution.
+
+REQUIREMENTS
+
+My testing was done with Python 1.5(.1) and Apache 1.3.3. It worked
+on Linux 2.0 and Solaris 2.5.1, and it should work on Windows NT. I
+haven tried compiling this version on NT, but the orgiinal 0.1b was
+actually first developed on NT.
+
+INSTALLATION
+
+If you want to compile it on Windows NT - you're on your own. It
+shouldn't be hard, I just don't feel like writing the instructions for
+it right now.
+
+The instrucitons below describe only the "Configure" method, not the new
+"Apaci" Apache configuration method. If you consider yourself a programmer,
+then you should feel right at home with "Configure" and a bit lost with
+"Apaci". At least I do.
+
+On UNIX, do this:
+
+1. Copy main/mod_python.c into src/modules/extra directory in Apache source
+tree.
+
+2. cd into the src directory in Apache source tree.
+
+3. Edit file Configuration so it has something like below. Edit
+EXTRA_LDFLAGS and EXTRA_LIBS to match your system, if you used additional
+libraries when compiling python, e.g. libreadline or libmysqlclient, they
+have to be in EXTRA_LIBS.
+
+This worked on Debian Linux:
+
+PY_LIB_DIR=/usr/local/lib/python1.5/config
+PY_INC_DIR=/usr/local/include/python1.5
+
+EXTRA_CFLAGS=-Wall
+EXTRA_LDFLAGS=-Xlinker -export-dynamic
+EXTRA_LIBS=$(PY_LIB_DIR)/libpython1.5.a -lreadline -lncurses -ldl -lm -lpthread
+EXTRA_INCLUDES=-I$(PY_INC_DIR)
+EXTRA_DEPS=
+
+On FreeBSD 3.3 (Python was installed using the ports collection) EXTRA_LIBS
+and EXTRA_LDFLAGS could look like this:
+
+EXTRA_LDFLAGS= -pthread -Xlinker -export-dynamic
+EXTRA_LIBS=$(PY_LIB_DIR)/libpython1.5.a -lmytinfo -lreadline -ltermcap -lm -lcrypt
+
+(You may want to try compiling without thread support on FreeBSD, I think there
+are some issues between apache and threads. You'll Python compiled without thread
+support for that. The ports collection Python has thread support enabled.)
+
+On Sun Solaris 7, I used this (no EXTRA_LDFLAGS necessary):
+
+EXTRA_LIBS=$(PY_LIB_DIR)/libpython1.5.a -lm -lthread -lpthread -ltermcap
+EXTRA_LDFLAGS=
+
+Then, somewhere down below in the Configuration file, add this:
+
+AddModule modules/extra/mod_python.o
+
+I recommend that it be the first AddModules line in the file, which means
+Python processing takes place LAST.
+
+4. Run ./Configure
+
+5. Run make. Now you should have an httpd program to use.
+
+From here on applicable to Windows also:
+
+6. Drop httpdapi.py and apache.py (found in main dir) into your pythonpath
+somewhere. An excellent place is /usr/local/lib/site-python.
+
+7. Drop httpdapitest.py and auth.py (found in sample dir) in a
+directory visible from the web. Do NOT make directory where your
+scripts are a ScriptAlias!
+
+8. Add this line to .htaccess or wherever else you prefer. NOTE that in
+order for AddHandler to work in .htaccess, you need to have AllowOverride
+FileInfo, so edit your httpd.conf accordingly:
+
+AddHandler python-program .py
+
+9. Restart the server if necessary. You should now be able to look at
+
+http://myserver/httpdapitest.py
+
+10. Now go read the comments at the top of httpdapi.py file about how to
+write your own programs. Enjoy!
+
+AUTHENTICATION
+
+If you want to do authentication via Python, put this in your .htaccess
+file:
+
+AuthPythonModule auth <-- replace "auth" with your module name
+AuthName "My Realm"
+AuthType Basic
+
+<Limit GET>
+require valid-user
+</Limit>
+
+make sure to look at auth.py and that it is in your pythonpath. You can
+replace auth with authDEBUG in your .htaccess to have the server reload the
+module every time - useful in debugging.
+
+TROUBLESHOOTING
+
+It is helpful to realize that httpdapitest.py may be read from your
+PYTHONPATH rather than the document directory of the server. To reduce
+possible confusion, Httpdapy always prepends a "." to sys.path (unless it
+is set explicitely with a PythonOption pythonpath). Also see SECURITY NOTE
+below.
+
+If you get server error, try adding this to your .htaccess:
+
+PythonOption debug 1
+
+This should print some traceback information about your Python error. If it
+didn't then something went wrong with the installation. Also check your
+error_log.
+
+If you're really having problems, edit mod_python.c and set debug
+to 1. Then run httpd from the command line with an -X option. This
+should print lots of debugging information. Also check the server
+error logs for errors.
+
+WHAT OTHER ARGUMENTS does PythonOption take?
+
+PythonOption takes two arguments, option name and option value. Whatever
+you set by PythonOption will be passed to user scripts as part of the
+self.pb parameter block. Options by PythonOption recurse into
+subdirectories. These values currently have special meaning to httpdapi:
+
+* "PythonOption debug 1" Turns debugging on. Default is off.
+
+* "PythonOption autoreload 0" Turns off autoreload mode for a very
+slight performance gain. The default is 1 (on).
+
+* "PythonOption rootpkg pkgname" "pkgname" will be prepended pkgname
+to all module names before they are imported. Good for keeping things
+organized and provides tighter security.
+
+* "PythonOption handler handlermodule" When a handler is set, all
+requests in this directory will be served by handlermodule only,
+regardless of what the URL says. This is useful for integration with
+Zope. See httpdapi_publisher.py for more details.
+
+* "PythonOption pythonpath path" allows specifying a pythonpath. When
+this option is present, Httpdapy will not prepend a "." to the
+path. The "path" argument will be processed with "eval" so it should
+be formatted accordingly. Here is an example:
+
+PythonOption pythonpath "['.','/usr/lib/python']"
+
+SECURITY NOTE
+
+So what if someone tries to execute python code from the standard
+Python library with a malicious intent? Since all the modules are
+imported from PYTHONPATH, doesn't that mean that anyone can do
+anything by calling the right URL's? The answer is: No, because any
+module that does not contain a RequestHandler class will error out.
+
+Still, this is a very valid concern, and I by no means gurarantee that
+Httpdapy has no security holes, though at this point I am not aware of
+any. For tighter security, always use the rootpkg option, as well as
+watch carefully what your pythonpath contains.
+
+APACHE NOTE
+
+It is important to understand that apache runs several processes that
+are every once in a while recycled to service requests. This means
+that if you assign a value to a variable in a script serviced by one
+child process, that variable is not visible from all the others. It
+also means that if you do any initialization, it may happen more than
+you might initially expect...
+
+Good Luck!
+
+Linux Note:
+
+You will encounter problems if your scripts use threads on Linux 2.0. The http
+server will appear to hang upon attempts to create new threads. This is because
+the LinuxThreads library (libpthreads) uses a signal (SIGUSR1) that is also in
+use by Apache. You can read more about the use of SIGUSR1 in LinuxThreads in
+the LinuxThreads FAQ at http://pauillac.inria.fr/~xleroy/linuxthreads/faq.html.
+I understand the issue with lack of signals has been addressed in the 2.1.x
+(soon to be 2.2) kernel.
+
+There is no simple resoulution to this problem other than not using threads in
+your programs. The FAQ suggests changing the LinuxThreads code switching it to
+use different signals. I have tried it and it works, but because LinuxThreads
+is now part of glibc, compiling the LinuxThreads library means compiling libc.
+To make a long story short, it is not something you want to do unless you
+really know what you are doing. A problem with libc may render your system
+unusable.
+
0  apache/__init__.py
View
No changes.
777 apache/apache.py
View
@@ -0,0 +1,777 @@
+
+"""
+ (C) Gregory Trubetskoy <grisha@ispol.com> May 1998, Nov 1998
+
+ This file is part of Httpdapy. See COPYRIGHT for Copyright.
+
+ Original concept and first code by Aaron Watters from
+ "Internet Programming with Python" by Aaron Watters,
+ Guido Van Rossum and James C. Ahlstrom, ISBN 1-55851-484-8
+
+ ==============================================================
+
+ HOW THIS MODULE WORKS
+
+ 1. At a request from the server, usually at startup and
+ whenever new interpreters are created, it (or rather a
+ server-specific module which imports this module right away) is
+ imported by the Python interpreter embedded in the server and
+ then the init() function is called.
+
+ Within init(), a Python object of CallBack class is created
+ and a variable holding a reference to it is set by init() using
+ an internal module called _apache. This reference is retained
+ for the lifetime of the server and is used by the server process
+ to service requests.
+
+ Note that Apache (and some others) routinely recylcles server
+ processes, therefore initialization happens more than once.
+
+ 2. When an HTTP request comes in the server determines if this is a
+ Python request. This is done differently on different servers
+ (mime types on Netscape or srm configuraition on Apache) but is
+ always based on the file extension.
+
+ If this is a Python request, then httpd will call the Service()
+ function of the callback object whose refernce it holds from step
+ 1 above.
+
+ The Service() function will:
+
+ Get the module name from the URI and import that module.
+ If the autoreload parameter is not 0, then last modification
+ time of the module will be checked and the module reloaded
+ if it is newer. Autoreload works even if debug is off.
+
+ Instantiate the RequestHandler object and call its
+ Handle() method passing it parameter block, session and
+ request objects.
+
+ These objects hold various information about the request
+ similar to what you would find in CGI environment variables.
+ To get a better idea of what is where, look at the output
+ of the httpdapitest.py - it shows all the variables. For
+ in-depth documentation, look at developer.netscape.com.
+
+ For example, http://localhost/home/myscript.pye
+ will result in the equivalent of:
+
+ >>> import myscript
+ >>> hr = myscript.RequestHandler(pb, sn, rq)
+ >>> hr.Handle()
+
+ Handle() in turn calls the following methods in the
+ following sequence:
+ Content()
+ Header()
+ Status()
+ Send()
+
+ You can override any one of these to provide custom headers,
+ alter the status and send out the text.
+
+ At the very least (and most often) you'll have to override Content().
+
+ Here is a minimal module:
+
+ import httpdapi
+
+ class RequestHandler(httpdapi.RequestHandler):
+ def Content(self):
+ return "<HTML><H1>Hello World!</H1></HTML>"
+
+ Here is a more elaborate one:
+
+ import httpdapi
+
+ class RequestHAndler(httpdapi.RequestHandler):
+ def Content(self):
+ self.redirect = "http://www.python.org"
+ return "<HTML>Your browser doesn't understand redirects!'</HTML>"
+
+ Here is how to get form data (doesn't matter POST or GET):
+
+ fd = self.form_data()
+
+ or, if you want to be sophisticated:
+
+ method = self.rq.reqpb['method']
+
+ if method == 'POST':
+ fdlen = atoi(self.rq.request_header("content-length", self.sn))
+ fd = cgi.parse_qs(self.sn.form_data(fdlen))
+ else:
+ fd = cgi.parse_qs(self.rq.reqpb['query'])
+
+ To cause specific HTTP error responses, you can raise SERVER_RETURN with a
+ pair (return_code, status) at any point. If status is not None it will serve
+ as the protocol_status, the return_code will be used as the return code
+ returned to the server-interface:
+
+ # Can't find the file!
+ raise SERVER_RETURN, (REQ_ABORTED, PROTOCOL_NOT_FOUND)
+
+ or to simply give up (eg, if the response already started):
+ raise SERVER_RETURN, (REQ_ABORTED, None)
+
+
+ 3. You can also do authentication in Python. In this case
+ AuthTrans() function of the callback object is called.
+
+ The AuthTrans function will:
+
+ get the module name from the configuration, import that module,
+ instantiate the AuthHandler object and call its
+ Handle() method passing it parameter block, session and
+ request objects:
+
+ Handle() can return any of these:
+ REQ_NOACTION - ask password again
+ REQ_ABORTED - Server Error
+ REQ_PROCEED - OK
+
+ You can also set the status to give out other responses, This will
+ show "Forbidden" on the browser:
+
+ self.rq.protocol_status(self.sn, httpdapi.PROTOCOL_FORBIDDEN)
+ return httpdapi.REQ_ABORTED
+
+ Here is a minimal module that lets grisha/mypassword in:
+
+ import httpdapi
+
+ class AuthHandler(httpdapi.AuthHandler):
+ def Handle(self):
+ user = self.rq.vars["auth-user"]
+ pw = self.rq.vars["auth-password"]
+ if user == 'grisha' and pw == 'mypassword':
+ return httpdapi.REQ_PROCEED
+ else:
+ return httpapi.REQ_NOACTION
+
+ That's basically it...
+
+"""
+
+import sys
+import string
+import traceback
+import time
+import os
+import stat
+import exceptions
+import types
+import _apache
+
+# XXX consider using intern() for some strings
+
+class CallBack:
+ """
+ A generic callback object.
+ """
+
+ def __init__(self, rootpkg=None, autoreload=None):
+ """
+ Constructor.
+ """
+
+ pass
+
+
+ def resolve_object(self, module_name, object_str):
+ """
+ This function traverses the objects separated by .
+ (period) to find the last one we're looking for.
+
+ The rules are:
+ 1. try the object directly,
+ failing that
+ 2. from left to right, find objects, if it is
+ a class, instantiate it passing the request
+ as single argument
+ """
+
+ # to bring the module in the local scope, we need to
+ # import it again, this shouldn't have any significant
+ # performance impact, since it's already imported
+
+ exec "import " + module_name
+
+ try:
+ obj = eval("%s.%s" % (module_name, object_str))
+ if hasattr(obj, "im_self") and not obj.im_self:
+ # this is an unbound method, it's class
+ # needs to be insantiated
+ raise AttributeError, obj.__name__
+ else:
+ # we found our object
+ return obj
+
+ except AttributeError, attr:
+
+ # try to instantiate attr before attr in error
+ list = string.split(object_str, '.')
+
+ i = list.index(str(attr))
+ klass = eval(string.join([module_name] + list[:i], "."))
+
+ # is this a class?
+ if type(klass) == types.ClassType:
+ obj = klass()
+ return eval("obj." + string.join(list[i:], "."))
+ else:
+ raise "ResolveError", "Couldn't resolve object '%s' in module '%s'." % \
+ (object_str, module_name)
+
+ def Dispatch(self, req, htype):
+ """
+ This is the handler dispatcher.
+ """
+
+ # be cautious
+ result = HTTP_INTERNAL_SERVER_ERROR
+
+ # request
+ self.req = req
+
+ # config
+ self.config = req.get_config()
+
+ # process options
+ autoreload, rootpkg, debug, pythonpath = 1, None, None, None
+ self.opt = req.get_options()
+ if self.opt.has_key("autoreload"):
+ autoreload = self.opt["autoreload"]
+ if self.opt.has_key("rootpkg"):
+ rootpkg = self.opt["rootpkg"]
+ if self.opt.has_key("debug"):
+ debug = self.opt["debug"]
+ if self.opt.has_key("pythonpath"):
+ pythonpath = self.opt["pythonpath"]
+
+ try:
+ # cycle through the handlers
+ handlers = string.split(self.config[htype])
+
+ for handler in handlers:
+
+ # split module::handler
+ module_name, object_str = string.split(handler, '::', 1)
+
+ # import module and find the object
+ module = import_module(module_name, req)
+ object = self.resolve_object(module_name, object_str)
+
+ # call the object
+ result = object(req)
+
+ if result != OK:
+ break
+
+
+ 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 type(value) == type(()):
+ (result, status) = value
+ if status:
+ req.status = status
+ else:
+ result, status = value, value
+ except:
+ pass
+
+ except PROG_TRACEBACK, traceblock:
+ # Program run-time error
+ try:
+ (etype, value, traceback) = traceblock
+ result = self.ReportError(etype, value, traceback,
+ htype=htype, 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,
+ htype=htype, hname=handler, debug=debug)
+ finally:
+ exc_traceback = None
+
+ return result
+
+
+ def ReportError(self, etype, evalue, etb, htype="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:
+ req = self.req
+
+ if str(etype) == "exceptions.IOError" \
+ and str(evalue)[:5] == "Write":
+ # if this is an IOError while writing to client,
+ # it is probably better to write to the log file
+ # even if debug is on.
+ debug = 0
+
+ if debug:
+
+ # replace magnus-internal/X-python-e with text/html
+ req.content_type = 'text/html'
+
+ #req.status = 200 # OK
+ req.send_http_header()
+
+ s = "<html><h3>mod_python: Python error:</h3>\n<pre>\n"
+ s = s + "<b>Handler: %s %s</b>\n<blockquote>\n" % (htype, hname)
+ for e in traceback.format_exception(etype, evalue, etb):
+ s = s + e + '\n'
+ s = s + "</blockquote>\n<b>End of output for %s %s</b>.\n" % (htype, hname)
+ s = s + "<em>NOTE: More output from other handlers, if any, may follow.\n"
+ s = s + "This will NOT happen, and request processing will STOP\n"
+ s = s + "at this point when you unset PythonOption debug.</em>\n\n"
+ s = s + "</pre></html>\n"
+
+ req.write(s)
+
+ return OK
+
+ else:
+ for e in traceback.format_exception(etype, evalue, etb):
+ s = "%s %s: %s" % (htype, hname, e[:-1])
+ _apache.log_error(s, APLOG_NOERRNO|APLOG_ERR, req.server)
+
+ return HTTP_INTERNAL_SERVER_ERROR
+ finally:
+ # erase the traceback
+ etb = None
+
+def import_module(module_name, req=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.
+ """
+
+ # get the options
+ autoreload, rootpkg, debug, pythonpath = 1, None, None, None
+ if req:
+ opt = req.get_options()
+ if opt.has_key("autoreload"):
+ autoreload = opt["autoreload"]
+ if opt.has_key("rootpkg"):
+ rootpkg = opt["rootpkg"]
+ if opt.has_key("debug"):
+ debug = opt["debug"]
+ if opt.has_key("pythonpath"):
+ pythonpath = opt["pythonpath"]
+
+ # unless pythonpath is set explicitely
+ if pythonpath:
+ sys.path = eval(pythonpath)
+ else:
+ # add '.' to sys.path
+ if '.' not in sys.path:
+ sys.path[:0] = ['.']
+
+ # if we're using packages
+ if rootpkg:
+ module_name = rootpkg + "." + module_name
+
+ # try to import the module
+ try:
+
+ oldmtime = None
+ mtime = None
+
+ if not autoreload:
+
+ # we could use __import__ but it can't handle packages
+ exec "import " + module_name
+ module = eval(module_name)
+
+ else:
+
+ # keep track of file modification time and
+ # try to reload it if it is newer
+ if sys.modules.has_key(module_name):
+
+ # the we won't even bother importing
+ module = sys.modules[module_name]
+
+ # does it have __mtime__ ?
+ if sys.modules[module_name].__dict__.has_key("__mtime__"):
+ # remember it
+ oldmtime = sys.modules[ module_name ].__mtime__
+
+ # import the module for the first time
+ else:
+
+ # we could use __import__ but it can't handle packages
+ exec "import " + module_name
+ module = eval(module_name)
+
+ # find out the last modification time
+ # but only if there is a __file__ attr
+ if module.__dict__.has_key("__file__"):
+
+ filepath = module.__file__
+
+ if os.path.exists(filepath):
+
+ mod = os.stat(filepath)
+ mtime = mod[stat.ST_MTIME]
+
+ # check also .py and take the newest
+ if os.path.exists(filepath[:-1]) :
+
+ # get the time of the .py file
+ mod = os.stat(filepath[:-1])
+ mtime = max(mtime, mod[stat.ST_MTIME])
+
+ # if module is newer - reload
+ if (autoreload and (oldmtime < mtime)):
+ module = reload(module)
+
+ # save mtime
+ module.__mtime__ = mtime
+
+ return module
+
+ except (ImportError, AttributeError, SyntaxError):
+
+ if debug :
+ # pass it on
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ raise exc_type, exc_value
+ else:
+ # show and HTTP error
+ raise SERVER_RETURN, HTTP_INTERNAL_SERVER_ERROR
+
+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 = {}
+ for k in req.subprocess_env.keys():
+ env[k] = req.subprocess_env[k]
+
+ 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(string.joinfields(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:
+ s = self.req.read(n)
+ self.pos = self.pos + len(s)
+ return s
+
+ def readlines(self):
+ s = string.split(self.buf + self.read(), '\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 = string.find(self.buf, '\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 = string.find(self.buf, '\n', x)
+
+ # carve out the piece, then shorten the buffer
+ result = self.buf[:i+1]
+ self.buf = self.buf[i+1:]
+ 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
+ ss = string.split(self.headers, '\n\n', 1)
+ if len(ss) < 2:
+ # headers not over yet
+ pass
+ else:
+ # headers done, process them
+ string.replace(ss[0], '\r\n', '\n')
+ lines = string.split(ss[0], '\n')
+ for line in lines:
+ h, v = string.split(line, ":", 1)
+ if string.lower(h) == "status":
+ status = int(string.split(v)[0])
+ self.req.status = status
+ elif string.lower(h) == "content-type":
+ self.req.content_type = string.strip(v)
+ else:
+ v = string.strip(v)
+ self.req.headers_out[h] = v
+ self.req.send_http_header()
+ 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 reead/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().
+ """
+
+ osenv = os.environ
+
+ # save env
+ env = eval(`osenv`)
+
+ si = sys.stdin
+ so = sys.stdout
+
+ env = build_cgi_env(req)
+ # the environment dictionary cannot be replace
+ # because some other parts of python already hold
+ # a reference to it. it must be edited "by hand"
+
+ for k in osenv.keys():
+ del osenv[k]
+ for k in env.keys():
+ osenv[k] = env[k]
+
+ sys.stdout = CGIStdout(req)
+ sys.stdin = CGIStdin(req)
+
+ sys.argv = [] # keeps cgi.py happy
+
+ return env, si, so
+
+def restore_nocgi(env, si, so):
+ """ see hook_stdio() """
+
+ osenv = os.environ
+
+ # restore env
+ for k in osenv.keys():
+ del osenv[k]
+ for k in env.keys():
+ osenv[k] = env[k]
+
+ sys.stdout = si
+ sys.stdin = so
+
+def init():
+ """
+ This function is called by the server at startup time
+ """
+
+ # create a callback object
+ obCallBack = CallBack()
+
+ import _apache
+
+ # "give it back" to the server
+ _apache.SetCallBack(obCallBack)
+
+## Some functions made public
+make_table = _apache.make_table
+log_error = _apache.log_error
+
+
+## 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
+
+
+
+
+SERVER_RETURN = "SERVER_RETURN"
+PROG_TRACEBACK = "PROG_TRACEBACK"
+OK = REQ_PROCEED = 0
+HTTP_INTERNAL_SERVER_ERROR = REQ_ABORTED = 500
+DECLINED = REQ_NOACTION = -1
+REQ_EXIT = "REQ_EXIT"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
49 apache/cgihandler.py
View
@@ -0,0 +1,49 @@
+"""
+ (C) Gregory Trubetskoy, 1998 <grisha@ispol.com>
+
+"""
+
+import apache
+import imp
+import os
+
+def handle(req):
+
+ # get the filename of the script
+ if req.subprocess_env.has_key("script_filename"):
+ dir, file = os.path.split(req.subprocess_env["script_filename"])
+ else:
+ dir, file = os.path.split(req.filename)
+ module_name, ext = os.path.splitext(file)
+
+ # we must chdir, because mod_python will cd into
+ # directory where the handler directive was last
+ # encountered, which is not always the same as
+ # where the file is....
+ os.chdir(dir)
+
+ try:
+
+ # simulate cgi environment
+ env, si, so = apache.setup_cgi(req)
+
+ try:
+ # we do not search the pythonpath (security reasons)
+ fd, path, desc = imp.find_module(module_name, [dir])
+ except ImportError:
+ raise apache.SERVER_RETURN, apache.HTTP_NOT_FOUND
+
+
+ # this executes the module
+ imp.load_module(module_name, fd, path, desc)
+
+ return apache.OK
+
+ finally:
+ # unsimulate the cgi environment
+ apache.restore_nocgi(env, si, so)
+ try:
+ fd.close()
+ except: pass
+
+
456 apache/httpdapi.py
View
@@ -0,0 +1,456 @@
+"""
+ (C) Gregory Trubetskoy <grisha@ispol.com> May 1998, Nov 1998, Apr 2000
+
+ Httpdapy handler module.
+"""
+
+import string
+import sys
+import apache
+import os
+
+# Response status codes for use with rq.protocol_status(sn, *)
+
+
+SERVER_RETURN = apache.SERVER_RETURN
+PROG_TRACEBACK = apache.PROG_TRACEBACK
+REQ_PROCEED = apache.REQ_PROCEED
+REQ_ABORTED = apache.REQ_ABORTED
+REQ_NOACTION = apache.REQ_NOACTION
+REQ_EXIT = apache.REQ_EXIT
+
+PROTOCOL_CONTINUE = apache.HTTP_CONTINUE
+PROTOCOL_SWITCHING = apache.HTTP_SWITCHING_PROTOCOLS
+PROTOCOL_OK = apache.HTTP_OK
+PROTOCOL_CREATED = apache.HTTP_CREATED
+PROTOCOL_NO_RESPONSE = apache.HTTP_NO_CONTENT
+PROTOCOL_PARTIAL_CONTENT = apache.HTTP_PARTIAL_CONTENT
+PROTOCOL_REDIRECT = apache.HTTP_MOVED_TEMPORARILY
+PROTOCOL_NOT_MODIFIED = apache.HTTP_NOT_MODIFIED
+PROTOCOL_BAD_REQUEST = apache.HTTP_BAD_REQUEST
+PROTOCOL_UNAUTHORIZED = apache.HTTP_UNAUTHORIZED
+PROTOCOL_FORBIDDEN = apache.HTTP_FORBIDDEN
+PROTOCOL_NOT_FOUND = apache.HTTP_NOT_FOUND
+PROTOCOL_METHOD_NOT_ALLOWED = apache.HTTP_METHOD_NOT_ALLOWED
+PROTOCOL_PROXY_UNAUTHORIZED = apache.HTTP_PROXY_AUTHENTICATION_REQUIRED
+PROTOCOL_CONFLICT = apache.HTTP_CONFLICT
+PROTOCOL_LENGTH_REQUIRED = apache.HTTP_LENGTH_REQUIRED
+PROTOCOL_PRECONDITION_FAIL = apache.HTTP_PRECONDITION_FAILED
+PROTOCOL_ENTITY_TOO_LARGE = apache.HTTP_REQUEST_ENTITY_TOO_LARGE
+PROTOCOL_URI_TOO_LARGE = apache. HTTP_REQUEST_URI_TOO_LARGE
+PROTOCOL_SERVER_ERROR = apache.HTTP_INTERNAL_SERVER_ERROR
+PROTOCOL_VERSION_NOT_SUPPORTED = apache.HTTP_VERSION_NOT_SUPPORTED
+PROTOCOL_NOT_IMPLEMENTED = apache.HTTP_NOT_IMPLEMENTED
+
+Status = {
+ "100" : PROTOCOL_CONTINUE,
+ "101" : PROTOCOL_SWITCHING,
+ "200" : PROTOCOL_OK,
+ "201" : PROTOCOL_CREATED,
+ "204" : PROTOCOL_NO_RESPONSE,
+ "206" : PROTOCOL_PARTIAL_CONTENT,
+ "302" : PROTOCOL_REDIRECT,
+ "304" : PROTOCOL_NOT_MODIFIED,
+ "400" : PROTOCOL_BAD_REQUEST,
+ "401" : PROTOCOL_UNAUTHORIZED,
+ "403" : PROTOCOL_FORBIDDEN,
+ "404" : PROTOCOL_NOT_FOUND,
+ "405" : PROTOCOL_METHOD_NOT_ALLOWED,
+ "407" : PROTOCOL_PROXY_UNAUTHORIZED,
+ "409" : PROTOCOL_CONFLICT,
+ "411" : PROTOCOL_LENGTH_REQUIRED,
+ "412" : PROTOCOL_PRECONDITION_FAIL,
+ "413" : PROTOCOL_ENTITY_TOO_LARGE,
+ "414" : PROTOCOL_URI_TOO_LARGE,
+ "500" : PROTOCOL_SERVER_ERROR,
+ "501" : PROTOCOL_NOT_IMPLEMENTED,
+ "505" : PROTOCOL_VERSION_NOT_SUPPORTED
+ }
+
+def Service(req):
+ """
+ """
+
+ # be pessimistic
+ result = apache.DECLINED
+
+ try:
+
+ # get filename
+ filename = req.filename
+
+ # module names do not have to end with .py
+ # they can have any extension or no extention at all
+ # the extention will be discarded
+
+ # find the module name by getting the string between the
+ # last slash and the last dot, if any.
+
+ slash = string.rfind(filename, "/")
+ dot = string.rfind(filename, ".")
+
+ if dot > slash:
+ module_name = filename[slash + 1:dot]
+ else:
+ # this file has no extension
+ module_name = filename[slash + 1:]
+
+ opt = req.get_options()
+ if opt.has_key("debug"):
+ debug = opt["debug"]
+
+ if opt.has_key("handler"):
+ module_name = opt["handler"]
+
+ # cd into the uri directory
+ if os.path.isdir(filename):
+ os.chdir(filename)
+ else:
+ os.chdir(filename[:slash])
+
+ # import the module
+ module = apache.import_module(module_name, req)
+
+ # instantiate the handler class
+ Class = module.RequestHandler
+
+ # construct and return an instance of the handler class
+ handler = Class(req)
+
+ # do it
+ result = handler.Handle(debug=debug)
+
+ except apache.SERVER_RETURN, value:
+ # SERVER_RETURN indicates a non-local abort from below
+ # with value as (result, status) or (result, None)
+ try:
+ (result, status) = value
+ if status:
+ req.status = status
+ except:
+ pass
+
+ return result
+
+
+class RequestHandler:
+ """
+ A superclass that may be used to create RequestHandlers
+ in other modules, for use with this module.
+ """
+
+ def __init__(self, req):
+
+ self.req = req
+
+ ## backward compatibility objects - pb, sn, rq
+ self.pb = NSAPI_ParameterBlock(req)
+ self.rq = NSAPI_Request(req)
+ self.sn = NSAPI_Session(req)
+
+ # default content-type
+ self.content_type = 'text/html'
+
+ # no redirect
+ self.redirect = ''
+
+ def Send(self, content):
+
+ if content:
+ # Apache doesn't want us to send content when using
+ # redirects, it puts up a default page.
+ if not self.redirect:
+ self.rq.start_response(self.sn)
+ self.sn.net_write(str(content))
+
+ def Header(self):
+ """
+ This prepares the headers
+ """
+
+ srvhdrs = self.rq.srvhdrs
+
+ # content-type
+ srvhdrs["content-type"] = self.content_type
+
+ # for redirects, add Location header
+ if self.redirect:
+ srvhdrs["Location"] = self.redirect
+
+ def Status(self):
+ """
+ The status is set here.
+ """
+ if self.redirect:
+ self.rq.protocol_status(self.sn, PROTOCOL_REDIRECT)
+ else:
+ self.rq.protocol_status(self.sn, PROTOCOL_OK)
+
+ def Handle(self, debug=0):
+ """
+ This method handles the request. Although, you may be
+ tempted to override this method, you should consider
+ overriding Content() first, it may be all you need.
+ """
+ try:
+ content = self.Content()
+ self.Header()
+ self.Status()
+ self.Send(content)
+ except:
+ # debugging ?
+ if debug:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ raise PROG_TRACEBACK, (exc_type, exc_value, exc_traceback)
+ return HTTP_INTERNAL_SERVER_ERROR
+
+ return REQ_PROCEED
+
+ def Content(self):
+ """
+ For testing and reference
+ """
+ return "Welcome to Httpdapi!"
+
+ def form_data(self):
+ """
+ Utility function to get the data passed via
+ POST or GET. Returns a dictionary keyed by name.
+ """
+
+ method = self.rq.reqpb['method']
+
+ if method == 'POST':
+ fdlen = int(self.rq.request_header("content-length", self.sn))
+ fd = cgi.parse_qs(self.sn.form_data(fdlen))
+ else:
+ fd = cgi.parse_qs(self.rq.reqpb['query'])
+
+ return fd
+
+ def build_cgi_env(self):
+ """
+ Utility function that returns a dictionary of
+ CGI environment variables as described in
+ http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+ """
+
+ return apache.build_cgi_env(self.req)
+
+ def hook_stdout(self):
+ """
+ Replace sys.stdout with an object that writes to the output
+ socket. Saves a copy of stdout so you can use unhook_stdout
+ later.
+ """
+
+ self.save_stdout = sys.stdout
+ sys.stdout = UnbufferedStdout(self.rq, self.sn)
+
+ def unhook_stdout(self):
+ """ see hook_stdout() """
+
+ try:
+ sys.stdout = self.save_stdout
+ except:
+ pass
+
+
+class AuthHandler(RequestHandler):
+
+ def Handle(self):
+
+ return REQ_PROCEED
+
+
+class UnbufferedStdout:
+
+ """Class that allows writing to stdout a la CGI
+ """
+
+ def __init__(self, rq, sn):
+ self.pos = 0
+ self.sn = sn
+ self.rq = rq
+
+ def close(self):
+ pass
+
+ def isatty(self):
+ return 0
+
+ def seek(self, pos, mode = 0):
+ pass
+
+ def tell(self):
+ return self.pos
+
+ def read(self, n = -1):
+ return ""
+
+ def readline(self, length = None):
+ return ""
+
+ def readlines(self):
+ return []
+
+ def write(self, s):
+
+ if not s: return
+
+ self.rq.start_response(self.sn)
+ self.sn.net_write(str(s))
+
+ self.pos = self.pos + len(s)
+
+ def writelines(self, list):
+ self.write(string.joinfields(list, ''))
+
+ def flush(self):
+ pass
+
+#####
+# from here on - backward compatibility
+
+class NSAPI_Pblock:
+
+ """This is basically a wrapper around the table object
+ """
+
+ def __init__(self, table):
+ self.table = table
+
+ def pblock2str(self):
+ s = ''
+ for key in self.table.keys():
+ s = s + '%s="%s" ' % (key, value)
+ return s
+
+ def nvinsert(self, name, value):
+ self.table[name] = value
+
+ def findval(name):
+ return self.table[name]
+
+ def pblock_remove(self, name):
+ del self.table[name]
+
+ def has_key(self, name):
+ return self.table.has_key(name)
+
+ def keys(self):
+ return self.table.keys()
+
+ def __getitem__(self, name):
+ return self.table[name]
+
+ def __setitem__(self, name, value):
+ self.table[name] = value
+
+ def __repr__(self):
+ return `self.table`
+
+
+def NSAPI_ParameterBlock(req):
+
+ pb = apache.make_table()
+ conf = req.get_config()
+ opt = req.get_options()
+
+ for k in conf.keys():
+ pb[k] = conf[k]
+ for k in opt.keys():
+ pb[k] = opt[k]
+ pb["fn"] = "python_request_handler"
+ pb["method"] = "GET|HEAD|POST"
+ pb["server-software"] = "Apache"
+ pb["type"] = req.content_type
+ pw = req.get_basic_auth_pw()
+ if pw:
+ pb["auth-password"] = pw
+ pb["auth-type"] = req.connection.ap_auth_type
+ pb["auth-user"] = req.connection.user
+
+ return NSAPI_Pblock(pb)
+
+
+class NSAPI_Request:
+
+ """ This is the old request object
+ """
+
+ def __init__(self, req):
+ self.req = req
+ self.response_started = 0
+
+ # reqpb
+ self.reqpb = apache.make_table()
+ self.reqpb["clf-request"] = self.req.the_request
+ self.reqpb["method"] = self.req.method
+ self.reqpb["protocol"] = self.req.subprocess_env["SERVER_PROTOCOL"]
+ self.reqpb["uri"] = self.req.uri
+ self.reqpb["query"] = self.req.subprocess_env["QUERY_STRING"]
+
+ # headers
+ self.headers = self.req.headers_in
+
+ # srvhdrs
+ self.srvhdrs = self.req.headers_out
+
+ # vars
+ self.vars = apache.make_table()
+ pw = self.req.get_basic_auth_pw()
+ if pw:
+ self.vars["auth-password"] = pw
+ if self.req.connection.ap_auth_type:
+ self.vars["auth-type"] = self.req.connection.ap_auth_type
+ if self.req.connection.user:
+ self.vars["auth-user"] = self.req.connection.user
+ if self.req.path_info:
+ self.vars["path-info"] = self.req.path_info
+ if self.req.subprocess_env.has_key("PATH_TRANSLATED"):
+ self.vars["path-translated"] = self.req.subprocess_env["PATH_TRANSLATED"]
+ if self.req.filename:
+ self.vars["path"] = self.req.filename
+
+
+ def start_response(self, sn):
+
+ if not self.response_started:
+ self.req.content_type = self.req.headers_out["content-type"]
+ self.req.send_http_header()
+ self.response_started = 1
+
+ def request_header(self, header, session=None):
+ return self.req.headers_in[string.lower(header)]
+
+
+ def protocol_status(self, sn, status):
+ self.req.status = status
+
+ def log_err(self, function, message, sno):
+ s = "for host %s trying to %s, %s reports: %s" % \
+ (self.req.connection.remote_ip, self.req.the_request, function, message)
+ apache.log_error(APLOG_NOERRNO|APLOG_ERR, self.req.server, s)
+
+
+class NSAPI_Session:
+
+ def __init__(self, req):
+ self.req = req
+
+ def session_dns(self):
+ return self.req.connection.remote_logname
+
+ def net_write(self, what):
+ return self.req.write(what)
+
+ def client(self):
+ client = apache.make_table()
+ client["ip"] = self.req.connection.remote_ip
+ client["dns"] = self.req.connection.remote_host
+ return client
+
+ def net_read(self, len):
+ return self.req.read(len)
+
115 apache/zhandler.py
View
@@ -0,0 +1,115 @@
+"""
+ (C) Gregory Trubetskoy, 1998 <grisha@ispol.com>
+
+ This file is part of Httpdapy.
+
+ This module allows one to use the Z Object Publisher (formerly Bobo) with
+ Httpdapy. This gives you the power of Zope object publishing along with the
+ speed of Httpdapy. It doesn't get any better than this!
+
+ WHAT IS THIS ZPublisher?????
+
+ ZPublisher is a component of Zope. While I don't profess at Zope itself as it
+ seems to be designed for different type of users than me, I do think that the
+ ZPublisher provides an ingenously simple way of writing WWW applications in
+ Python.
+
+ Take a look at the zpublisher_hello.py file. Notice how it has one method
+ defined in it. Through ZPublisher, that method can be invoked through the web
+ via a URL similar to this:
+
+ http://www.domain.tld/site/zpublisher_hello/sayHello and
+ http://www.domain.tld/site/zpublisher_hello/sayHello?name=Joe
+
+ If the above didn't "click" for you, go read the ZPublisher documentation at
+ http://classic.zope.org:8080/Documentation/Reference/ObjectPublishingIntro
+ for a more in-depth explanation.
+
+ QUICK START
+
+ 1. Download and install Zope.
+ 2. Don't start it. You're only interested in ZPublisher, and in order for
+ it to work, Zope doesn't need to be running.
+ 3. Pick a www directory where you want to use ZPublisher. For our purposes
+ let's imagine it is accessible via http://www.domain.tld/site.
+ 4. Make sure that the FollowSymLinks option is on for this directory
+ in httpd.conf.
+ 5. Make a symlink in this directory to the ZPublisher directory:
+ cd site
+ ln -s /usr/local/src/Zope-2.1.0-src/lib/python/ZPublisher .
+ 5. Verify that it is correct:
+ ls -l
+ lrwxr-xr-x 1 uid group 53 Dec 13 12:15 ZPublisher -> /usr/local/src/Zope-2.1.0-src/lib/python/ZPublisher
+ 6. Create an .htaccess file with this in it:
+ SetHandler python-program
+ PythonOption handler httpdapi_publisher
+ PythonOption debug 1
+ 7. Look at http://www.domain.tld/site/zpublisher_hello/sayHello?name=Joe
+
+ Noteworthy:
+
+ This module automatically reloads modules just like httpdapy does with
+ autoreload on. But modules that are imported by your code will not get
+ reloaded. There are ways around having to restart the server for script
+ changes to take effect. For example, let's say you have a module called
+ mycustomlib.py and you have a module that imports it. If you make a changes
+ to mycustomlib.py, you can force the changes to take effect by requesting
+ http://www.domain.tld/site/mycustomlib/. You will get a server error, but
+ mycustomelib should get reloaded.
+
+ P.S.: Previous versions of this file contained references on how to get Zope
+ (not just Zpublisher, but the whole shabang) work. Don't bother with it - it
+ won't work with httpdapy. This is because of locking issues. Older versions
+ of Zope had no locking, so different children of apache would corrupt the
+ database by trying to access it at the same time. Starting with version 2
+ Zope does have locking, however, it seems that the first child locks the
+ database without ever releasing it and after that no other process can acess
+ it.
+
+"""
+
+import apache
+import os
+import sys
+
+try:
+ import ZPublisher
+except ImportError:
+ import cgi_module_publisher
+ ZPublisher = cgi_module_publisher
+
+def publish(req):
+
+ conf = req.get_config()
+
+ # get module name to be published
+ dir, file = os.path.split(req.filename)
+ module_name, ext = os.path.splitext(file)
+
+ # if autoreload is on, we will check dates
+ # and reload the module if the source is newer
+ apache.import_module(module_name, req)
+
+ # setup CGI environment
+ env, si, so = apache.setup_cgi(req)
+
+ try:
+ ZPublisher.publish_module(module_name, stdin=sys.stdin,
+ stdout=sys.stdout, stderr=sys.stderr,
+ environ=os.environ)
+ s = `req.headers_out`
+ f = open("/tmp/XXX", "w")
+ f.write(s)
+ f.close()
+ finally:
+ apache.restore_nocgi(env, si, so)
+
+ return apache.OK
+
+
+
+
+
+
+
+
2,611 src/mod_python.c
View
@@ -0,0 +1,2611 @@
+/*
+ * (C) Gregory Trubetskoy <grisha@ispol.com> Nov 1998
+ *
+ * mod_python.c
+ *
+ * $Id: mod_python.c,v 1.1 2000/05/04 23:08:24 grisha Exp $
+ *
+ * See accompanying documentation and source code comments
+ * for details. See COPYRIGHT file for Copyright.
+ *
+ * Apr 2000 - rename to mod_python and go apache-specific.
+ * Nov 1998 - support for multiple interpreters introduced.
+ * May 1998 - initial release.
+ *
+ */
+
+
+/* Apache headers */
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "util_script.h"
+#include "http_log.h"
+
+/* Python headers */
+#include "Python.h"
+#include "structmember.h"
+
+/******************************************************************
+ Declarations
+ ******************************************************************/
+
+#define VERSION_COMPONENT "mod_python/2.0a"
+#define MODULENAME "apache"
+#define INITSTRING "apache.init()"
+#define INTERP_ATTR "__interpreter__"
+
+/* debugging? Use ./httpd -X when on */
+static int debug = 1;
+
+/* Are we in single interpreter mode? */
+static int single_mode = 0;
+
+/* List of available Python obCallBacks/Interpreters
+ * (In a Python dictionary) */
+static PyObject * interpreters = NULL;
+
+/* The CallBack object. This variable is used as
+ * a way to pass a pointer between SetCallBack()
+ * function and make_obcallback(). Nothing else.
+ * Don't rely on its value. */
+static PyObject *obCallBack = NULL;
+
+/* This function is used with ap_register_cleanup() */
+void noop(void *data) {};
+void python_decref(void *object);
+
+/* some forward declarations */
+PyObject * make_obcallback(const char *module, const char *initstring);
+PyObject * tuple_from_array_header(const array_header *ah);
+PyObject * get_obcallback(const char *name, server_rec * req);
+
+/*********************************
+ Python things
+ *********************************/
+/*********************************
+ members of _apache module
+ *********************************/
+
+/* froward declarations */
+static PyObject * SetCallBack(PyObject *self, PyObject *args);
+static PyObject * log_error(PyObject *self, PyObject *args);
+static PyObject * make_table(PyObject *self, PyObject *args);
+
+/* methods of _apache */
+static struct PyMethodDef _apache_module_methods[] = {
+ {"SetCallBack", (PyCFunction)SetCallBack, METH_VARARGS},
+ {"log_error", (PyCFunction)log_error, METH_VARARGS},
+ {"make_table", (PyCFunction)make_table, METH_VARARGS},
+ {NULL, NULL} /* sentinel */
+};
+
+
+/********************************
+ tableobject
+ ********************************/
+
+typedef struct tableobject {
+ PyObject_VAR_HEAD
+ table *table;
+ pool *pool;
+} tableobject;
+
+static PyTypeObject tableobjecttype;
+
+#define is_tableobject(op) ((op)->ob_type == &tableobjecttype)
+
+static PyObject * tablegetitem (tableobject *self, PyObject *key );
+static PyObject * table_has_key (tableobject *self, PyObject *args );
+static PyObject * table_keys (tableobject *self);
+static int tablelength (tableobject *self);
+static int tablesetitem (tableobject *self, PyObject *key, PyObject *val);
+static int tb_setitem (tableobject *self, const char *key, const char *val);
+static tableobject * make_tableobject(table * t);
+
+static PyMethodDef tablemethods[] = {
+ { "keys", (PyCFunction)table_keys, 1},
+ { "has_key", (PyCFunction)table_has_key, 1},
+ { NULL, NULL } /* sentinel */
+};
+
+static PyMappingMethods table_mapping = {
+ (inquiry) tablelength, /*mp_length*/
+ (binaryfunc) tablegetitem, /*mp_subscript*/
+ (objobjargproc) tablesetitem, /*mp_ass_subscript*/
+};
+
+/* another forward */
+tableobject * headers_in(request_rec *req);
+
+/********************************
+ serverobject
+ ********************************/
+
+typedef struct serverobject {
+ PyObject_HEAD
+ server_rec *server;
+ PyObject *next;
+} serverobject;
+
+static PyTypeObject serverobjecttype;
+
+#define is_serverobject(op) ((op)->ob_type == &serverobjecttype)
+
+#define OFF(x) offsetof(server_rec, x)
+static struct memberlist server_memberlist[] = {
+ {"defn_name", T_STRING, OFF(defn_name), RO},
+ {"defn_line_number", T_INT, OFF(defn_line_number), RO},
+ {"srm_confname", T_STRING, OFF(srm_confname), RO},
+ {"access_confname", T_STRING, OFF(access_confname), RO},
+ {"server_admin", T_STRING, OFF(server_admin), RO},
+ {"server_hostname", T_STRING, OFF(server_hostname), RO},
+ {"port", T_SHORT, OFF(port), RO},
+ {"error_fname", T_STRING, OFF(error_fname), RO},
+ {"loglevel", T_INT, OFF(loglevel), RO},
+ {"is_virtual", T_INT, OFF(is_virtual), RO},
+ /* XXX implement module_config ? */
+ /* XXX implement lookup_defaults ? */
+ /* XXX implement server_addr_rec ? */
+ {"timeout", T_INT, OFF(timeout), RO},
+ {"keep_alive_timeout", T_INT, OFF(keep_alive_timeout), RO},
+ {"keep_alive_max", T_INT, OFF(keep_alive_max), RO},
+ {"keep_alive", T_INT, OFF(keep_alive), RO},
+ {"send_buffer_size", T_INT, OFF(send_buffer_size), RO},
+ {"path", T_STRING, OFF(path), RO},
+ {"pathlen", T_INT, OFF(pathlen), RO},
+ {"server_uid", T_INT, OFF(server_uid), RO},
+ {"server_gid", T_INT, OFF(server_gid), RO},
+ {NULL} /* Sentinel */
+};
+
+/********************************
+ connobject
+ ********************************/
+
+typedef struct connobject {
+ PyObject_HEAD
+ conn_rec *conn;
+ PyObject *server;
+ PyObject *base_server;
+} connobject;
+
+static PyTypeObject connobjecttype;
+
+#define is_connobject(op) ((op)->ob_type == &connobjecttype)
+
+#undef OFF
+#define OFF(x) offsetof(conn_rec, x)
+static struct memberlist conn_memberlist[] = {
+ /* server in getattr */
+ /* base_server in getattr */
+ /* XXX vhost_lookup_data? */
+ {"child_num", T_INT, OFF(child_num), RO},
+ /* XXX BUFF? */
+ /* XXX struct sockaddr_in local_addr? */
+ /* XXX struct sockaddr_in remote_addr? */
+ {"remote_ip", T_STRING, OFF(remote_ip), RO},
+ {"remote_host", T_STRING, OFF(remote_host), RO},
+ {"remote_logname", T_STRING, OFF(remote_logname), RO},
+ {"user", T_STRING, OFF(user), RO},
+ {"ap_auth_type", T_STRING, OFF(ap_auth_type), RO},
+ /* XXX aborted, keepalive, keptalive, double_reverse, keepalives ? */
+ {NULL} /* Sentinel */
+};
+
+/********************************
+ requestobject
+ ********************************/
+
+typedef struct requestobject {
+ PyObject_HEAD
+ request_rec * request_rec;
+ PyObject * connection;
+ PyObject * server;
+ PyObject * next;
+ PyObject * prev;
+ PyObject * main;
+ tableobject * headers_in;
+ tableobject * headers_out;
+ tableobject * err_headers_out;
+ tableobject * subprocess_env;
+ tableobject * notes;
+ int header_sent;
+} requestobject;
+
+static PyTypeObject requestobjecttype;
+
+#define is_requestobject(op) ((op)->ob_type == &requestobjecttype)
+
+static PyObject * req_send_http_header (requestobject *self, PyObject *args);
+static PyObject * req_get_basic_auth_pw (requestobject *self, PyObject *args);
+static PyObject * req_write (requestobject *self, PyObject *args);
+static PyObject * req_read (requestobject *self, PyObject *args);
+static PyObject * req_get_config (requestobject *self, PyObject *args);
+static PyObject * req_get_options (requestobject *self, PyObject *args);
+static PyObject * req_get_dirs (requestobject *self, PyObject *args);
+static PyObject * req_add_common_vars (requestobject *self, PyObject *args);
+
+static PyMethodDef requestobjectmethods[] = {
+ {"send_http_header", (PyCFunction) req_send_http_header, METH_VARARGS},
+ {"get_basic_auth_pw", (PyCFunction) req_get_basic_auth_pw, METH_VARARGS},
+ {"write", (PyCFunction) req_write, METH_VARARGS},
+ {"read", (PyCFunction) req_read, METH_VARARGS},
+ {"get_config", (PyCFunction) req_get_config, METH_VARARGS},
+ {"get_options", (PyCFunction) req_get_options, METH_VARARGS},
+ {"get_dirs", (PyCFunction) req_get_dirs, METH_VARARGS},
+ {"add_common_vars", (PyCFunction) req_add_common_vars, METH_VARARGS},
+ { NULL, NULL } /* sentinel */
+};
+
+#undef OFF
+#define OFF(x) offsetof(request_rec, x)
+static struct memberlist request_memberlist[] = {
+ /* connection, server, next, prev, main in getattr */
+ {"the_request", T_STRING, OFF(the_request), RO},
+ {"assbackwards", T_INT, OFF(assbackwards), RO},
+ {"proxyreq", T_INT, OFF(proxyreq), RO},
+ {"header_only", T_INT, OFF(header_only), RO},
+ {"protocol", T_STRING, OFF(protocol), RO},
+ {"proto_num", T_INT, OFF(proto_num), RO},
+ {"hostname", T_STRING, OFF(hostname), RO},
+ {"request_time", T_LONG, OFF(request_time), RO},
+ {"status_line", T_STRING, OFF(status_line), RO},
+ {"status", T_INT, OFF(status) },
+ {"method", T_STRING, OFF(method), RO},
+ {"method_number", T_INT, OFF(method_number), RO},
+ {"allowed", T_INT, OFF(allowed), RO},
+ {"sent_bodyct", T_INT, OFF(sent_bodyct), RO},
+ {"bytes_sent", T_LONG, OFF(bytes_sent), RO},
+ {"mtime", T_LONG, OFF(mtime), RO},
+ {"chunked", T_INT, OFF(chunked), RO},
+ {"byterange", T_INT, OFF(byterange), RO},
+ {"boundary", T_STRING, OFF(boundary), RO},
+ {"range", T_STRING, OFF(range), RO},
+ {"clength", T_LONG, OFF(clength), RO},
+ {"remaining", T_LONG, OFF(remaining), RO},
+ {"read_length", T_LONG, OFF(read_length), RO},
+ {"read_body", T_INT, OFF(read_body), RO},
+ {"read_chunked", T_INT, OFF(read_chunked), RO},
+ {"content_type", T_STRING, OFF(content_type) },
+ {"handler", T_STRING, OFF(handler), RO},
+ {"content_encoding", T_STRING, OFF(content_encoding), RO},
+ {"content_language", T_STRING, OFF(content_language), RO},
+ /* XXX content_languages */
+ {"no_cache", T_INT, OFF(no_cache), RO},
+ {"no_local_copy", T_INT, OFF(no_local_copy), RO},
+ {"unparsed_uri", T_STRING, OFF(unparsed_uri), RO},
+ {"uri", T_STRING, OFF(uri), RO},
+ {"filename", T_STRING, OFF(filename), RO},
+ {"path_info", T_STRING, OFF(path_info), RO},
+ {"args", T_STRING, OFF(args), RO},
+ /* XXX finfo */
+ /* XXX parsed_uri */
+ /* XXX per_dir_config */
+ /* XXX request_config */
+ /* XXX htaccess */
+ {NULL} /* Sentinel */
+};
+
+/********************************
+ *** end of Python things ***
+ ********************************/
+
+/********************************
+ Apache things
+ ********************************/
+
+/* Apache module declaration */
+module MODULE_VAR_EXPORT python_module;
+extern module python_module;
+
+/* structure describing per directory configuration parameters */
+typedef struct
+{
+ int authoritative;
+ char *config_dir;
+ table *options;
+ table *directives;
+ table *dirs;
+} py_dir_config;
+
+/********************************
+ *** end of Apache things ***
+ ********************************/
+
+/******************************************************************
+ *** end of declarations ***
+ ******************************************************************/
+
+
+/******************************************************************
+ Python objects and their methods
+ ******************************************************************/
+
+/********************************
+ table object
+ ********************************/
+
+/*
+ * This is a mapping of a Python object to an Apache table.
+ *
+ * This object behaves like a dictionary. Note that the
+ * underlying table can have duplicate keys, which can never
+ * happen to a Python dictionary. But this is such a rare thing
+ * that I can't even think of a possible scenario or implications.
+ *
+ */
+
+/**
+ ** make_tableobject
+ **
+ * This routine creates a Python tableobject given an Apache
+ * table pointer.
+ *
+ */
+
+static tableobject * make_tableobject(table * t)
+{
+ tableobject *result;
+
+ result = PyMem_NEW(tableobject, 1);
+ if (! result)
+ return (tableobject *) PyErr_NoMemory();
+
+ result->table = t;
+ result->ob_type = &tableobjecttype;
+ result->pool = NULL;
+
+ _Py_NewReference(result);
+ return result;
+}
+
+
+/**
+ ** make_table
+ **
+ * This returns a new object of built-in type table.
+ *
+ * NOTE: The ap_table gets greated in its own pool, which lives
+ * throught the live of the tableobject. This is because this
+ * object may persist from hit to hit.
+ *
+ * make_table()
+ *
+ */
+
+static PyObject * make_table(PyObject *self, PyObject *args)
+{
+ tableobject *t;
+ pool *p;
+
+ p = ap_make_sub_pool(NULL);
+
+ /* two is a wild guess */
+ t = make_tableobject(ap_make_table(p, 2));
+
+ /* remember the pointer to our own pool */
+ t->pool = p;
+
+ return (PyObject *)t;
+
+}
+
+/**
+ ** table_getattr
+ **
+ * Gets table's attributes
+ */
+
+static PyObject * table_getattr(PyObject *self, char *name)
+{
+ return Py_FindMethod(tablemethods, self, name);
+}
+
+/**
+ ** tablegetitem
+ **
+ * Gets a dictionary item
+ */
+
+static PyObject * tablegetitem(tableobject *self, PyObject *key)
+{
+ const char *v;
+ char *k;
+
+ k = PyString_AsString(key);
+
+ v = ap_table_get(self->table, k);
+
+ if (! v)
+ {
+ PyErr_SetObject(PyExc_KeyError, key);
+ return NULL;
+ }
+
+ return PyString_FromString(v);
+}
+
+/**
+ ** table_has_key
+ **
+ */
+
+static PyObject * table_has_key(tableobject *self, PyObject *args)
+{
+
+ const char *val, *key;
+
+ if (! PyArg_ParseTuple(args, "s", &key))
+ return NULL;
+
+ val = ap_table_get (self->table, key);
+
+ if (val)
+ return PyInt_FromLong(1);
+ else
+ return PyInt_FromLong(0);
+}
+
+/**
+ ** tablelength
+ **
+ * Number of elements in a table. Called
+ * when you do len(table) in Python.
+ */
+
+static int tablelength(tableobject *self)
+{
+ return ap_table_elts(self->table)->nelts;
+};
+
+/**
+ ** tablesetitem
+ **
+ * insert into table dictionary-style
+ * *** NOTE ***
+ * Since the underlying ap_table_set makes a *copy* of the string,
+ * there is no need to increment the reference to the Python
+ * string passed in.
+ */
+
+static int tablesetitem(tableobject *self, PyObject *key, PyObject
+ *val)
+{
+
+ char *k;
+
+ if (key && !PyString_Check(key)) {
+ PyErr_SetString(PyExc_TypeError,
+ "table keys must be strings");
+ return -1;
+ }
+
+ k = PyString_AsString(key);
+
+ if ((val == Py_None) || (val == NULL)) {
+ ap_table_unset(self->table, k);
+ }
+ else {
+ if (val && !PyString_Check(val)) {
+ PyErr_SetString(PyExc_TypeError,
+ "table values must be strings");
+ return -1;
+ }
+ ap_table_set(self->table, k, PyString_AsString(val));
+ }
+ return 0;
+};
+
+/**
+ ** tb_setitem
+ **
+ * This is a wrapper around tablesetitem that takes
+ * char * for convenience, for internal use.
+ */
+
+static int tb_setitem(tableobject *self, const char *key, const char *val)
+{
+ PyObject *ps1, *ps2;
+
+ ps1 = PyString_FromString(key);
+ ps2 = PyString_FromString(val);
+
+ tablesetitem(self, ps1, ps2);
+
+ Py_DECREF(ps1);
+ Py_DECREF(ps2);
+
+ return 0;
+
+ /* prevent complier warning about function
+ never used */
+ tb_setitem(self, key, val);
+}
+
+/**
+ ** table_dealloc
+ **
+ * Frees table's memory
+ */
+static void table_dealloc(tableobject *self)
+{
+
+ if (self->pool)
+ ap_destroy_pool(self->pool);
+
+ free(self);
+}
+
+/**
+ ** table_repr
+ **
+ * prints table like a dictionary
+ */
+
+static PyObject * table_repr(tableobject *self)
+{
+ PyObject *s;
+ array_header *ah;
+ table_entry *elts;
+ int i;
+
+ s = PyString_FromString("{");
+
+ ah = ap_table_elts (self->table);
+ elts = (table_entry *) ah->elts;
+
+ i = ah->nelts;
+ if (i == 0)
+ PyString_ConcatAndDel(&s, PyString_FromString("}"));
+
+ while (i--)
+ if (elts[i].key)
+ {
+ PyString_ConcatAndDel(&s, PyString_FromString("'"));
+ PyString_ConcatAndDel(&s, PyString_FromString(elts[i].key));
+ PyString_ConcatAndDel(&s, PyString_FromString("': '"));
+ PyString_ConcatAndDel(&s, PyString_FromString(elts[i].val));
+ PyString_ConcatAndDel(&s, PyString_FromString("'"));
+ if (i > 0)
+ PyString_ConcatAndDel(&s, PyString_FromString(", "));
+ else
+ PyString_ConcatAndDel(&s, PyString_FromString("}"));
+ }
+
+ return s;
+}
+
+/**
+ ** table_keys
+ **
+ *
+ * Implements dictionary's keys() method.
+ */
+
+static PyObject * table_keys(tableobject *self)
+{
+
+ PyObject *v;
+ array_header *ah;
+ table_entry *elts;
+ int i, j;
+
+ ah = ap_table_elts(self->table);
+ elts = (table_entry *) ah->elts;
+
+ v = PyList_New(ah->nelts);
+
+ for (i = 0, j = 0; i < ah->nelts; i++)
+ {
+ if (elts[i].key)
+ {
+ PyObject *key = PyString_FromString(elts[i].key);
+ PyList_SetItem(v, j, key);
+ j++;
+ }
+ }
+ return v;
+}
+
+
+/**
+ ** copy_table
+ **
+ * Merge two tables into one. Matching ley values
+ * in second overlay the first.
+ */
+
+static void copy_table(table *t1, table *t2)
+{
+
+ array_header *ah;
+ table_entry *elts;
+ int i;
+
+ ah = ap_table_elts(t2);
+ elts = (table_entry *)ah->elts;
+ i = ah->nelts;
+
+ while (i--)
+ if (elts[i].key)
+ ap_table_set(t1, elts[i].key, elts[i].val);
+}
+
+/**
+ ** print_table
+ **
+ * Print apache table. Only used for debugging.
+ */
+
+static void print_table(table * t)
+{
+ array_header *ah;
+ table_entry *elts;
+ int i;
+
+ ah = ap_table_elts (t);
+ elts = (table_entry *) ah->elts;
+ i = ah->nelts;
+
+ while (i--)
+ if (elts[i].key)
+ printf(" %s: \t%s\n", elts[i].key, elts[i].val);
+}
+
+/**
+ ** print_array
+ **
+ * Print apache array (of strings). Only used for debugging.
+ */
+
+static void *print_array(array_header *ah)
+{
+ int i;
+ char **elts;
+
+ elts = (char **)ah->elts;
+
+ for (i = 0; i < ah->nelts; ++i) {
+ printf("%s ", elts[i]);
+ }
+ printf("\n");
+
+ return NULL;
+
+ /* avoid compiler warning */
+ print_array(ah);
+
+}
+
+/********************************
+ *** end of table object ***
+ ********************************/
+
+/********************************
+ server object
+ ********************************/
+
+/*
+ * This is a mapping of a Python object to an Apache server_rec.
+ *
+ */
+
+/**
+ ** make_serverobject
+ **
+ * This routine creates a Python serverobject given an Apache
+ * server_rec pointer.
+ *
+ */
+
+static serverobject * make_serverobject(server_rec *t)
+{
+ serverobject *result;
+
+ result = PyMem_NEW(serverobject, 1);
+ if (! result)
+ return (serverobject *) PyErr_NoMemory();
+
+ result->server = t;
+ result->ob_type = &serverobjecttype;
+ result->next = NULL;
+
+ _Py_NewReference(result);
+ return result;
+}
+
+/**
+ ** server_dealloc
+ **
+ *
+ */
+
+static void server_dealloc(serverobject *self)
+{
+
+ Py_XDECREF(self->next);
+ free(self);
+}
+
+/**
+ ** server_getattr
+ **
+ * Get server object attributes
+ *
+ *
+ */
+
+static PyObject * server_getattr(serverobject *self, char *name)
+{
+ if (strcmp(name, "next") == 0)
+ /* server.next serverobject is created as needed */
+ if (self->next == NULL) {
+ if (self->server->next == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ else {
+ self->next = (PyObject *)make_serverobject(self->server->next);
+ Py_INCREF(self->next);
+ return self->next;
+ }
+ }
+ else {
+ Py_INCREF(self->next);
+ return self->next;
+ }
+
+ else if (strcmp(name, "error_log") == 0)
+ return PyInt_FromLong((long)fileno(self->server->error_log));
+
+ else if (strcmp(name, "names") == 0) {
+ return tuple_from_array_header(self->server->names);
+ }
+ else if (strcmp(name, "wild_names") == 0) {
+ return tuple_from_array_header(self->server->wild_names);
+ }
+ else
+ return PyMember_Get((char *)self->server, server_memberlist, name);
+
+}
+
+/********************************
+ *** end of server object ***
+ ********************************/
+
+/********************************
+ conn object
+ ********************************/
+
+/*
+ * This is a mapping of a Python object to an Apache conn_rec.
+ *
+ */
+
+/**
+ ** make_connobject
+ **
+ * This routine creates a Python connobject given an Apache
+ * conn_rec pointer.
+ *
+ */
+
+static connobject * make_connobject(conn_rec *t)
+{
+ connobject *result;
+
+ result = PyMem_NEW(connobject, 1);
+ if (! result)
+ return (connobject *) PyErr_NoMemory();
+
+ result->conn = t;
+ result->ob_type = &connobjecttype;
+ result->server = NULL;
+ result->base_server = NULL;