submit to reddit       

Writing CGI scripts on a Raspberry Pi

This tutorial assumes that you have installed Apache2. You can do this by opening a terminal and typing

sudo apt-get install apache2

Serving static HTML files is limited, and not interactive. Scripting makes web sites dynamic and opens up possibilities like building interactive web sites and sites that can remotely control devices.

CGI scripts are usually stored in /usr/lib/cgi-bin, and they must have executable permission. Scripts execute when they are requested by a browser. Anything printed to stdout will be displayed in a browser, so many scripts are used to generate HTML.

CGI scripting in bash

Start Leafpad by opening a terminal and typing

sudo leafpad /usr/lib/cgi-bin/hello.cgi &

Type this code into leafpad:

#!/bin/bash echo -e "Content-type: text/html\n\n" echo "<h1>Hello World</h1>"

Save it, and make the file executable with this command:

sudo chmod +x /usr/lib/cgi-bin/hello.cgi

Open a browser and go to the address of the script. For example, if your Pi's IP address is, the URL of the script is You should see the words "Hello World" in large letters in your browser.

The first line of the script indicates that this file should be interpreted by bash. This line won't be sent to the browser that requested the page.

The next line is an HTTP header. The header is printed to stdout, so it will be sent to the browser. In this example the header just tells the browser that the following text should be interpreted as HTML. HTTP headers can contain other information such as the length of time that a page should be stored in cache. Cookies can also be set by printing set cookie commands in the header.

Note the double '\n' at the end of the header. This is important because there must be an empty line after the header. This empty line tells the browser that the header has finished, and any following text is HTML. Don't forget the quotes at the beginning and end of each line of HTML that's echoed.

I'm using the echo command to print HTML to stdout. It's also possible to store snippets of HTML in files, and then print the contents of those file to stdout using the cat command.

Environment variables

When an HTTP request is received, several environment variables are set by the web server and passed on to the script. You can find a list of CGI environment variables at http://en.wikipedia.org/wiki/Common_Gateway_Interface.

One particularly important environment variable is QUERY_STRING, which holds arguments that are passed to scripts. The REQUEST_METHOD variable tells the server which mechanism was used to make a request, GET or POST. The REMOTE_ADDR variable contains the IP address of the computer that made the request.

If you have installed wiringPi, then you can use the gpio command to control GPIO pins in your bash scripts. If you're using a Pi Face board, don't forget to use the -p option with the gpio utility. Note that if you're using a Pi Face, you don't need to set the modes of each output or input because they are fixed and can't be changed.


Writing CGI scripts in Python

In the last post, I looked at CGI scripting in Bash. The principles are pretty much the same when you use Python; you need a line to specify which interpreter should execute the script. There needs to be a header, which at the very least should specify the content-type, and as before, anything printed to stdout after the header is displayed in the browser that made the request. Before we can use Python scripts in Apache, we need to update its settings.

How to enable .py scripts in Apache2

By default Python is configured to only execute scripts with specific endings such as .cgi. In order to make Apache execute .py files, you need to edit a configuration file. In a terminal type this command:

sudo leafpad /etc/apache2/sites-enabled/000-default &

This file contains settings for the default site (you can create other sites on the same server). Look for a section of code that looks like this:

<Directory "/usr/lib/cgi-bin">

Add the following line inside the above section of code:

AddHandler cgi-script .py

In a terminal window, type this command to reload Apache's configuration files:

sudo service apache2 reload

Now type this code into a text editor, and save it as /usr/lib/cgi-bin/hello.py:

#!/usr/bin/env python
print "Content-type: text/html\n\n"
print "<h1>Hello World</h1>"

Make the file executable using this command:

$ sudo chmod +x hello.py

As in the previous post, you can execute this script by accessing it through your browser. The IP address of my Raspberry Pi is, so I typed this address into my browser: Once again, you should see the words "Hello World" in large letters.

How do I debug my Python scripts?

There are many different ways of debugging CGI scripts. One of the basic methods is to enable traceback print outs in Python scripts. You can do this by importing modules cgi and cgitb. If you import cgitb, it won't work unless you enable it at the start of your scripts:

