Skip to content

Web python

Very interesting: : this page

Traditionally there are three main ways of using python in web pages : cgi, mod_python and wsgi. But there is also the possibility of websockets or even creating a raw socket server to get data, but granted that is not traditional web scripting.

All have their own pro's and con's but basically it is regarded that you should use the newest, which is wsgi. However as i see it that means completely generating the web content like cgi and i actually don't know if that is the best way to go.

cgi

The good 'ole way. The web page calls a python program in the designated /cgi directory and creates the web page, or .. provides some information through an ajax call of course.

cgi advantages

Mainly, it is supported by almost all providers, and it's setup is probably the simplest.

cgi disadvantages

The python interpreter must be started up on every call, so it is inherently slow. And it is already being called obsolete though i doubt if this will go away.

If i can only gain a microsecond i will, so cgi is completely off the table. So i choose either mod_python or wsgi, and sadly there will be no installation and usage section here...

mod_python

mod_python has the possibility of creating python server pages. See the separate chapter below. This is the closest you will come to the php-way of interleaving html and code. It has a similar tag-wise way of including python code within <% and %>.

mod python advantages

It is faster than cgi because the interpreter is part of apache, and already started when you call your functions. Also it can be better combined with html as said before.

mod python disadvantages

Well ?!, can't find any other than that apache has discontinued mod_python, all other posts just say 'use mod_wsgi' without any explanation. So for myself i should say that mod_python is somewhat harder to install and use.

I will still explain the mod_python way here, because no one has completely convinced me of not using it yet.

install mod_python

Quick guide to success :

mod python
apt-get install libapache2-mod-python

Edit /etc/apache2/sites-enabled/000-default, and add these lines :

/etc/apache2/sites-enabled/000-default
1
2
3
4
<Location /mpinfo>
            SetHandler mod_python
            PythonHandler mod_python.testhandler
</Location>

Restart apache and browse to visit

You should get some info about mod_python. mod_python.testhandler is just an internal script so you won't have to provide some file containing phpinfo() or something.

usage

It's all about apache handlers on mod_python. You saw one earlier (testhandler), and another one often used is the publisher handler. Pick an apache directory where you want your scripts to run and configure it in apache like this :

PythonDebug
1
2
3
4
5
<Directory /usr/local/apache2/htdocs/PublisherExample>
    AddHandler mod_python .py
    PythonDebug On
    PythonHandler mod_python.publisher
</Directory>

And of course restart again. Note the PythonDebug On line, it is very useful during development as it puts error messages to html page instead of the apache logs.

Now create a script to print the time, and a message called ModPythonExample.py

web output
1
2
3
4
5
6
7
8
from time import strftime, localtime

def publisher_example(req):
    req.content_type = 'text/html'
    time_str = strftime("%a %b %d %H:%M:%S %Y", localtime())
    message = "<h1>Hello from mod_python!</h1>"
    message += "<p>The time on this server is %s</p>" % (time_str)
    return message

These two scripts are linked to eachother like this :

  • You configure scripts in the directory /usr/local/apache2/htdocs/PublisherExample to be handled by standard handler mod_python.publisher
  • The url given will be /PublisherExample/ModPythonExample.py/publisher_example, or Directory/scriptname/functionname.
  • publisher runs that function and displays the result

custom handlers

Of course you can also make a custom handler, say customexample. The apache configuration will change to :

customexample
1
2
3
4
5
<Directory /usr/local/apache2/htdocs/CustomExample>
    AddHandler mod_python .py
    PythonDebug On
    PythonHandler customexample
</Directory>

Make a new script called customexample.py

customexample.py
1
2
3
4
5
6
from mod_python import apache

def handler(req):
    req.content_type = 'text/plain'
    req.write('Hello from mod_python!')
    return apache.OK

restart apache, and note that not only the new script will print :

output
Hello from mod_python!

But also your ModPythonExample.py because we altered the apache configuration. And it now handles it like :

  • You configure scripts in the directory /usr/local/apache2/htdocs/PublisherExample to be handled by the customexample handler
  • period

python server pages

Now this is roughly the php tags version we are familiar with. Alter the section in the apache configuration, or use another directory :

