Author: | Dave Kuhlman |
---|---|
Address: | dkuhlman@rexx.com http://www.rexx.com/~dkuhlman |
Revision: | 1.0a |
Date: | Feb. 6, 2004 |
Copyright: | Copyright (c) 2004 Dave Kuhlman. This documentation is covered by The MIT License: http://www.opensource.org/licenses/mit-license. |
Abstract
This document provides and explains a skeleton for Quixote applications that generate configuration/text files from user parameters and a template file.
This small Quixote application is intended as an example or skeleton for applications that (1) request a few parameters from a user and then (2) generate a configuration (or other text) file from a template file.
The application source files, including start-up scripts for both Apache/SCGI and Medusa servers is here: http://www.rexx.com/~dkuhlman/quixote_iptables_firewall_config.zip.
The application actually generates a configuration file for Arno's IPTables-firewall. You can learn more about that at: http://rocky.molphys.leidenuniv.nl/.
You will find additional help for installing and running a Quixote application from a skeleton in the following document: A Quixote Application Skeleton.
Look at this example as a special case of more general class of applications, which is any application that needs to get some information from the client/user, then generate content (from a template) to be returned to the user.
The organization of the files in the skeleton is as follows:
Follow these steps:
Unroll the skeleton files. Something like the following should do it:
unzip quixote_iptables_firewall_config.zip
Rename the directory containing the sample files. In what follows, I'm going to explain things as if you had renamed it to "myapp", which, on a UNIX/Linux system, you would do as follows:
mv skeleton myapp
Add the directory in which you unrolled the sample files to your PYTHONPATH. For example, if, when you unrolled the sample files, you created /aaaa/bbbb/skeleton, then you will need something like the following (or the equivalent):
export PYTHONPATH=/aaaa/bbbb:$PYTHONPATH
Modify the start-up scripts in the scripts sub-directory -- You will need to change the lines containing "iptables_firewall_config" to reflect your renaming of the directory containing the application.
Test your application -- You can run either of the following in the scripts sub-directory:
python server-scgi.py -p 3001
where:
Or:
python server-medusa.py -p 8081
Here is a sequence of steps that you might follow.
You will need to replace the template file scripts/iptables-firewall.conf.template. Also, modify the line in ui/configui/ptl that refers to it.
If you look in scripts/iptables-firewall.conf.template, you will see expressions of the form "%(xxx)s" and %(yyy)d, where "xxx" and "yyy" are keys in a dictionary that you provide. So, in general, you provide a dictionary containing the keys in your template file, then do something like the following:
templateFile = file(str('mytemplatefile'), str('r')) template = templateFile.read() templateFile.close() configuration = template % configDict
In ui/configui.ptl, you will want to modify the function that creates the form used to collect values from the end user. Note the following:
You will need to modify method create_config_form so that it creates the form you need. You should add and subtract widgets, rename them, etc.
In this step we assume that the form (or request, whatever) holds the values from the user. This is currently done in method do_finish, which does the following:
Extract the values from the form/request and put them in a dictionary.
Clean-up or regularize the values. For example, where None is returned because the value was not entered, we might want to replace that with an empty string.
Read in the template file.
Fill the values from the dictionary into the template. For example:
content = template % configDict
Return the content to the user.
So, the most significant changes that you will need to make is to change the variables that are extracted from the form. Here is the example from the distribution:
class ConfigUI: o o o def do_finish [html] (self, request): self.request = request self.configForm = self.create_config_form() # Create the dictionary of configuration values. self.configDict = {} self.update('ext_if_dhcp_ip') # [1] self.update('nat') self.update('ext_if') self.update('modem_if') self.update('modem_if_ip') self.update('modem_ip') self.update('int_if') self.update('internal_net') # Clean up the configuration dictionary. For example, some missing # values should be blank strings instead of None. if not self.configDict['modem_if_ip']: # [2] self.configDict['modem_if_ip'] = '' if not self.configDict['modem_ip']: self.configDict['modem_ip'] = '' contentList = ['[config]'] self.add_content(self.configDict, contentList) content = '\n'.join(contentList) # Read the config template file. # [3] templateFile = file(str('iptables-firewall.conf.template'), str('r')) template = templateFile.read() templateFile.close() # Plug the configuration values into the template. configuration = template % self.configDict # Did the user ask for debug/configuration information. if self.configForm['debug']: header('Finished') '<pre>\n' content '</pre>\n' '<hr/>\n' '<pre>\n' configuration '</pre>\n' footer() else: '<html><head><title>Configuration file</title></head><body><pre>' configuration # [4] '</pre></body></html>'
Explanation:
Python makes access to a relational database easy to do. And, with Quixote, it is easy to reuse database connections for satisfying more than one request. You can see the following document for suggestions on how to access a relational database from a Quixote request handler:
I'd like to avoid the use of sessions.
The question here is: How can we make a Web application that continues over several requests and responses (i.e. over several Web pages) more REST-full than it would be with the use of state and sessions on the server?
There are a variety of answers to this question. I'll give several of them.
The technique we'll use here is to pass state and session information in a hidden field in the form. One way to do this is to write it in ".ini" format. You can use the ConfigParser module in the Python standard library to parse this content.
And, something like the following could (1) pass the state information in a response and (2) retrieve the state information from the next request:
def step_2 [html] (self, request): myform = form2.Form() myhidden = myform.add(form2.HiddenWidget, 'hidden_state') # [1] o o o state_info = '[general]\n' # [2] 'option1=value1\n' 'option2=value2\n' 'option3=value3\n' myform['hidden_state'] = state_info # [3] o o o header() myform.render() # [4] footer() def step_3 [html] (self, request): myform = form2.Form() myhidden = myform.add(form2.HiddenWidget, 'hidden_state') o o o state_info = myform['hidden_state'] # [5] state_file = StringIO.StringIO() state_file.write(state_info) # [6] parser = configparser.ConfigParser() parser.readfp(state_file) # [7] option2 = parser.get('general', 'option2') # [8]
Explanation:
It is easy to implement a subclass of ConfigParser that gets its input from a string. Here is one:
class StringConfigParser(ConfigParser.ConfigParser): def read_string(self, instring): configFile = StringIO.StringIO() configFile.write(instring) configFile.seek(0) self.readfp(configFile) configFile.close()
One solution is to format the state information as XML, pack it into the body of the response, and to require that the client return that XML content in the body of the next request.
There are several additional things that you will want to know in doing this:
When generating the response:
Render the XML content as you would HTML content. A PTL function or method will return this content automatically.
Set the Content-type header as follows:
response = request.response response.set_content_type('text/xml')
When processing the (next) request):
Read the XML content from the body of the request as follows:
from xml.dom import minidom o o o def sample [html] (self, request): content = request.stdin.read() doc = minidom.parseString(content) o o o
Also, you may want to consider using generateDS.py to build parsers for your XML content.
http://www.mems-exchange.org/software/quixote/: The Quixote support Web site.
Support for Quixote Etc: A variety of helpful documents on Quixote.
A Quixote Application Skeleton: Example and skeleton files for building a Quixote application.
Arno's IPTables-firewall: Arno's IPTABLES firewall script