#!/usr/bin/env python

import cgi
import cgitb


print "Content-type: text/html\n\n"

print "<h1>Hello World</h1>"

You can also get some information about script problems from /var/log/apache2/error.log. You need to scroll down to the end of this file to see the most recent errors.


Using Google charts in Python CGI scripts

A common use of the Raspberry Pi is data logging. People use them to monitor temperatures, voltages, weather conditions, and all sorts of other things. Once you've collected data, you need some way to display it. Google provide Javascript code to embed charts in your site.

In this eaxmple I'm going to use a Python script to read data from a file and display a page with a Javascript chart in it. The chart will have dates along the x-axis and a count along the y-axis. The count could be the number of hits to a web site, the number of times a button has been pressed, or anything you want. The data used in this example is stored in a text file in /var/www/data.txt. It's format is as follows:


The complete data set for this example looks like this:


The date will be used to label each column, and the height of each column is determined by the value. You don't have to use the date - any string will do. You could use names if you wanted. When I created data.txt, I changed its owner so that Apache can access it:

$ sudo chown www-data:www-data /var/www/data.txt

I wrote a script called chart.py and stored it in /usr/lib/cgi-bin.

#!/usr/bin/env python import cgi import cgitb # enable tracebacks of exceptions cgitb.enable() # print data from a file formatted as a javascript array # return a string containing the table #  def print_table(filename,delimiter):     data_lines=[]     result=""     with open(filename) as data_file:         data_lines=data_file.readlines()         for line in data_lines[:-1]:             x, y=line.strip('\n').split(delimiter)             result += "['"+x+"', "+y+"],\n"         else:             x, y=data_lines[-1].strip('\n').split(delimiter)             result += "['"+x+"', "+y+"]"     return result # print an HTTP header # def printHTTPheader():     print "Content-type: text/html"     print ""     print "" def main():     printHTTPheader()     # this string contains the web page that will be served     page_str="""     <h1>Charts with Python and Javascript</h1>     <script type="text/javascript" src="https://www.google.com/jsapi"> </script>     <script type="text/javascript">       google.load("visualization", "1", {packages:["corechart"]});       google.setOnLoadCallback(drawChart);       function drawChart() {         var data = google.visualization.arrayToDataTable([     ['Date', 'Count'],     %s          ]);         var options = {           title: 'Google column chart',           hAxis: {title: 'Date', titleTextStyle: {color: 'blue'}},           vAxis: {title: 'Count', titleTextStyle: {color: 'blue'}}         };         var chart = new google.visualization.ColumnChart                 (document.getElementById('chart_div'));         chart.draw(data, options);       }     </script>     <div id="chart_div"></div>     </body>     """ % print_table('/var/www/data.txt', ';')     # serve the page with the data table embedded in it     print page_str if __name__=="__main__":     main()

I made the file executable with this command:

$ sudo chmod +x /usr/lib/cgi-bin/chart.py

The Python script generates the pagesent by the server, and the Javascript code is executed in the browser.

Chart.py starts in main() by printing an HTTP header so that the browser knows that the folloing text is HTML. The variable page_str is a string that contains all the HTML and Javascript for the page. The array that stores the data for the chart is inserted at the position marked by %s in page_str. The text substituted here is the table returned by a call to print_table().

This function opens data.txt and reads the data contained in it. A 'for' loop is used to read the file line by line. The strip and split methods are used to remove the trailing newline and break the string into tuples. The tuples are then formatted like this:

['x', y],

Each of these newly formed strings is appended to a larger string that contains the whole table. Note that the last line doesn't need to have a comma at the end of it, so it needs to be handled differently than the other lines. The for loop executes for all but the last line. The 'else' statement after the loop handles the last line, and formats it without a comma at the end.

Once the table has been inserted into the string and the string has been received by a browser, the Javascript code will execute and display the graph. I pointed my browser to

Charts with Python and Javascript

