VAMPIRE

Processing Form Parameters

Author: Graham Dumpleton
Contact: grahamd@dscpl.com.au
Updated:29/04/2005

This article details the support Vampire provides to assist in processing HTML form parameters.

Content Handler Arguments

A traditional content handler in mod_python must accept a single argument. The name of the argument is not important but by convention is usually named "req". The argument will be passed the mod_python request object. It is by using the request object that a handler is able to deliver a response back to the client which generated the original request. If a request provides form parameters, the handler would need to use the "FieldStorage" class supplied with mod_python to process the request object and obtain the form parameter values.

from mod_python import apache
from mod_python import util

def handler(req):
  form = util.FieldStorage(req,keep_blank_values=1)
  user = form.get("user",None)
  req.content_type = "text/plain"
  req.send_http_header()
  req.write("user=%s\n"%user)
  return apache.OK

In Vampire, the manner in which arguments are defined and used in content handlers is not the same as traditional content handlers. Instead, it is more akin to how arguments are used in published functions when using mod_python.publisher. That is, the argument which accepts the request object must be called "req". Further, where a request is providing form parameters, the form parameters will be automatically decoded and passed through an argument of the same name.

from mod_python import apache
from mod_python import util

def handler(req,user=None):
  req.content_type = "text/plain"
  req.send_http_header()
  req.write("user=%s\n"%user)
  return apache.OK

Where there can exist multiple form parameters, the handler would define an argument corresponding to each. The order of the arguments is not important. It is not even necessary that the "req" argument be the first argument, although it is recommended. If it is necessary to catch all form parameters, including any which weren't expected, a keyword argument list argument can be specified.

from mod_python import apache
from mod_python import util

def handler(req,user=None,passwd=None,**params):
  req.content_type = "text/plain"
  req.send_http_header()
  req.write("user=%s\n"%user)
  req.write("passwd=%s\n"%passwd)
  for name in params.keys():
    req.write("%s=%s\n"%(name,params[name]))
  return apache.OK

Although the treatment of arguments and automatic processing of form parameters is the same as if using mod_python.publisher, the code within the body of the content handler still needs to be written as for a traditional content handler.

The only difference is that where with a traditional content handler "apache.OK" must be returned to indicate the handler was successful, in Vampire this is optional and returning of "None" or no explicit return value has the same effect. In a traditional content handler, if you allow "None" to be returned implicitly or explicitly, mod_python will raise a HTTP internal server error as a response.

Structured Form Parameters

Traditionally, the value of any form parameter is usually a single string value. The one exception to this is where the same form parameter name may be repeated more than once in the input. In this case, the value will be a list, where each successive element in the list is the value to which the form parameter was set each time it appeared in the input.

/form?name=value --> name="value"
/form?name=value1&name=value2 --> name=["value1","value2"]

Vampire expands on this by interpreting the names of form parameters in a special way. If form parameter names following certain conventions, it is possible to have related form parameters be aggregated together automatically into lists and dictionaries or even a combination of the two. The special characters which are used to denote structure are '-' and '.'. These are used in indicating that form parameters should be aggregated as lists and dictionaries respectively.

To indicate that form parameters should be aggregated together as a list, they should each be named with the same prefix and then each should in turn be suffixed by '-' and an integer indicating its relative position in the list in respect to other values.

/form?name-1=value1&name-2=value2 --> name=["value1","value2"]
/form?name-1=value1&name-3=value3 --> name=["value1","value3"]

The result here is not the same as a repeated form parameter, because when there is only one such named form parameter, it will still yield a list. A repeating form parameter of the same name, even in this form, will still result in a list being created just for that element in the list.

/form?name-1=value1 --> name=["value1"]
/form?name-1=value1&name-1=value2 --> name=[["value1","value2"]]

To indicate that form parameters should be aggregated together in the form of a dictionary, they should each be named with the same prefix and then each should in turn be suffixed by '.' and the label for the element within the dictionary.