server pages
1
2
3
4
5
<Directory /usr/local/apache2/htdocs/PSPExample>
    AddHandler mod_python .psp
    PythonHandler mod_python.psp
    PythonDebug On
</Directory>

As you see we use another extension and another handler. Now you could also just use .py as an extension, but .psp is the default so why not. Now create a file called index.psp the /PSPExample directory containing:

psp
<html>
<head>
    <title>Python Server Pages (PSP)</title>
</head>
<body>
<%
import time
%>
     Hello world, the time is: <%=time.strftime("%Y-%m-%d, %H:%M:%S")%>
</body>
</html>

And browse to /PSPExample/index.psp. Now you can also try to go to the enclosing directory. You will see that you don't get the same page but a listing containing index.psp. If you want index pages top behave the same as with index.php and index.html, alter the apache config to content more extensions.

The setting used is DirectoryIndex, find it and alter it. Mine was in /etc/apache2/mods-enabled/dir.conf :

<pre>
DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm index.psp index.py
</pre>
mod_wsgi


This would be the newer and preferred way of using python. As said before, this is not so perfect for mixing html and python code, but it does suit well for an approach where you use ajax to get content from python scripts and than formatting it with javascript. For real intermixing you better use a framework with templates like django, which underneath... uses wsgi.

mod wsgi advantages

Better support, for one, easier use and same performance as mod_python.

mod wsgi disadvantages

Worse intermixing with html, and not ideal for replacing php scripts with mod_python versions.

mod wsgi installation

Installation under apache is as simple as installing and enabling the module :

mod wsgi
apt-get install libapache2-mod-wsgi

Normally it will be enable by default, but you can also do :

enable
a2enmod wsgi
service apache2 reload

usage

The WSGI application interface is implemented as a callable object:

  • a function,
  • a method,
  • a class or an instance with a call method

Any of these function,method or call method gets two parameters:

  • environment
  • handler

Our example will be a function :

JsonService
#! /usr/bin/env python

from wsgiref.simple_server import make_server

def JsonService(environ,callback):
        method ='The request method was %s' % environ['REQUEST_METHOD']
        status = '200 OK'
        response_body = ['%s: %s' % (key, value) for key, value in sorted(environ.items())]
        response_body = '\n'.join(response_body)
        response_headers = [('Content-Type', 'text/plain'),
                       ('Content-Length', str(len(response_body)))]
        callback(status, response_headers)
        return [response_body]
httpd = make_server('localhost', 7072, JsonService)
httpd.handle_request()

make_server does just that, with the JsonService application (function). The server is then started and you can visit it at http://localhost:7072 The handler will then print out the complete environment in the browser.

If you only need a single environment variable look at the method line for an example.

The returned variable is return between [] with some reason. If not the server allegedly send the while strings byte by byte. And that does not happen as an iterable. I detect no difference on my local machine, but i have no problem putting it with brackets.

For the syntax on the part that fills the formatted list see lists below.

wsgi seems to be mainly designed for frameworks and toolkits because there is no .psp port for it and so you need to generate html with python and pass that to the client. But i use it in another way, for generating content through java calls. Below are two straight examples one for GET and one for POST requests.

GET

This is a complete server example that handles a GET request, running as a standalone server at port 8051 :

GET
#!/usr/bin/python

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

html = """
<html>
<body>
   <form method="get" action="parsing_get.wsgi">
      <p>
         Age: <input type="text" name="age">
         </p>
      <p>
         Hobbies:
         <input name="hobbies" type="checkbox" value="software"> Software
         <input name="hobbies" type="checkbox" value="tunning"> Auto Tunning
         </p>
      <p>
         <input type="submit" value="Submit">
         </p>
      </form>
   <p>
      Age: %s<br>
      Hobbies: %s
      </p>
   </body>
</html>"""