Each chart has options that can be used to customize the way data is displayed. For different types of chart you may need to to modify the format of the table generated by the script. You can find more at Google Charts.

The Javascript code for the column chart is from this page.


Sending data to an HTTP server - get and post methods

There are two different methods for passing variables to a CGI script, the get method and the post method.

The Get Method

The get method is used for ordinary requests to get pages from a server, and it can also have arguments appended to it. The usual format is


The data that's being passed to the script is placed held in an environment variable named QUERY_STRING. The question mark separates the url from the data.

Clicking on this link will generate a get request with several arguments attached:


This python code reads the arguments from the query string and prints them out:

#!/usr/bin/env python import os # print header print "Content-type: text/html\n\n" query_string=os.environ["QUERY_STRING"] print "<h2>Query string</h2>" print "query_string: "+query_string+"<br>" print "<h2>Argument list</h2>" arg_list=query_string.split('&') #print arg_list i=1 for arg in arg_list: key, value=arg.split('=') print "key "+str(i)+": "+key+"<br>" print"value "+str(i)+": "+value+"<br>" i +=1

Don't pass sensitive data like passwords using the get method.

The Post Method

When the post method is used, the browser sends a URL request first and then the data is sent seperately. CGI scripts running on the server process the URL request, and then the data is read from stdin. The post method is often use with forms.

The following form uses several hidden fields and a submit button:

<form action="/cgi-bin/examples/postmethod.py" method="POST"> <input type="hidden" name="os" value="linux"> <input type="hidden" name="cpu" value="ARM11"> <input type="hidden" name="server" value="apache2"> <input type="submit" value="Submit"> </form>

When the submit button is clicked on, this python code executes:

#!/usr/bin/env python import sys args=sys.stdin.read() # print header print "Content-type: text/html\n\n" print "<h2>Arguments</h2>" arg_list=args.split('&') i=1 for arg in arg_list: key, value=arg.split('=') print "key "+str(i)+": "+key+"<br>" print"value "+str(i)+": "+value+"<br>" i +=1

Using the Python CGI module

Python has a CGI API which you can use to access arguments in the same way regardless of whether the request is a post or get. The following script is a simple example of how to use it:

#!/usr/bin/env python import cgi # print header print "Content-type: text/html\n\n" print "<h2>Arguments</h2>" form = cgi.FieldStorage() arg1 = form.getvalue('os') print "OS: " +arg1+"<br>" arg2 = form.getvalue('cpu') print "CPU: "+arg2+"<br>" arg3 = form.getvalue('server') print "Server: "+arg3+"<br>"

The call to cgi.FieldStorage() determines what type of request needs to be processed, and then converts the arguments into an easily accessible form. This script can be used to process get and post requests. Click on the link below to invoke the script using a get request, and click on the submit button to invoke the script using a post request.

<form action="/cgi-bin/examples/cgiform.py" method="POST"> <input type="hidden" name="os" value="linux"> <input type="hidden" name="cpu" value="ARM11"> <input type="hidden" name="server" value="apache2"> <input type="submit" value="Submit"> </form>


Web Forms with Python

Web applications usually need get input from users at some point. Whether you're building a blogging site or a web UI for an embedded device, forms are a great way to allow users to interact with a web site.

A form is a set of input fields contained in form HTML tags. The following are types of form fields:

  • text area
  • text input
  • radio buttons
  • check boxes
  • drop down menus
  • legend
  • file select
  • password
  • submit button
  • reset button

The form tag contains two attributes, the action attribute which specifies which script should execute when the submit button is clicked, and the method used to pass the form to the server, GET or POST.

When the user fills in a form and clicks the submit button, the form data is sent to the server and processed by the script in the form attribute.

Text Area

Text areas allow users to enter a large amount of text like a blog post.

HTML code:

<form action="/cgi-bin/examples/textarea.py" method="POST"> <textarea name="post" cols=70 rows=5></textarea><br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "post" not in form: print "<h1>The text area was empty.</h1>" else: text=form["post"].value print "<h1>Text from text area:</h1>" print cgi.escape(text)

Text Input