/form?name.key1=value1&name.key2=value2 --> name={'key2':'value2','key1':'value1'}

A repeating form parameter of the same name, will again result in a list being created just for that element in the dictionary.

/form?name.key1=value1 --> name={'key1':'value1'}
/form?name.key1=value1&name.key1=value2 --> name={'key1':['value1','value2']}

The two different naming conventions for the construction of lists and dictionaries may be combined, making it possible to create more or less arbitrary structures consisting of dictionaries, lists and strings.

/form?name.key-1=value1 --> name={'key':['value1']}
/form?name-1.key=value1 --> name=[{'key': 'value1'}]

Because Vampire performs this special interpretation of form parameter names, if porting code from a different system, it may be necessary to modify the names of form parameters where they are interpreted in unexpected ways. Alternatively, the special interpretation of form parameter names can be disabled. This can be done by setting the "VampireStructuredForms" option to "Off".

PythonOption VampireStructuredForms Off

Although "vampire::publisher" is intended as a drop in replacement for mod_python.publisher, it too by default supports this mechanism for structured data in form values. As such, all the above applies to "vampire::publisher", including the need to turn the feature off if so desired.

Lazy Request Processing

When one is using mod_python.publisher, a request will always be processed for form parameters. This causes a few problems and limits what you can use mod_python.publisher for. Specifically, a published function can not be used to process POST requests where the incoming content does not describe a HTML form. You could not for example implement an XML-RPC service using mod_python.publisher.

In addition to this limitation, if a published function performs an internal redirect and form parameters are supplied in a POST request, the form parameters will not be available to the target of the redirection. This is because the request content will have been consumed by the initial processing of the request to obtain the form parameters. It isn't possible for the target of the redirection to read the content of the request a second time.

A similar problem to that arising from redirection is where a templating system is used where the templating system expects to be able to do its own processing of the form parameters. This is the case with mod_python.psp and as such, you cannot from a published function create PSP template objects explicitly and still expect to be able to use POST requests for providing form parameters.

To avoid all these problems, when using Vampire content handlers a request object will only be processed for form parameters when it is determined there is actually a need to do so. That is, no processing will be done if the content handler doesn't define any arguments through which form parameters could be passed. In the case of a POST request, processing of form parameters will also not be performed if the type of content provided doesn't actually equate to a HTML form.

This means that provided that the only argument which is defined is that for the "req" object, you can still implement handlers where the handler takes responsibility for consuming and processing the actual content contained in the request. It would also be possible to define a handler which triggered an internal redirection to another handler which did then make use of the ability to specify form parameters as arguments to the handler.

Note that "vampire::publisher" performs the same as mod_python.publisher and will always process a request object for form parameters. Thus it has the same limitation as mod_python.publisher in respect of internal redirects and use of third party templating systems. If you still wish to model handlers as published functions, but need to avoid these limitations, you will need to convert your code so as to use the "vampire.Publisher()" handler object instead. This will work because like content handlers, "vampire.Publisher()" will only trigger processing of form parameters if it is actually needed.

Accessing The Form Object

When the request object is processed for form parameters, the resulting instance of the "util.FieldStorage" class is stored in the request object as the "form" attribute. This is compatible with where mod_python.publisher also caches the form object. If however it were the case that a content handler didn't define any arguments so as to cause processing of form parameters in the first place, then the form object will not have been created and the "form" attribute will therefore not exist.

If you wish to access the full set of input form parameters from within a handler, or any function called from the handler, provided you have access to the request object, you can use:

params = vampire.processForm(req)

The dictionary returned will be the same as what would have been passed to the content handler through the keyword argument list parameter, if the only arguments it specified were that for the request object and the keyword argument list parameter.

If processing of form parameters hadn't yet been performed at that point, it will be performed at that time and will have the side effect of setting the "form" attribute of the request object to the form object.

If prior to the call you had created your own instance of the "util.FieldStorage", provided that you had stored it in the request object as the "form" attribute, your instance of the form object will be used as the basis of calculating the set of form parameters and a new one will not be created.

