Python CGI Programming
What is CGI
CGI is currently maintained by NCSA, which defines CGI as follows:
CGI (Common Gateway Interface), the Common Gateway Interface, is a program that runs on the server, such as an HTTP server, providing an interface to client HTML pages.
Web Browsing
To better understand how CGI works, let's look at the process of clicking a link or URL on a web page:
- Use your browser to access the URL and connect to the HTTP web server.
- The web server receives the request message, parses the URL, checks if the requested file exists on the server, and returns the file content if it exists, otherwise returns an error message.
- The browser receives information from the server and displays the received file or error message.
CGI programs can be Python scripts, PERL scripts, SHELL scripts, C or C++ programs, etc.
CGI Architecture Diagram
Web Server Support and Configuration
Before you start CGI programming, make sure your web server supports CGI and has been configured with CGI processing.
Apache supports CGI configuration:
Set up the CGI directory:
ScriptAlias /cgi-bin/ /var/www/cgi-bin/
All HTTP servers execute CGI programs in a pre-configured directory. This directory is called the CGI directory and by convention, it is named /var/www/cgi-bin.
The extension for CGI files is .cgi, and Python can also use the .py extension.
By default, Linux servers are configured to run the cgi-bin directory at /var/www.
If you want to specify a different directory for running CGI scripts, you can modify the httpd.conf configuration file as follows:
<Directory "/var/www/cgi-bin">
AllowOverride None
Options +ExecCGI
Order allow,deny
Allow from all
</Directory>
Add the .py suffix in AddHandler so that we can access Python script files ending in .py:
AddHandler cgi-script .cgi .pl .py
First CGI Program
We use Python to create our first CGI program, named hello.py, located in the /var/www/cgi-bin directory, with the following content:
Example
#!/usr/bin/python3
print("Content-type:text/html")
print() # Empty line, to indicate the end of the header
print('<html>')
print('<head>')
print('<meta charset="utf-8">')
print('<title>Hello Word - My First CGI Program!</title>')
print('</head>')
print('<body>')
print('<h2>Hello Word! I am the first CGI program from tutorialpro.org</h2>')
print('</body>')
print('</html>')
After saving the file, modify hello.py, changing the file permissions to 755:
chmod 755 hello.py
The above program, when accessed in the browser, displays the following result:
This hello.py script is a simple Python script. The first line of output, "Content-type:text/html," is sent to the browser, informing it that the content type to be displayed is "text/html".
An empty line is printed to tell the server that the header information is over.
HTTP Header
The "Content-type:text/html" in the hello.py file is part of the HTTP header, which is sent to the browser to inform it of the content type of the file.
The format of the HTTP header is as follows:
HTTP Field Name: Field Content
For example:
Content-type: text/html
The following table describes commonly used information in the HTTP header in CGI programs:
Header | Description |
---|---|
Content-type: | MIME information corresponding to the entity requested. For example: Content-type:text/html |
Expires: Date | The date and time the response expires |
Location: URL | Used to redirect the recipient to a location other than the requested URL to complete the request or identify a new resource |
Last-modified: Date | The last modification time of the requested resource |
Content-length: N | The length of the requested content |
Set-Cookie: String | Sets an Http Cookie |
CGI Environment Variables
All CGI programs receive the following environment variables, which play important roles in CGI programs:
Variable Name | Description |
---|---|
CONTENT_TYPE | The value of this environment variable indicates the MIME type of the information being passed. Currently, the CONTENT_TYPE environment variable is typically: application/x-www-form-urlencoded, indicating that the data comes from an HTML form. |
CONTENT_LENGTH | If the server communicates with the CGI program using the POST method, this environment variable indicates the number of bytes of valid data that can be read from the standard input STDIN. This environment variable must be used when reading input data. |
HTTP_COOKIE | The COOKIE content from the client machine. |
HTTP_USER_AGENT | Provides information about the client's browser, including version number or other proprietary data. |
PATH_INFO | The value of this environment variable represents additional path information following the CGI program name. It often appears as a parameter to the CGI program. |
QUERY_STRING | If the server communicates with the CGI program using the GET method, the value of this environment variable is the information being passed. This information follows the CGI program name, separated by a question mark '?'. |
REMOTE_ADDR | The value of this environment variable is the IP address of the client machine sending the request, such as 192.168.1.67. This value is always present and is the only identifier the Web client needs to provide to the Web server, which can be used in the CGI program to differentiate between different Web clients. |
REMOTE_HOST | The value of this environment variable contains the hostname of the client machine sending the CGI request. If reverse DNS lookup is not supported, this environment variable does not need to be defined. |
REQUEST_METHOD | Provides the method by which the script was called. For scripts using the HTTP/1.0 protocol, only GET and POST are meaningful. |
SCRIPT_FILENAME | The full path of the CGI script. |
SCRIPT_NAME | The name of the CGI script. |
SERVER_NAME | This is the hostname, alias, or IP address of your web server. |
SERVER_SOFTWARE | The value of this environment variable contains the name and version number of the HTTP server calling the CGI program. For example, the value above is Apache/2.2.14(Unix). |
Below is a simple CGI script that outputs the CGI environment variables:
Example
#!/usr/bin/python3
import os
print("Content-type: text/html")
print()
print("<meta charset=\"utf-8\">")
print("<b>Environment Variables</b><br>")
print("<ul>")
for key in os.environ.keys():
print("<li><span style='color:green'>%30s </span> : %s </li>" % (key, os.environ[key]))
print("</ul>")
Save the above script as test.py and modify the file permissions to 755. The execution result is as follows:
GET and POST Methods
The browser client transmits information to the server through two methods, GET and POST.
Using GET Method to Transfer Data
The GET method sends encoded user information to the server, with the data information included in the URL of the request page, separated by a "?" character, as shown below:
http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
- GET requests can be cached
- GET requests remain in the browser history
- GET requests can be bookmarked
- GET requests should not be used when dealing with sensitive data
- GET requests have length restrictions
- GET requests should only be used to retrieve data
Simple URL Example: GET Method
The following is a simple URL that sends two parameters to the hello_get.py program using the GET method:
/cgi-bin/test.py?name=tutorialpro.org&url=http://www.tutorialpro.org
The following is the code for the hello_get.py file:
Example
#!/usr/bin/python3
# CGI processing module
import cgi, cgitb
# Create an instance of FieldStorage
form = cgi.FieldStorage()
# Retrieve data
site_name = form.getvalue('name')
site_url = form.getvalue('url')
print("Content-type:text/html")
print()
print("<html>")
print("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>tutorialpro.org CGI Test Example</title>")
print ("</head>")
print ("<body>")
print ("<h2>%s Official Website: %s</h2>" % (site_name, site_url))
print ("</body>")
print ("</html>")
After saving the file, modify hello_get.py and change the file permissions to 755:
chmod 755 hello_get.py
Browser request output result:
Simple Form Example: GET Method
The following is an example of sending two pieces of data to the server using the GET method through an HTML form. The server script submitted is also the hello_get.py file. The hello_get.html code is as follows:
Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="get">
Site Name: <input type="text" name="name"> <br />
Site URL: <input type="text" name="url" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
By default, the cgi-bin directory can only store script files. We will store hello_get.html in the test directory and change the file permissions to 755:
chmod 755 hello_get.html
Gif demonstration is shown below:
Transmitting Data Using the POST Method
Using the POST method to transmit data to the server is more secure and reliable, especially for sensitive information such as user passwords.
The following is also hello_get.py, which can also handle POST form data submitted by the browser:
Example
#!/usr/bin/python3
# CGI processing module
import cgi, cgitb
# Create an instance of FieldStorage
form = cgi.FieldStorage()
# Get data
site_name = form.getvalue('name')
site_url = form.getvalue('url')
print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>tutorialpro.org CGI Test Example</title>")
print ("</head>")
print ("<body>")
print ("<h2>%s Official Website: %s</h2>" % (site_name, site_url))
print ("</body>")
print ("</html>")
The following form submits data to the server script hello_get.py via the POST method (method="post"):
Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py" method="post">
Site Name: <input type="text" name="name"> <br />
Site URL: <input type="text" name="url" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
</form>
Gif demonstration is shown below:
Passing Checkbox Data Through a CGI Program
A checkbox is used to submit one or multiple option data. The HTML code is as follows:
Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<form action="/cgi-bin/checkbox.py" method="POST" target="_blank">
English:
<input type="checkbox" name="tutorialpro" value="on" /> tutorialpro.org <input type="checkbox" name="google" value="on" /> Google <input type="submit" value="Select Sites" /> </form> </body> </html>
Here is the code for the checkbox.py file:
## Example
!/usr/bin/python3
Import CGI processing module
import cgi, cgitb
Create an instance of FieldStorage
form = cgi.FieldStorage()
Receive field data
if form.getvalue('google'): google_flag = "Yes" else: google_flag = "No"
if form.getvalue('tutorialpro'): tutorialpro_flag = "Yes" else: tutorialpro_flag = "No"
print ("Content-type:text/html") print () print ("<html>") print ("<head>") print ("<meta charset=\"utf-8\">") print ("<title>tutorialpro.org CGI Test Example</title>") print ("</head>") print ("<body>") print ("<h2> tutorialpro.org selected: %s</h2>" % tutorialpro_flag) print ("<h2> Google selected: %s</h2>" % google_flag) print ("</body>") print ("</html>")
Modify checkbox.py permissions:
chmod 755 checkbox.py
Browser access GIF demonstration:
</head> <body> <form action="/cgi-bin/textarea.py" method="post" target="_blank"> <textarea name="textcontent" cols="40" rows="4"> Enter content here... </textarea> <input type="submit" value="Submit" /> </form> </body> </html>
textarea.py script code is as follows:
## Example
!/usr/bin/python3
Import CGI processing module
import cgi, cgitb
Create an instance of FieldStorage
form = cgi.FieldStorage()
Receive field data
if form.getvalue('textcontent'): text_content = form.getvalue('textcontent') else: text_content = "No content"
print ("Content-type:text/html") print () print ("<html>") print ("<head>") print ("<meta charset=\"utf-8\">") print ("<title>tutorialpro.org CGI Test Example</title>") print ("</head>") print ("<body>") print ("<h2> The content entered is: %s</h2>" % text_content) print ("</body>") print ("</html>")
Modify textarea.py permissions:
chmod 755 textarea.py
Browser access Gif demonstration:
Set-Cookie: name=name; expires=date; path=path; domain=domain; secure
- **name=name:** The value to be set for the cookie (name cannot use "**;**" and "**,**"), multiple name values are separated by "**;**", for example: **name1=name1; name2=name2; name3=name3**.
- **expires=date:** The expiration date of the cookie, format: expires="Wdy, DD-Mon-YYYY HH:MM:SS".
- **path=path:** The path for which the cookie is set. If path is a directory, the cookie is valid for all files and subdirectories in this directory, for example: path="/cgi-bin/". If path is a file, the cookie is only valid for this file, for example: path="/cgi-bin/cookie.cgi".
- **domain=domain:** The domain for which the cookie is valid, for example: domain="www.tutorialpro.org".
- **secure:** If this flag is given, the cookie can only be transmitted through an SSL protocol HTTPS server.
- The reception of cookies is achieved by setting the environment variable HTTP_COOKIE, which a CGI program can retrieve to obtain cookie information.
---
## Cookie Setting
Setting a cookie is very simple; the cookie is sent separately in the HTTP header. The following example sets the name and expires for a cookie:
## Example
```python
#!/usr/bin/python3
print('Set-Cookie: name="tutorialpro.org"; expires=Wed, 28 Aug 2016 18:30:00 GMT')
print('Content-Type: text/html')
print()
print("""
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<h1>Cookie set OK!</h1>
</body>
</html>
""")
Save the above code to cookie_set.py and modify the permissions of cookie_set.py:
chmod 755 cookie_set.py
The above example uses the Set-Cookie header to set the Cookie information, with optional settings for other attributes such as the expiration time Expires, domain Domain, and path Path. These settings are made before the "Content-type:text/html".
Retrieving Cookie Information
Retrieving cookie information is also very simple. The cookie information is stored in the CGI environment variable HTTP_COOKIE in the following format:
key1=value1; key2=value2; key3=value3....
The following is a simple CGI program to retrieve cookie information:
Example
#!/usr/bin/python3
# Import modules
import os
import http.cookies
print("Content-type: text/html")
print()
print("""
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<h1>Reading cookie information</h1>
""")
if 'HTTP_COOKIE' in os.environ:
cookie_string = os.environ.get('HTTP_COOKIE')
c = http.cookies.SimpleCookie()
c.load(cookie_string)
try:
data = c['name'].value
print("cookie data: " + data + "<br>")
except KeyError:
print("cookie not set or expired<br>")
print("""
</body>
</html>
""")
Save the above code to cookie_get.py and modify the permissions of cookie_get.py:
chmod 755 cookie_get.py
The above cookie setting demonstration is shown in the following GIF:
File Upload Example
To set up an HTML form for uploading files, you need to set the enctype attribute to multipart/form-data, as shown in the following code:
Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<form enctype="multipart/form-data"
action="/cgi-bin/save_file.py" method="post">
<p>Select file: <input type="file" name="filename" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
</body>
</html>
The save_file.py
script file code is as follows:
Example
#!/usr/bin/python3
import cgi, os
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
# Get the file name
fileitem = form['filename']
# Check if the file was uploaded
if fileitem.filename:
# Set the file path
fn = os.path.basename(fileitem.filename)
open('/tmp/' + fn, 'wb').write(fileitem.file.read())
message = 'File "' + fn + '" uploaded successfully'
else:
message = 'No file was uploaded'
print ("""\
Content-Type: text/html\n
<html>
<head>
<meta charset="utf-8">
<title>tutorialpro.org(tutorialpro.org)</title>
</head>
<body>
<p>%s</p>
</body>
</html>
""" % (message,))
Save the above code to save_file.py
and modify the permissions of save_file.py
:
chmod 755 save_file.py
The above cookie setting demonstration Gif is shown below:
If you are using a Unix/Linux system, you must replace the file separator. On Windows, you only need to use the open()
statement:
fn = os.path.basename(fileitem.filename.replace("\\", "/" ))
File Download Dialog
First, create a foo.txt
file in the current directory for the program to download.
File download is achieved by setting HTTP headers. The functional code is as follows:
Example
#!/usr/bin/python3
# HTTP Header
print ("Content-Disposition: attachment; filename=\"foo.txt\"")
print ()
# Open file
fo = open("foo.txt", "rb")
str = fo.read();
print (str)
# Close file
fo.close()