A text input field can be used to enter a single line of text.

HTML code:

<form action="/cgi-bin/examples/textinput.py" method="POST"> <input type="text" size="70" name="data" value=""><br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "data" not in form: print "<h1>The text input box was empty.</h1>" else: text=form["data"].value print "<h1>Text from text input box:</h1>" print cgi.escape(text)

Radio buttons

Radio buttons allow users to pick one out of several options.

HTML code:

<form action="/cgi-bin/examples/radiobuttons.py" method="POST"> <input type="radio" name="light" value="on">On<br> <input type="radio" name="light" value="off">Off <br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "light" not in form: print "<h1>Neither radio button was selected.</h1>" else: text=form["light"].value print "<h1>Radio button chosen:</h1>" print cgi.escape(text)

Check box

Users can pick one or more options

HTML code:

<form action="/cgi-bin/examples/checkbox.py" method="POST"> <input type="checkbox" name="setting" value="set" > Executable <br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "setting" not in form: print "<h1>The box was not checked</h1>" else: text=form["setting"].value print "<h1>The check box was ticked:</h1>" print text

Drop down menu

Users can pick an option from a drop down menu.

HTML code:

<form action="/cgi-bin/examples/dropdown.py" method="POST"> My favaourite programming language is <select name="language"> <option value="c">C</option> <option value="python">Python</option> <option value="php">PHP</option> <option value="Java">Java</option> </select> <br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>
My favaourite programming language is

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "language" not in form: print "<h1>No option chosen</h1>" else: text=form["language"].value print "<h1>The option chosen was:</h1>" print text


HTML code:

<form action="/cgi-bin/examples/legend.py" method="POST"> <fieldset> <legend>Legend</legend> <p>These paragraphs are surrounded by a legend.<p> <p>You can use legends to group things together like these two
paragraphs.<p> </fieldset> </form>

These paragraphs are surrounded by a legend.

You can use legends to group things together like these two

Password field

Like a text input box, but characters are obscured as they're typed in.

HTML code:

<form action="/cgi-bin/examples/password.py" method="POST"> Username: <input type="text" name="user_name" value=""> Password: <input type="password" name="password"> <br> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>
Username: Password:

Python code:

#!/usr/bin/env python import cgi import cgitb cgitb.enable() print "Content-type: text/html\n\n" form=cgi.FieldStorage() if "user_name" not in form: print "<h1>No username was entered</h1>" else: text=form["user_name"].value print "<h1>Username:</h1>" print cgi.escape(text) if "password" not in form: print "<h1>No password was entered</h1>" else: text=form["password"].value print "<h1>Password:</h1>" print cgi.escape(text)


Using Python to set, retrieve and clear cookies

What are cookies?

Sometimes it's useful for a web site to store data on a user's computer. Some sites allow users to set preferences and options that control the way pages are displayed, or specifiy which categories a user is interested in. The BBC News web site uses cookies to store information about users' locations so that they can see local news from their region on the BBC News home page.

It would be risky to allow web sites to access files on a user's computer because malicious web sites could access users' personal data. Cookies are stored inside a web browser, so web sites can store information in them without needing access to files. Any malicious data put into cookies is contained and can't do any damage.

How do web servers set cookies?

Cookies are set and accessed by web servers when a user loads a page. When a web server receives a request from a web browser, it sends HTTP headers back to the browser, and then it sends the page that was requested. The HTTP headers are a series of text parameters sparated by carriage return and line feed characters. Cookies are set using the Set-Cookie parameter.

In a script, the simplest way to set headers is to print them before sending any other data. You must make sure there is an empty line after the headers and before the page content, hence the double "\r\n" at the end of the last header field:

#!/usr/bin/env python print 'Content-Type: text/html\r\n' print 'Set-Cookie: raspberrypi="Hello world"; \ expires=Wed, 28 Aug 2013 18:30:00 GMT\r\n\r\n' print """ <html> <body> <h1>Some web page</h1> </body> </html> """