The full set of form parameters will be cached within the request object in a hidden field, thus calling "vampire.processForm()" multiple times will still be efficient as it will return the cached set of form parameters.

Delayed Form Interpretation

If using an object as a handler, as a way of reusing code through derivation, it may not be convenient to have to specify arguments to the "__call__()" method to capture form parameters. One means of accessing the form parameters at a later point is using "vampire.processForm()". Using this approach means explicitly having to access each argument from the dictionary of form parameters as necessary.

An alternative shortcut is to define a further instance method whose job it is to use the form parameters, defining any named arguments to receive them. At the point that the form parameters need to be processed, the "vampire.executeHandler()" function is used to trigger processing of the request for the form parameters and the execution of the instance method.

class Servlet:

  def __init__(self,req):
    self.req = req

  def __call__(self):
    vampire.executeHandler(self.req,self.processForm)
    return self.renderPage()

  def processForm(self,lhs,rhs):
    self.__result = float(lhs) * float(rhs)

  def renderPage(self):
    self.req.content_type = "text/plain"
    self.req.send_http_header()
    self.req.write(str(self.__result))

handler = vampire.Instance(Servlet)

The "vampire.executeHandler()" function can actually be applied to any callable object. Where an argument is the same name as a form parameter, the form parameter will be passed via that argument. If an argument is called "req", the original request object will be passed via that argument.

Validating Form Parameters

Although Vampire provides the means for processing a request and turning them into arbitrary structures, no means of performing data validation is provided by Vampire. You will need to provide your own mechanisms for data validation or use a third party module such as FunFormKit.

Form validation generally entails checking if required form parameters are provided and whether the values adhere to some particular type and/or format. Such values may optionally be converted from a string into integers, booleans or some special purpose custom data object.

Vampire can't directly help with content validation or type conversion, but if using "vampire.executeHandler()", it does provide a way to determine if the appropriate form parameters were actually provided before using this function. If such a check isn't performed and "vampire.executeHandler()" is called when not all required form parameters were provided, the client will receive a HTTP internal server error as a response.

The "vampire.processForm()" function already provides access to the full set of form parameters that have been provided by a request. What is now required is a means of determining the list of form parameters that a function designed to process them is expecting.

The Python "inspect" module provides the "getargspec()" function for determining such information, however, you need to know when using it if you are applying it to a function or an instance method, and if the latter, skip the first actual argument the function defines. If the target is an instance of a class which is callable, you need to actually apply the "inspect.getargspec()" function to the "__call__()" method as well as skip the first actual argument.

To streamline this task, the "vampire.handlerArguments()" function is provided. This works in a similar way to "inspect.getargspec()", but will automatically determine the actual function which should be queried if a callable object and separate out the leading function argument when an instance method.

The value returned by "vampire.handlerArguments()" will be a tuple consisting of "(this, args, varargs, varkw, defaults)". Where 'this' is the name of the first argument when handler is an instance method or None, 'args' is a list of the argument names, 'varargs' and 'varkw' are the names of the '*' and '**' arguments or None, and 'defaults' is an n-tuple of the default values of the last n arguments.

All this information could then be used as follows, as the part of an overall form validation mechanism to determine the list of missing form parameters:

def missingArguments(req,handler):

  # Derive list of supplied form parameters.

  supplied = vampire.processForm(req)

  # Derive list of expected form parameters.

  this,args,varargs,varkw,defaults = vampire.handlerArguments(handler)

  # Check if handler accepts all form parameters.

  if varkw:
    return []

  # Check that other form parameters are supplied.
  # Need to skip the "req" argument if it exists.

  missing = []

  mandatory = len(args)

  if defaults:
    mandatory = mandatory - len(defaults)

  i = 0
  for name in args:
    if name != "req":
      if not name in supplied and i < mandatory:
        missing.append(name)
    i = i + 1

  return missing

Instead of returning a generic HTTP error response, it would now be possible to return a customised HTML page which provided a list of the missing form parameters.