Python on the Net Using CGI and WSGI

0
7245

The Internet is wonderful. You can host your application anywhere and access it anywhere! And there is no easier way to share it with friends. It isn’t hard to write your own little application. The Apache Web server does all the hard networking work. Your application will print the output and it will be sent back to the browser that made the request. The concept behind all this is the common gateway interface, or CGI, for short.

Apache in Fedora and Ubuntu is configured to handle Python CGI code out-of-the-box—only the location where we place the code is different. Let’s work with the default locations because configuring Apache is outside the scope of this article.
In Fedora, you write your code in /var/www/cgi-bin, and in Ubuntu you write it in /usr/lib/cgi-bin. The configuration files are in /etc/httpd in Fedora and in /etc/apache2 in Ubuntu. Try the following code in hello.py:

#!/usr/bin/python
# Tell the browser that it is just plain text
# n => a blank line. Indicates end of header
print 'Content-Type: text/plainn'
# The content
print 'Hello, Friends!'

The first line instructs the shell to run this script using Python; but the file hello.py should be executable, that is:

chmod +x hello.py

Now, point your browser to localhost/cgi-bin/hello.py and you should see ‘Hello, Friends!’.

Getting started with HTML

The Web pages are normally not plain text. They are HTML documents where the tags instruct the browser how to display the content. So, modify hello.py to be an HTML document.

#!/usr/bin/python
# Tell the browser that it is an html document
print 'Content-Type: text/htmln'
# Print the html document using triple quote for convenience.
print "'<html>
   <header></header>
   <body>
      <h3>Hello, Friends</h3>
      <p>Welcome - this is a paragraph.</p>
   </body>
</html>'"

The structure of the HTML document is <tag>content</tag>, where the content may include other tags. You are concerned with the body part. You now have two statements being displayed—one as a header and the other as a paragraph. You can try different types of headers—h1 through h4 and see the difference.

Displaying a form

You can now build a small application of classifieds for your friends. You will have three fields, a description and a contact e-mail ID. Write the following in cgi-bin/form.py:

#!/usr/bin/python
# Define the form
content_header =  "Content-Type: text/htmln"
html_page = """
<html>
   <head> </head>
   <body>
      <h1>Entry Form</h1>
      <form method=”post” action=”form.py”>
      <p><label>Title: </label><input type=”text” name=”title” value=”%(title)s”/ ></p >
      <p>Description: </p>
      <textarea name = “desc” rows=4 cols=60 >%(desc)s</textarea >
      <p><label>Email-id: <input type=”text” name=”email” value=”%(email)s” /></label></p >
      <button type=”submit”>Submit</button>
      <h4>%(message)s<h4>
      </form>
   </body></html>
"""
# Python Code
message = email = title = desc = ""
# present the form
print content_header
print html_page % {"title":title, "email":email, "desc":desc, "message":message}

The string html_page is almost a static HTML page but with a few variables. The value for these variables is substituted in the print statement. Point your browser to http://localhost/cgi-bin/form.py.

You can enter the data, but not much happens. When you display the form initially, there will be no data in the form. The same program is being called when the form is ‘actioned’. So, your code has to be able to differentiate between the two states. You will need to make use of the cgi module and add some Python code.

Adding logic

Add the following code after the Python code above:

import cgi
def process_form(email, title, desc):
   return "Your entry has been processed"
form = cgi.FieldStorage()
email = form.getvalue("email","")
title = form.getvalue("title","")
desc = form.getvalue("desc","")
if not email or not title:
   message = "Please enter Title and Email"
else:
   message = process_form(email, title, desc)
   email = title = desc = ""
# present the form
print content_header
print html_page % {"title":title, "email":email, "desc":desc, "message":message}

The cgi module does not differentiate between get and post actions. The getvalue method picks up the value from the form. The second parameter passed to this method assigns a default value in case the value is not available. The method process_form currently does nothing. It could contain the code for storing the data in a database, or whatever else you may need.

Adding some style

However, the impression may be that pages generated through Python are plain and ugly. We can make them even uglier, but colourful! The look and feel will be controlled by a style sheet exactly like the usual HTML pages. So, replace the <head></head> line above by:

<head>
   <style type=”text/css”>
      p {margin-left: 50px}
      body {background-color: tan}
      input {background-color: yellow}
      button {background-color: orange}
   </style>
</head>

The page now will be more colourful. Incidentally, the styles can usually be read from a CSS file.

WSGI

