-1 (On including in 3.3)

We need to have some release discipline. The beta cycle for 3.2 was something like 8 months long and I don't want to see that happen again. One of the reasons for that long cycle was the feeling that "it might be a long time before we do another release, so let's make this change now", which ultimately *caused* the delay.

There are a lot of really great things in 3.3 (new importer) and I think we should get it out as soon as possible. I'd also like to see a 3.4 release fairly soon (4-6 months?) after a though audit for python 2.5 64-bit support. If the new handler is accepted it may not have to wait too long. Also, if it's pure python it would be pretty painless for people to backport it if they want to use it in the mean time.

Maybe we should adopt some sort of calendar release policy. If we aim for a minor point release every 6 months then people will always know that they won't have to wait too long for the latest and greatest features to appear. This would alleviate the urge to stuff too much into any one release. I'm not suggesting that we be a slave to the calendar - just use it as a guideline.

Jim

Graham Dumpleton wrote:
I know that we are very close to getting mod_python 3.3 out the door,
and I know that it is me holding it up purely through the documentation
not yet being updated to at least give some basic details on new bits
in the rewritten module importer, but even at this late stage I have been
thinking if we aren't with 3.3 missing a great opportunity to add a new
basic mod_python dispatch handler. After all, it may well be some time
before we get around to releasing the next version.

On that basis, I have quickly thrown together a new dispatch handler
which I think could be included with mod_python. After seeing so many
people going off and writing their own dispatchers with varied success I
could see that including this would save a lot of mucking around for
people.

I have attached the actual code for the handler module, but I'll explain
a few things about it as well.

First off, intended that this would exist as 'mod_python.dispatcher'. In
the simplest case, if you wanted to put all requests which fall within a
directory through it, you would use:

  SetHandler mod_python
  PythonHandler mod_python.dispatcher

The basic premise of the dispatcher is then that it will attempt to match
any request against a resource against a handler found in a .py file
corresponding to that specific resource.

Thus, if a request is made against '/index', the dispatcher would look for
a .py file called 'index.py' in that directory and execute the 'handler()'
function within it. If the request was made against '/index.html', the
dispatcher would instead try and execute the 'handler_html()' function.

  from mod_python import apache

  def handler_html(req):
    req.content_type = 'text/html'
    req.write('<html><body><p>index</p></body></html>')
    return apache.OK

So, an important aspect of the dispatcher is that it doesn't just push all
requests for a resource through the one handler function. Instead, it will
call different handlers within the one resource code file for the different
extensions.

The benefit of this is that it becomes really easy to control what extension you want a resource accessed under. Further, it is easy to provide different
views of a resource where the format of the result is different based on
the extension.

  def handler_csv(req):
    ...

  def handler_xml(req):
    ...

Because of the way that Apache matches files, this will all work within any
subdirectories as well. Thus if the request is against '/subdir/index.html',
the dispatcher will try and execute 'handler_html()' in 'subdir/index.py'.

If the directory is shared with static files which one wants to be served
up as is, one can use the Files directive to indicate which files should be
still served up as static files and not processed by mod_python.

  SetHandler mod_python
  PythonHandler mod_python.dispatcher

  <Files *.jpg>
  SetHandler None
  </Files>

Instead of starting with SetHandler, one could instead start with AddHandler.

  AddHandler mod_python .html
  PythonHandler mod_python.dispatcher

  # Must block access to static .py files.

  <Files *.py>
  deny from all
  </Files>

In this way, only things with a .html extension will be handled by the dispatcher. If there are individual .html files that you want served as static files or in some other way, can again use Files directive to mark specifically how they should
be dealt with.

  <Files index.html>
  SetHandler default-handler
  </Files>

For the next tricky bit of this dispatcher, it can be used for other phases besides that for just the response handler phase. For example, if I want to be able to
specify fixup handlers for individual resources, then I could use:

  SetHandler mod_python
  PythonFixupHandler mod_python.dispatcher
  PythonHandler mod_python.dispatcher

This would then allow me to say something like:

  from mod_python import apache

  def fixuphandler(req):
    req.used_path_info = apache.AP_REQ_ACCEPT_PATH_INFO
    return apache.OK

  def handler(req):
    ...

What is happening here is that the default behaviour of the dispatcher is to
prohibit additional path information to be provided for a request against
a resource. This could be enabled on a per resource basis from the Apache
configuration file:

  <Files resource>
  AcceptPathInfo On
  </Files>

or, as shown, a fixuphandler() function specific to that resource could be
provided within the same resource code file and set req.used_path_info
as appropriate instead.