Cookies have a name and a value. In the example above, the name of the cookie that's set is raspberrypi, and its value is "Hello world". They can have other attributes including:

  • domain: the domain name of the site setting the cookie,
  • path: the path that set cookie, or the path where a cookie is relevant,
  • expires: the expiry date when browsers should delete the cookie,
  • secure: if this field is set, the cookie should only be retrieved from a secure server using HTTPS. This ensures that cookies are encrypted when they are sent.

Setting cookies with Python

Python provides a library for handling cookies. The example below shows how to create a cookie, assign a value to it, and assign a value to the 'expires' attribute:

#!/usr/bin/env python import Cookie # create the cookie c=Cookie.SimpleCookie() # assign a value c['raspberrypi']='Hello world' # set the xpires time c['raspberrypi']['expires']=1*1*3*60*60 # print the header, starting with the cookie print c print "Content-type: text/html\n" # empty lines so that the browser knows that the header is over print "" print "" # now we can send the page content print """ <html> <body> <h1>The cookie has been set</h1> </body> </html> """

After the cookie has been set up, instead of having to print the Set-Cookie directive explicitly, we can just print the variable c. In this example, the expires time is set to 10,800 seconds (3 hours) from the time the cookie was set. You can execute this script by following this link (if your browser has cookies enabled, this script will set a cookie in your bowser that contains the string "Hello world"):


See this page on Wikihow that shows you how to view cookies in different browsers.

You can see what the HTTP headers look like by using the wget command, which is usually used to download files from web sites. This command can be told to save headers using the --save-headers option:

$ wget --save-headers http://raspberrywebserver.com/cgi-bin/examples/cookies.py

This command will create a file called cookies.py in the directory where the command was run. If you open the file in a text editor, you'll see the headers followed by the content produced when the script is executed on a server.

Retrieving cookies

If you save data in cookies, you need to be able to retrieve that data. The following script will check to see if a cookie has been set in a browser:

#!/usr/bin/env python import os import Cookie print "Content-type: text/html\n\n" print """ <html> <body> <h1>Check the cookie</h1> """ if 'HTTP_COOKIE' in os.environ: cookie_string=os.environ.get('HTTP_COOKIE') c=Cookie.SimpleCookie() c.load(cookie_string) try: data=c['raspberrypi'].value print "cookie data: "+data+"<br>" except KeyError: print "The cookie was not set or has expired<br>" print """ </body> </html> """

The Python cookie library is used to create a cookie variable, and then the load function is used to get the cookie contents from the browser. Once it's been loaded the data contained in the cooke can be accessed. Clicking on the following link will execute the script and see if the cookie is set on your computer:


Deleting cookies

Cookies can be cleared by setting their expires time to some time in the past. It's conventional to set them to midnight Thursday 1st January 1970. Web servers can't force browsers to clear cookies; they can only suggest that a browser deletes a cookie. Whether or not a cookie is actually deleted is up to the browser.

This script sets the expires time of a cookie in the past so that the cookie will be cleared:

#!/usr/bin/env python import Cookie import cgi # set the cookie to expire c=Cookie.SimpleCookie() c['raspberrypi']='' c['raspberrypi']['expires']='Thu, 01 Jan 1970 00:00:00 GMT' # print the HTTP header print c print "Content-type: text/html\n\n" print """ <html> <body> <h1>The cookie has been set to expire</h1> </body> </html> """

It also sets the value of the cookie to an empty string in case the browser doesn't delete the cookie. Click this link to clear the cookie from your browser (note that this works better in some browsers than others):


See also: http://en.wikipedia.org/wiki/HTTP_cookie.


Setting up Nginx and uWSGI for CGI scripting

Nginx is an open source web server that's designed to handle heavy traffic efficiently and quickly, and it has a reputation for serving static content like images and HTML files much faster than Apache. Unlike Apache, Nginx doesn't have built in support for executing CGI scripts, so a helper application like FastCGI or uWSGI is needed to handle dynamic content.

I'm using Nginx with uWSGI. There are many different modes and plugins that you can use with uWSGI. I'm using the CGI plugin.