Now that you know how to write a simple application for the Web, you may want to write an application server in Python. Chances are you would use one like Django, Turbo-Gears, Pylons, etc. But you can use the Web server gateway interface as well, which is supported by various application servers. You can find out more about it at www.wsgi.org or just by searching for ‘wsgi tutorials’, e.g., webpython.codepoint.net/wsgi_tutorial. Try the following simple code in a file test_app.py:

#!/usr/bin/python
from wsgiref.simple_server import make_server
def application(environ, start_response):
   http://webpython.codepoint.net/wsgi_tutorialkeys_of_interest = ['PATH_INFO', 'QUERY_STRING','REQUEST_METHOD']
   env_vars = (key + ':' + value for key, value in environ.items()
      if key in keys_of_interest)
   msg = 'n'.join(env_vars)
   status = '200 OK'
   response_headers = [(‘Content-Type’, ‘text/plain’)]
   start_response(status, response_headers)
   return ["Environment Variables of Interestnn", msg]
# Start the server and handle just one request
httpd = make_server('localhost', 8080, application)
httpd.handle_request()

To create a Web server, you will need to have a method called application (not mandatory, but preferable). The method requires two parameters, environ and start_response, and returns a list of strings to be displayed. The first parameter is a dictionary containing various environment variables. Your program displays a few of them related to the Web page. The second parameter is a method you call to, well, start the response for the request.

You can convert your code into a Web server using the simple reference server included in Python. Your server would normally keep handling requests and instead of calling handle_request, you would call serve_forever.

You may notice the use of () instead of [] to define env_vars, using list comprehensions. The use of square brackets creates a list while the use of parentheses creates a generator for the list. Using a generator saves the creation of a temporary list, which can be especially beneficial when dealing with potentially large lists.

You would run the application from the command prompt, python test_app.py, and by pointing the browser to localhost:8080/anything?anyvar=anyvalue. The result would be as follows:

Environment Variables of Interest

REQUEST_METHOD:GET
PATH_INFO:/anything
QUERY_STRING:anyvar=anyval

You can change the CGI application you wrote to work with WSGI instead. Your file, application.py, should then include the following:

#!/usr/bin/python
from wsgiref.simple_server import make_server
from cgi import parse_qs
def application(environ, start_response):
    path_info = environ['PATH_INFO']
    if path_info.endswith('form.py'):
        # for post method, params in a file object
        req_size = int(environ['CONTENT_LENGTH'])
        params = parse_qs(
            environ['wsgi.input'].read(req_size))
        response = process_form(params)
    else:
        response = display_form()
    status = '200 OK'
    response_headers = [('Content-Type', 'text/html')]
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [response]
httpd = make_server('localhost', 8080, application)
httpd.serve_forever()

The core application code is not very different from the earlier one. The form responds with a POST method and calls form.py because the form had called it in the CGI version; and you too are using the same HTML page and you call process_form. For all other URIs, you display a blank form. A URI does not correspond to a file or a method name any more. You can use path_info and the parameters returned to perform the actions required for the Web application.

The parameters returned by the GET method are in the QUERY_STRING environment variable. However, the POST method may return large amounts of data; so it is available in a file object referred by wsgi.input. The method parse_qs parses a string and converts the parameters into a dictionary. Following is a minimal example of display_form and process_form that you may write:

html_page = defined as above
def display_form(title="", desc="", email="", msg=""):
    return html_page % {'title':title, 'desc':desc,
            'email':email, 'message':msg}
# the logic
def process_form(params):
    email = params.get("email",[""])[0]
    title = params.get("title",[""])[0]
    desc = params.get("desc",[""])[0]
    if not email or not title:
        return display_form(title=title, desc=desc, email=email,
            msg="Please enter Title & Email")
    else:
        # the code to save or process the data would appear here
        return display_form(msg="Your message has been processed")

The data returned by POST is a list of values because a form may include multiple rows with the same field name. So, you pick the first value from the list.

The Web server frameworks make it much easier to handle URLs, manage complex Web pages and associate form fields with database columns. So, you would normally use one of the frameworks. Managing classifieds would be a pretty simple task for any of the frameworks mentioned.

It is hoped that by the wider adoption of WSGI, it will be possible to write components that may be ‘callable’ from various frameworks or Web applications. Here’s an example of plugging Trac within a Pylons framework: wiki.pylonshq.com/display/pylonscookbook/A+Pylons+Controller+with+Trac+as+WSGI+Callable.

So, you can easily extend your classifieds application with a wiki, using moin-moin and a fault tracking application using Trac!

LEAVE A REPLY

Please enter your comment!
Please enter your name here