def application(environ, start_response):

   # Returns a dictionary containing lists as values.
   d = parse_qs(environ['QUERY_STRING'])

   # In this idiom you must issue a list containing a default value.
   age = d.get('age', [''])[0] # Returns the first age value.
   hobbies = d.get('hobbies', []) # Returns a list of hobbies.

   # Always escape user input to avoid script injection
   age = escape(age)
   hobbies = [escape(hobby) for hobby in hobbies]

   response_body = html % (age or 'Empty',
               ', '.join(hobbies or ['No Hobbies']))

   status = '200 OK'

   # Now content type is text/html
   response_headers = [('Content-Type', 'text/html'),
                  ('Content-Length', str(len(response_body)))]
   start_response(status, response_headers)

   return [response_body]

httpd = make_server('localhost', 8051, application)
# Now it is serve_forever() in instead of handle_request().
# In Windows you can kill it in the Task Manager (python.exe).
# In Linux a Ctrl-C will do it.
httpd.serve_forever()

It is pretty self-explanatory, note the difference in the last line where it says serve_forever() instead if handle_request(). Also pretty self-explanatory ...

POST

Here is the same example in post form :

POST
!/usr/bin/env python

from wsgiref.simple_server import make_server
from cgi import parse_qs, escape

html = """
<html>
<body>
   <form method="post" action="parsing_post.wsgi">
      <p>
         Age: <input type="text" name="age">
         </p>
      <p>
         Hobbies:
         <input name="hobbies" type="checkbox" value="software"> Software
         <input name="hobbies" type="checkbox" value="tunning"> Auto Tunning
         </p>
      <p>
         <input type="submit" value="Submit">
         </p>
      </form>
   <p>
      Age: %s<br>
      Hobbies: %s
      </p>
   </body>
</html>
"""

def application(environ, start_response):

   # the environment variable CONTENT_LENGTH may be empty or missing
   try:
      request_body_size = int(environ.get('CONTENT_LENGTH', 0))
   except (ValueError):
      request_body_size = 0

   # When the method is POST the query string will be sent
   # in the HTTP request body which is passed by the WSGI server
   # in the file like wsgi.input environment variable.
   request_body = environ['wsgi.input'].read(request_body_size)
   d = parse_qs(request_body)

   age = d.get('age', [''])[0] # Returns the first age value.
   hobbies = d.get('hobbies', []) # Returns a list of hobbies.

   # Always escape user input to avoid script injection
   age = escape(age)
   hobbies = [escape(hobby) for hobby in hobbies]

   response_body = html % (age or 'Empty',
               ', '.join(hobbies or ['No Hobbies']))

   status = '200 OK'

   response_headers = [('Content-Type', 'text/html'),
                  ('Content-Length', str(len(response_body)))]
   start_response(status, response_headers)

   return [response_body]

httpd = make_server('localhost', 8051, application)
httpd.serve_forever()

Now on to a wsgi example that handles ajax calls.

ajax json rpc

Let's use JQuery to post ajax requests to a python script. The calling side would be :

ajax
<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">

        <title>test</title>
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
        <script>

            $(function()
            {
                $('#clickme').click(function(){
                    alert('Im going to start processing');

                    $.ajax({
                        url: "ajaxpost.py",
                        type: "post",
                        datatype:"json",
                        data: {'key':'value','key2':'value2'},
                        success: function(response){
                            alert(response.message);
                            alert(response.keys);
                        }
                    });
                });
            });

        </script>
    </head>
    <body>
        <button id="clickme"> click me </button>
    </body>

</html>

This will not do exactly what we want, it will fail because it does not interpret the ajaxpost.py file but just dumps the source code. So we need to make apache run the script not return it.

One way to do it is use mod_wsgi, but apparently it can also be done with fastcgi, and me like that so what you do is install flup (ok the makers don't know how that sound in dutch i guess) which implements a standard interface between python web applications and webservers (ajp,fastcgi and scgi) .

flup
apt-get install python-flup 

In the python code, run a flup server like this (myapplication.fcgi)

fcgi
1
2
3
4
#!/usr/bin/python
from flup.server.fcgi import WSGIServer
from myapplication import application
WSGIServer(application).run()

And in the apache config, create a scriptalias like this :

fcgi apache
1
2
3
4
<ServerName www.example.com>
    Alias /public /path/to/the/static/files
    ScriptAlias / /path/to/myapplication.fcgi/
</ServerName>