The first thing I needed to do was install Nginx:

sudo apt-get install nginx

I wanted to duplicate the set up that I have with Apache. I have a numerous scripts in /usr/lib/cgi-bin, and the web directory is /var/www, so I edited /etc/nginx/sites-enabled/default and set the root directive to /var/www.

I created two locations which Nginx can use to serve requests from. Locations are directories in the Pi's file system which contain files that can be served by the web server. The Nginx configuration files can be used to define locations and define rules about how requests for URLs in those locations should be handled.

The first location is the 'root' location for files in /var/www. This is the default location where static files are stored. In this location I used the try_files directive to tell Nginx to try sending a file if the URL matches a file name. If the requested URL is a directory rather than a file, the try directive will see if that directory contains a file called index.html. If there is a file called index.html in the directory, it will be sent to the user. If the requested URL wasn't a file, and it wasn't a directory, the try_directive will fall back on redirecting the request to pyindex.py, the main script of the CMS that powers my site. Instead of using this script, you can just replace /cgi-bin/pyindex.py?q=$uri with index.html.

The second location was for /cgi-bin. If a user requests a URL that references a file in /usr/lib/cgi-bin, then a CGI script needs to be executed. Nginx doesn't handle CGI, so the request has to be passed to the uWSGI daemon, which is accessible via the loopback address. In the uWSGI settings this location is mapped on to /usr/lib/cgi-bin - see below. Setting uwsgi_modifier1 to 9 tells uWSGI to handle requests for this location as CGI scripts.

This is the complete configuration file in /etc/nginx/sites-enabled/:

server { #listen 80; ## listen for ipv4; this line is default and implied #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 root /var/www; index index.html; # Make site accessible from http://localhost/ server_name localhost; location / { index index.html; # First attempt to serve request as file, then # as directory, then fall back to the CMS. try_files $uri $uri/index.html /cgi-bin/pyindex.py?q=$uri; } location /cgi-bin { include uwsgi_params; uwsgi_modifier1 9; uwsgi_pass; } location /doc/ { alias /usr/share/doc/; autoindex on; allow; allow ::1; deny all; } }

Nginx doesn't use multiple threads to serve requests in the same way that Apache does. It's recommended that you configure Nginx to use one worker process per CPU core, so just one worker when running on a Raspberry Pi. In /etc/nginx/nginx.conf, I set worker_processes to 1.

After making these configuration changes, it's necessary to restart Nginx:

sudo service nginx restart

The next step was to install uWSGI. The version of uWSGI in the Raspbian repository is out of date, and didn't seem to work properly. Instead of installing it with apt-get, use this command:

curl http://uwsgi.it/install | bash -s cgi /home/pi/uwsgi

This downloads and builds the code with the CGI plugin, and places the uwsgi binary in /home/pi/uwsgi. The uwsgi file doesn't have to be in your home directory, you can put it somewhere else if you prefer. Building the code takes a little while, so be patient.

A configuration file is needed to tell the uWSGI daemon how to handle requests. I saved the following as /home/pi/uwsgi_config.ini:

[uwsgi] plugins = cgi socket = chdir = /usr/lib/cgi-bin/ module = pyindex cgi=/cgi-bin=/usr/lib/cgi-bin/ cgi-helper =.py=python

This configuration file tells uWSGI to use the CGI plugin, listen on, and change the working directory to /usr/lib/cgi-bin/. It also specifies that the location /cgi-bin is mapped onto /usr/lib/cgi-bin/, and that uWSGI should use Python to execute .py files.

The next step is to start the uWSGI process, running it as user www-data:

sudo -u www-data ./uwsgi ./uwsgi_config.ini

The next step is to set up a simple test script to make sure everything's working. I saved this code in /usr/lib/cgi-bin/hello.py:

#!/usr/bin/env python print "Content-type: text/html\n\n" print "<h1>Hello World</h1>"

I made the file executable with this command:

sudo chmod +x /usr/lib/cgi-bin/hello.py

Now when I go to in my browser, I'm greeted with this message:

Hello World


Follow me