Thus, any of the prior phases could be enabled on a case by case basis
if they needed to be overridden by specific resources at some point.

Alternatively, one could setup the handler so it checks resource code files
for the existence of a handler for any of the prior phases by using:

  SetHandler mod_python
  PythonHandlerModule mod_python.dispatcher

That way, with one directive, would allow other phases such as the
headerparserhandler, accesshandler phases etc, to also be overridden
on a per resource basis.

Although the dispatcher is targeted at allowing handlers to be specified on
a per resource basis, it is still possible to mixin other handlers which apply
across all requests.

For example:

  PythonHeaderParserHandler moddir::directoryindex

  SetHandler mod_python
  PythonHandlerModule mod_python.dispatcher

  from mod_python import apache

  # moddir.py

  def directoryindex(req):
    if req.content_type == 'httpd/unix-directory:
      req.filename = req.filename + 'index.html'
      req.finfo = apache.stat(req.filename)
      req.content_type = 'text/html'
    return apache.OK

Thus one can emulate the DirectoryIndex directive, which otherwise would
not work with this dispatcher and causes mod_python other problems anyway
because of bugs with internal fast redirects in Apache.

One final example with this. One need not even have an actual response handler defined for a resource, but still define a fixup handler. For example, if one wanted to use SSI for a specific resource which existed as a static .html file. One could
do this with:

  def fixuphandler_html(req):
    req.handler = 'default-handler'
    req.add_output_filter('INCLUDES')
    req.ssi_globals = { ... }
    return apache.OK

All up, although the actual code for this new dispatch handler is quite little, it allows for a great deal of flexibility and would probably give users more to work
with than what most people come up with for a simple dispatcher.

For those who have been trying mod_python 3.3, would be great to see you give
this new handler a go and see what you think. Any comments most welcome,
especially whether or not it is something people feel would be worthwhile to include in mod_python 3.3. Note that for people not using mod_python 3.3, this
will not work with older versions.

BTW, to test this, just throw it a directory in your document tree and setup the
.htaccess file to contain:

  SetHandler mod_python
  PythonHandlerModule _handlers
  PythonDebug On

  <Files *.py>
  deny from all
  </Files>

Just don't make requests against '/_handlers' as it will loop back on itself given
that it isn't meant to be in the document tree itself.

Have fun.

Graham



------------------------------------------------------------------------

from mod_python import apache

import os, posixpath

def _phasehandler(req):

    # Ignore requests made against the directory.

    if req.filename[-1] == '/':
        return apache.DECLINED

    # Requests where there is no code file are ignored.

    stub, extn = posixpath.splitext(req.filename)
    directory = os.path.dirname(req.filename)
    target = stub + '.py'

    if not os.path.exists(target):
        return apache.DECLINED

    # Import the Python code file.

    module = apache.import_module(target)

    # Ensure that there is a handler within the code file for
    # the appropriate phase and resource extension.

    name = req.phase[6:].lower()

    if extn:
        name = "%s_%s" % (name, extn[1:])

    if not hasattr(module, name):
        return apache.DECLINED

    # Get a reference to the handler and invoke it.

    return getattr(module, name)(req)


postreadrequesthandler = _phasehandler
headerparserhandler = _phasehandler
accesshandler = _phasehandler
authenhandler = _phasehandler
authzhandler = _phasehandler
typehandler = _phasehandler
fixuphandler = _phasehandler


def handler(req):

    # Ensure that no additional path information has been
    # provided if it isn't desired. The default behaviour is to
    # reject such additional path information. Whether
    # additional path information is accepted can be specified
    # using the AcceptPathInfo directive or by setting the
    # req.used_path_info attribute appropriately in an earlier
    # phase.

    if req.used_path_info != apache.AP_REQ_ACCEPT_PATH_INFO:
        if req.path_info:
            return apache.HTTP_NOT_FOUND

    # Requests against the directory itself are forbidden.

    if req.filename[-1] == '/':
        return apache.HTTP_FORBIDDEN

    # Requests where there is no code file are rejected.

    stub, extn = posixpath.splitext(req.filename)
    directory = os.path.dirname(req.filename)
    target = stub + '.py'

    if not os.path.exists(target):
        return apache.HTTP_NOT_FOUND

    # Import the Python code file.

    module = apache.import_module(target)

    # Ensure that there is a handler within the code file for
    # the response handler phase.

    if extn:
        name = "handler_%s" % extn[1:]
    else:
        name = 'handler'

    if not hasattr(module, name):
        return apache.HTTP_NOT_FOUND

    # Get a reference to the handler and invoke it.

    return getattr(module, name)(req)


------------------------------------------------------------------------





Reply via email to