第 5 章 电子邮件协议、FTP和CGI编程

本章攻略:

  • 列出远程FTP服务器中的文件
  • 把本地文件上传到远程FTP服务器中
  • 把当前工作目录中的内容压缩成ZIP文件后通过电子邮件发送
  • 通过POP3协议下载谷歌电子邮件
  • 通过IMAP协议查收远程服务器中的电子邮件
  • 通过Gmail的SMTP服务器发送带有附件的电子邮件
  • 使用CGI为基于Python的Web服务器编写一个留言板

5.1 简介

本章通过Python攻略介绍FTP、电子邮件和CGI通信协议。Python这门语言很高效也很友好。使用Python可以很方便地实现简单的FTP操作,例如下载和上传文件。

本章有些有趣的攻略,例如在Python脚本中管理谷歌电子邮件(也叫Gmail)。使用这些攻略可以通过IMAP、POP3和SMTP协议查收、下载和发送电子邮件。还有一个攻略编写了一个支持CGI功能的Web服务器,演示了基本的CGI操作,例如编写Web应用中的游客留言表单。

5.2 列出FTP远程服务器中的文件

你可能想列出Linux内核FTP网站(ftp.kernel.org)中的文件。这个攻略也可用在其他FTP网站上。

5.2.1 准备工作

如果处理需要账户的FTP网站,你需要提供用户名和密码。但在这个攻略中不需要Linux内核FTP网站的用户名和密码,因为可以匿名登录。

5.2.2 实战演练

要从选中的FTP网站中获取文件,可以使用ftplib库。这个库的详细文档可在http://docs.python.org/2/library/ftplib.html中查看。

我们来看一下如何使用ftplib获取文件。

代码清单5-1是一次简单的FTP连接测试,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. FTP_SERVER_URL = 'ftp.kernel.org'
  6. import ftplib
  7. def test_ftp_connection(path, username, email):
  8. #Open ftp connection
  9. ftp = ftplib.FTP(path, username, email)
  10. #List the files in the pub directory
  11. ftp.cwd("pub")
  12. print "File list at %s:" %path
  13. files = ftp.dir()
  14. print files
  15. ftp.quit()
  16. if __name__ == '__main__':
  17. test_ftp_connection(path=FTP_SERVER_URL, username='anonymous',
  18. email='nobody@nourl.com')

这个攻略会列出FTP路径(ftp.kernel.org/pub)中的文件和文件夹。运行这个脚本后,会看到如下输出:

  1. $ python 5_1_list_files_on_ftp_server.py
  2. File list at ftp.kernel.org:
  3. drwxrwxr-x 6 ftp ftp 4096 Dec 01 2011 dist
  4. drwxr-xr-x 13 ftp ftp 4096 Nov 16 2011 linux
  5. drwxrwxr-x 3 ftp ftp 4096 Sep 23 2008 media
  6. drwxr-xr-x 17 ftp ftp 4096 Jun 06 2012 scm
  7. drwxrwxr-x 2 ftp ftp 4096 Dec 01 2011 site
  8. drwxr-xr-x 13 ftp ftp 4096 Nov 27 2011 software
  9. drwxr-xr-x 3 ftp ftp 4096 Apr 30 2008 tools

5.2.3 原理分析

这个攻略使用ftplib创建了一个连接到ftp.kernel.org/pub上的FTP客户端会话。test_ftp_connection()函数的参数是连接FTP服务器所需的路径、用户名和电子邮件地址。

FTP客户端会话使用ftplib库中的FTP()函数创建,其参数就是传入test_ftp_connection()的参数。FTP()函数返回一个客户端句柄,用于运行常用的FTP命令,例如切换工作目录的命令cwd()dir()方法返回目录的文件夹结构。

处理完成后,最后调用ftp.quit()终止FTP会话。

5.3 把本地文件上传到远程FTP服务器中

你可能想把文件上传到FTP服务器中。

5.3.1 准备工作

让我们来搭建一个本地FTP服务器。在Unix/Linux中,可以使用下述命令安装wu-ftpd包:

  1. $ sudo apt-get install wu-ftpd

在运行Windows的设备中,可以安装FileZilla FTP服务器,下载地址为https://filezilla-project.org/download.php?type=server

你应该按照FTP服务器包的说明来创建一个FTP账户。

你可能还想上传一个文件到FTP服务器中。你可以指定服务器地址、登录凭据和文件名作为脚本的输入参数。你应该在本地新建一个文件,命名为readme.txt,在里面随便写些文本。

5.3.2 实战演练

我们使用下面的脚本来搭建一个FTP本地服务器。在Unix/Linux中,可以安装wu-ftpd包。然后,可以把文件上传到已登录用户的家目录中。你可以指定服务器地址、登录凭据和文件名作为脚本的输入参数。

代码清单5-2是FTP上传文件示例,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import os
  6. import argparse
  7. import ftplib
  8. import getpass
  9. LOCAL_FTP_SERVER = 'localhost'
  10. LOCAL_FILE = 'readme.txt'
  11. def ftp_upload(ftp_server, username, password, file_name):
  12. print "Connecting to FTP server: %s" %ftp_server
  13. ftp = ftplib.FTP(ftp_server)
  14. print "Login to FTP server: user=%s" %username
  15. ftp.login(username, password)
  16. ext = os.path.splitext(file_name)[1]
  17. if ext in (".txt", ".htm", ".html"):
  18. ftp.storlines("STOR " + file_name, open(file_name))
  19. else:
  20. ftp.storbinary("STOR " + file_name, open(file_name, "rb"), 1024)
  21. print "Uploaded file: %s" %file_name
  22. if __name__ == '__main__':
  23. parser = argparse.ArgumentParser(description='FTP Server Upload Example')
  24. parser.add_argument('--ftp-server', action="store", dest="ftp_server", default=LOCAL_FTP_SERVER)
  25. parser.add_argument('--filename', action="store", dest="file_name", default=LOCAL_FILE)
  26. parser.add_argument('--username', action="store", dest="username", default=getpass.getuser())
  27. given_args = parser.parse_args()
  28. ftp_server, file_name, username = given_args.ftp_server, given_args.file_name, given_args.username
  29. password = getpass.getpass(prompt="Enter you FTP password: ")
  30. ftp_upload(ftp_server, username, password, file_name)

如果搭建了本地FTP服务器,运行下面这个脚本后会登入FTP服务器并上传文件。如果命令行中没有指定默认文件名,这个脚本将上传readme.txt文件。

  1. $ python 5_2_upload_file_to_ftp_server.py
  2. Enter your FTP password:
  3. Connecting to FTP server: localhost
  4. Login to FTP server: user=faruq
  5. Uploaded file: readme.txt
  6. $ cat homefaruq/readme.txt
  7. This file describes what to do with the .bz2 files you see elsewhere
  8. on this site (ftp.kernel.org).

5.3.3 原理分析

在这个攻略中,我们假设本地FTP服务器正在运行中。除此之外还可以连接远程FTP服务器。在ftp_upload()方法中,使用Python中的ftplib模块提供的FTP()函数创建一个FTP连接对象。然后调用login()方法,登录服务器。

登录成功后,在ftp对象上调用方法storlines()storbinary()执行STOR命令。前一个方法用于发送ASCII文本文件,例如HTML或纯文本文件。后一个方法用于发送二进制数据,例如压缩文件。

这些FTP方法最好放在try-catch错误处理块中,为了行文简洁,这里并没有这么做。

5.4 把当前工作目录中的内容压缩成ZIP文件后通过电子邮件发送

如果能把当前工作目录中的内容压缩成ZIP文件发送给别人,该多么有趣啊。使用这个攻略,你可以快速和朋友共享文件。

5.4.1 准备工作

如果你的设备中没有安装任何邮件服务器,你需要安装一个本地邮件服务器,例如postfix。在Debian/Ubuntu系统中可以使用apt-get的默认设置安装,如下面的命令所示:

  1. $ sudo apt-get install postfix

5.4.2 实战演练

我们首先要压缩当前目录,然后创建一封电子邮件。电子邮件可通过外部的SMTP主机发送,也可使用本地电子邮件服务器发送。和其他的攻略类似,发件人和收件人信息都从命令行参数中获取。

代码清单5-3展示了如何把文件夹压缩成ZIP文件再通过电子邮件发送,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import os
  6. import argparse
  7. import smtplib
  8. import zipfile
  9. import tempfile
  10. from email import encoders
  11. from email.mime.base import MIMEBase
  12. from email.mime.multipart import MIMEMultipart
  13. def email_dir_zipped(sender, recipient):
  14. zf = tempfile.TemporaryFile(prefix='mail', suffix='.zip')
  15. zip = zipfile.ZipFile(zf, 'w')
  16. print "Zipping current dir: %s" %os.getcwd()
  17. for file_name in os.listdir(os.getcwd()):
  18. zip.write(file_name)
  19. zip.close()
  20. zf.seek(0)
  21. # Create the message
  22. print "Creating email message..."
  23. email_msg = MIMEMultipart()
  24. email_msg['Subject'] = 'File from path %s' %os.getcwd()
  25. email_msg['To'] = ', '.join(recipient)
  26. email_msg['From'] = sender
  27. email_msg.preamble = 'Testing email from Python.\n'
  28. msg = MIMEBase('application', 'zip')
  29. msg.set_payload(zf.read())
  30. encoders.encode_base64(msg)
  31. msg.add_header('Content-Disposition', 'attachment',
  32. filename=os.getcwd()[-1] + '.zip')
  33. email_msg.attach(msg)
  34. email_msg = email_msg.as_string()
  35. # send the message
  36. print "Sending email message..."
  37. try:
  38. smtp = smtplib.SMTP('localhost')
  39. smtp.set_debuglevel(1)
  40. smtp.sendmail(sender, recipient, email_msg)
  41. except Exception, e:
  42. print "Error: %s" %str(e)
  43. finally:
  44. smtp.close()
  45. if __name__ == '__main__':
  46. parser = argparse.ArgumentParser(description='Email Example')
  47. parser.add_argument('--sender', action="store", dest="sender", default='you@you.com')
  48. parser.add_argument('--recipient', action="store", dest="recipient")
  49. given_args = parser.parse_args()
  50. email_dir_zipped(given_args.sender, given_args.recipient)

运行这个脚本后看到的输出如下所示。因为开启了电子邮件调试模式,所以还显示了一些额外信息。

  1. $ python 5_3_email_current_dir_zipped.py --recipient=faruq@localhost
  2. Zipping current dir: homefaruq/Dropbox/PacktPub/pynetcookbook/
  3. pynetcookbook_code/chapter5
  4. Creating email message...
  5. Sending email message...
  6. send: 'ehlo [127.0.0.1]\r\n'
  7. reply: '250-debian6.debian2013.com\r\n'
  8. reply: '250-PIPELINING\r\n'
  9. reply: '250-SIZE 10240000\r\n'
  10. reply: '250-VRFY\r\n'
  11. reply: '250-ETRN\r\n'
  12. reply: '250-STARTTLS\r\n'
  13. reply: '250-ENHANCEDSTATUSCODES\r\n'
  14. reply: '250-8BITMIME\r\n'
  15. reply: '250 DSN\r\n'
  16. reply: retcode (250); Msg: debian6.debian2013.com
  17. PIPELINING
  18. SIZE 10240000
  19. VRFY
  20. ETRN
  21. STARTTLS
  22. ENHANCEDSTATUSCODES
  23. 8BITMIME
  24. DSN
  25. send: 'mail FROM:<you@you.com> size=9141\r\n'
  26. reply: '250 2.1.0 Ok\r\n'
  27. reply: retcode (250); Msg: 2.1.0 Ok
  28. send: 'rcpt TO:<faruq@localhost>\r\n'
  29. reply: '250 2.1.5 Ok\r\n'
  30. reply: retcode (250); Msg: 2.1.5 Ok
  31. send: 'data\r\n'
  32. reply: '354 End data with <CR><LF>.<CR><LF>\r\n'
  33. reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>
  34. data: (354, 'End data with <CR><LF>.<CR><LF>')
  35. send: 'Content-Type: multipart/mixed;
  36. boundary="===============0388489101==...[TRUNCATED]
  37. reply: '250 2.0.0 Ok: queued as 42D2F34A996\r\n'
  38. reply: retcode (250); Msg: 2.0.0 Ok: queued as 42D2F34A996
  39. data: (250, '2.0.0 Ok: queued as 42D2F34A996')

5.4.3 原理分析

为了通过电子邮件发送压缩后的文件夹,我们用到了Python中的zipfilesmtplibemail三个模块。这在email_dir_zipped()方法中实现。这个方法接收两个参数:发件人和收件人的电子邮件地址。

为了压缩文件,我们使用tempfile模块中的TemporaryFile类新建了一个临时文件,把这个文件的前缀设为mail,后缀设为.zip。然后把临时文件作为参数传给ZipFile类的构造方法,初始化一个ZIP压缩文件对象。接着在这个压缩对象上调用write()方法,添加当前目录中的文件。

为了发送电子邮件,我们使用email.mime.multipart模块中的MIMEmultipart()类创建了一个MIME为multipart的邮件。和普通的电子邮件一样,主题、收件人和发件人信息都在电子邮件的首部中设定。

电子邮件的附件使用MIMEBase()方法创建。我们首先设置application/zip首部,然后在附件对象上调用set_payload()方法。为了正确地编码消息,调用了encoders模块中的encode_base64()方法。add_header()方法能帮助我们设定附件的首部。至此,附件已经准备好,可以添加到邮件中了,因此我们调用了attach()方法。

若想发送电子邮件,要调用smtplib模块中的SMTP类创建一个实例。在这个实例上调用sendmail()方法后,会利用操作系统中的程序正确地把电子邮件发送出去。发送的细节被隐藏了,不过,如果你想看到详细的过程,可以打开调试模式,如前所示。

5.4.4 参考资源

5.5 通过POP3协议下载谷歌电子邮件

你可能想通过POP3协议下载谷歌(或者其他任何一个电子邮件服务提供商)账户中的电子邮件。

5.5.1 准备工作

要运行这个攻略,你需要有谷歌或其他服务提供商的电子邮件账户。

5.5.2 实战演练

我们要尝试下载用户谷歌电子邮件账户中的第一封邮件。用户名在命令行中输入,但为了保密,密码不能在命令行中指定,而是在运行脚本时输入,而且不能显示出来。

代码清单5-4展示了如何通过POP3协议下载谷歌账户中的电子邮件,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import getpass
  7. import poplib
  8. GOOGLE_POP3_SERVER = 'pop.googlemail.com'
  9. def download_email(username):
  10. mailbox = poplib.POP3_SSL(GOOGLE_POP3_SERVER, '995')
  11. mailbox.user(username)
  12. password = getpass.getpass(prompt="Enter your 谷歌 password: ")
  13. mailbox.pass_(password)
  14. num_messages = len(mailbox.list()[1])
  15. print "Total emails: %s" %num_messages
  16. print "Getting last message"
  17. for msg in mailbox.retr(num_messages)[1]:
  18. print msg
  19. mailbox.quit()
  20. if __name__ == '__main__':
  21. parser = argparse.ArgumentParser(description='Email Download Example')
  22. parser.add_argument('--username', action="store", dest="username", default=getpass.getuser())
  23. given_args = parser.parse_args()
  24. username = given_args.username
  25. download_email(username)

运行这个脚本后会看到类似下面的输出。为了保护隐私,输出的内容有所删减。

  1. $ python 5_4_download_google_email_via_pop3.py --username=<USERNAME>
  2. Enter your 谷歌 password:
  3. Total emails: 333
  4. Getting last message
  5. ...[TRUNCATED]

5.5.3 原理分析

这个攻略通过POP3协议从用户的谷歌账户中下载第一封邮件。在download_email()函数中,使用poplib模块中的POP3_SSL类创建了一个mailbox对象。在POP3_SSL类的构造方法中,我们传入了谷歌POP3服务器的地址和端口号。然后在mailbox对象上调用user()方法,设定用户的账户。密码使用getpass模块中的getpass()方法以一种安全的方式从用户的输入中获取,然后传给mailbox对象。在mailbox对象上调用list()方法会以一个Python列表的形式返回电子邮件。

这个脚本先显示电子邮件的数量,然后调用retr()方法取回第一封邮件。最后,在mailbox对象上调用quit()方法,安全地结束连接。

5.6 通过IMAP协议查收远程服务器中的电子邮件

除了使用POP3协议之外,还可以使用IMAP协议从谷歌账户中取回电子邮件。使用这种方法,取回后邮件不会被删除。

5.6.1 准备工作

要运行这个攻略,你需要有谷歌或其他服务提供商的电子邮件账户。

5.6.2 实战演练

让我们连接到谷歌的电子邮件账户,读取第一封电子邮件。如果你没有删除,第一封电子邮件应该是来自谷歌的欢迎消息。

代码清单5-5展示了如何通过IMAP协议查收谷歌账户中的电子邮件,如下所示:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import getpass
  7. import imaplib
  8. GOOGLE_IMAP_SERVER = 'imap.googlemail.com'
  9. def check_email(username):
  10. mailbox = imaplib.IMAP4_SSL(GOOGLE_IMAP_SERVER, '993')
  11. password = getpass.getpass(prompt="Enter your 谷歌 password: ")
  12. mailbox.login(username, password)
  13. mailbox.select('Inbox')
  14. typ, data = mailbox.search(None, 'ALL')
  15. for num in data[0].split():
  16. typ, data = mailbox.fetch(num, '(RFC822)')
  17. print 'Message %s\n%s\n' % (num, data[0][1])
  18. break
  19. mailbox.close()
  20. mailbox.logout()
  21. if __name__ == '__main__':
  22. parser = argparse.ArgumentParser(description='Email Download Example')
  23. parser.add_argument('--username', action="store", dest="username", default=getpass.getuser())
  24. given_args = parser.parse_args()
  25. username = given_args.username
  26. check_email(username)

运行这个脚本后会看到如下输出。为了保护隐私,删减了一些信息。

  1. $ python 5_5_check_remote_email_via_imap.py --username=<USER_NAME>
  2. Enter your 谷歌 password:
  3. Message 1
  4. Received: by 10.140.142.16; Sat, 17 Nov 2007 09:26:31 -0800 (PST)
  5. Message-ID: <...>@mail.gmail.com>
  6. Date: Sat, 17 Nov 2007 09:26:31 -0800
  7. From: "Gmail Team" <mail-noreply@google.com>
  8. To: "<User Full Name>" <USER_NAME>@gmail.com>
  9. Subject: Gmail is different. Here's what you need to know.
  10. MIME-Version: 1.0
  11. Content-Type: multipart/alternative;
  12. boundary="----=_Part_7453_30339499.1195320391988"
  13. ------=_Part_7453_30339499.1195320391988
  14. Content-Type: text/plain; charset=ISO-8859-1
  15. Content-Transfer-Encoding: 7bit
  16. Content-Disposition: inline
  17. Messages that are easy to find, an inbox that organizes itself, great
  18. spam-fighting tools and built-in chat. Sound cool? Welcome to Gmail.
  19. To get started, you may want to:
  20. [TRUNCATED]

5.6.3 原理分析

上述脚本从命令行参数中获取谷歌用户名,然后调用check_email()函数。在这个函数中,使用imaplib模块中的IMAP4_SSL类创建了一个mailbox对象,实例化时传入了谷歌的IMAP服务器地址和默认的端口号。

然后,在这个函数中使用密码登录账户。密码使用getpass模块中的getpass()方法从用户输入中捕获。然后在mailbox对象上调用select()方法,选择收件箱。

mailbox对象上可以调用很多有用的方法,其中两个是search()fetch(),在这个脚本中用来获取第一封邮件。最后,在mailbox对象上调用close()logout()方法,安全地结束IMAP连接。

5.7 通过Gmail的SMTP服务器发送带有附件的电子邮件

你也许想从谷歌的电子邮件账户中发送一封邮件到其他账户中,或许还想为这封邮件附加一个文件。

5.7.1 准备工作

要运行这个攻略,你需要有谷歌或其他服务提供商的电子邮件账户。

5.7.2 实战演练

我们可以创建一封邮件,把Python的LOGO python-logo.gif附加到邮件中,然后从谷歌账户中发给另一个账户。

代码清单5-6展示了如何从谷歌账户中发送电子邮件:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import argparse
  6. import os
  7. import getpass
  8. import re
  9. import sys
  10. import smtplib
  11. from email.mime.image import MIMEImage
  12. from email.mime.multipart import MIMEMultipart
  13. from email.mime.text import MIMEText
  14. SMTP_SERVER = 'smtp.gmail.com'
  15. SMTP_PORT = 587
  16. def send_email(sender, recipient):
  17. """ Send email message """
  18. msg = MIMEMultipart()
  19. msg['Subject'] = 'Python Emaill Test'
  20. msg['To'] = recipient
  21. msg['From'] = sender
  22. subject = 'Python email Test'
  23. message = 'Images attached.'
  24. # attach imgae files
  25. files = os.listdir(os.getcwd())
  26. gifsearch = re.compile(".gif", re.IGNORECASE)
  27. files = filter(gifsearch.search, files)
  28. for filename in files:
  29. path = os.path.join(os.getcwd(), filename)
  30. if not os.path.isfile(path):
  31. continue
  32. img = MIMEImage(open(path, 'rb').read(), subtype="gif")
  33. img.addheader('Content-Disposition', 'attachment', filename=filename)
  34. msg.attach(img)
  35. part = MIMEText('text', "plain")
  36. part.set_payload(message)
  37. msg.attach(part)
  38. # create smtp session
  39. session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
  40. session.ehlo()
  41. session.starttls()
  42. session.ehlo
  43. password = getpass.getpass(prompt="Enter your 谷歌 password: ")
  44. session.login(sender, password)
  45. session.sendmail(sender, recipient, msg.as_string())
  46. print "Email sent."
  47. session.quit()
  48. if __name__ == '__main__':
  49. parser = argparse.ArgumentParser(description='Email Sending Example')
  50. parser.add_argument('--sender', action="store", dest="sender")
  51. parser.add_argument('--recipient', action="store", dest="recipient")
  52. given_args = parser.parse_args()
  53. send_email(given_args.sender, given_args.recipient)

如果提供的谷歌账户信息正确,运行这个脚本后会看到成功消息,提示把一封邮件发送给了另一个电子邮件地址。运行这个脚本之后,可以到收件人的电子邮件账户中确认是否真的成功发送了邮件。

  1. $ python 5_6_send_email_from_gmail.py --sender=<USERNAME>@gmail.com
  2. recipient=<USER>@<ANOTHER_COMPANY.com>
  3. Enter you Google password:
  4. Email sent.

5.7.3 原理分析

在这个攻略的send_email()函数中创建了一封邮件。我们为send_email()函数提供了谷歌账户,邮件从这个账户中发出。邮件头对象msg使用MIMEMultipart()方法创建,然后添加了主题、收件人和发件人。

在当前路径中寻找.gif格式图片时用到了Python中的正则表达式处理模块。图片附件对象imgemail.mime.image模块中的MIMEImage()方法创建。然后为图片对象设定了正确的首部,最后再把图片附加到前面创建的msg对象上。如这个攻略所示,我们可以在for循环中附加多个图片。使用类似的方式,还可以添加纯文本附件。

为了发送电子邮件,我们创建了一个SMTP会话,并在这个会话对象上调用了测试方法,例如ehlo()starttls()。然后使用用户名和密码登录谷歌的SMTP服务器,再调用sendmail()方法发送电子邮件。

5.8 使用CGI为基于Python的Web服务器编写一个留言板

通用网关接口(Common Gateway Interface,简称CGI)是Web编程的一种标准。通过CGI,可以使用脚本生成服务器输出。你可能想获取用户在浏览器中输入的表单数据,重定向到另一个页面,确认收到了用户执行的操作。

5.8.1 实战演练

我们首先要运行一个支持CGI脚本的Web服务器。我们把用Python编写的CGI脚本放在cgi-bin/子目录中,然后访问包含反馈表单的HTML页面。提交表单后,Web服务器把表单数据发送给CGI脚本,我们会看到这个脚本生成的输出。

代码清单5-7展示了如何让使用Python编写的Web服务器支持CGI:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program is optimized for Python 2.7.
  4. # It may run on any other version with/without modifications.
  5. import os
  6. import cgi
  7. import argparse
  8. import BaseHTTPServer
  9. import CGIHTTPServer
  10. import cgitb
  11. cgitb.enable() ## enable CGI error reporting
  12. def web_server(port):
  13. server = BaseHTTPServer.HTTPServer
  14. handler = CGIHTTPServer.CGIHTTPRequestHandler #RequestsHandler
  15. server_address = ("", port)
  16. handler.cgi_directories = ["/cgi-bin", ]
  17. httpd = server(server_address, handler)
  18. print "Starting web server with CGI support on port: %s ..." %port
  19. httpd.serve_forever()
  20. if __name__ == '__main__':
  21. parser = argparse.ArgumentParser(description='CGI Server Example')
  22. parser.add_argument('--port', action="store", dest="port", type=int, required=True)
  23. given_args = parser.parse_args()
  24. web_server(given_args.port)

下面的截图展示了启用CGI的Web服务器正在伺服内容:

图5-1

运行这个脚本后,会看到如下输出:

  1. $ python 5_7_cgi_server.py --port=8800
  2. Starting web server with CGI support on port: 8800 ...
  3. localhost - - [19/May/2013 18:40:22] "GET HTTP1.1" 200 -

现在,你要在浏览器中访问http://localhost:8800/5_7_send_feedback.html

你会看到一个输入表单。假设你在表单中填写了下面的内容:

  1. Name: User1
  2. Comment: Comment1

下面的截图展示了在网页表单中输入用户评论的过程:

图5-2

然后浏览器会被重定向到http://localhost:8800cgi-bin5_7getfeedback.py,在这个页面中你会看到如下输出:

  1. User1 sends a comment: Comment1

用户的评论会显示在浏览器中:

图5-3

5.8.2 原理分析

我们创建了一个简单的HTTP服务器,以支持处理CGI请求。Python在模块BaseHTTPServerCGIHTTPserver中提供了这些接口。

我们配置了处理程序,使用路径/cgi-bin存放CGI脚本。其他路径不能运行CGI脚本。

文件5_7_send_feedback.html中的HTML反馈表单显示了一个很简单的HTML表单,包含以下代码:

  1. <html>
  2. <body>
  3. <form action="cgi-bin5_7getfeedback.py" method="post">
  4. Name: <input type="text" name="Name"> <br >
  5. Comment: <input type="text" name="Comment" >
  6. <input type="submit" value="Submit" >
  7. <form>
  8. </body>
  9. </html>

注意,这个表单的方法是POSTaction属性被设为了cgi-bin5_7getfeedback.py文件。这个文件的内容如下:

  1. #!usrbin/env python
  2. # Python Network Programming Cookbook -- Chapter - 5
  3. # This program requires Python 2.7 or any later version
  4. # Import modules for CGI handling
  5. import cgi
  6. import cgitb
  7. # Create instance of FieldStorage
  8. form = cgi.FieldStorage()
  9. # Get data from fields
  10. name = form.getvalue('Name')
  11. comment = form.getvalue('Comment')
  12. print "Content-type:text/html\r\n\r\n"
  13. print "<html>"
  14. print "<head>"
  15. print "<title>CGI Program Example </title>"
  16. print "</head>"
  17. print "<body>"
  18. print "<h2> %s sends a comment: %s</h2>" % (name, comment)
  19. print "</body>"
  20. print "</html>"

在这个CGI脚本中,调用cgilib模块中的FieldStorage()方法,得到一个用于处理HTML表单输入的表单对象。然后使用getvalue()方法处理两个输入(namecomment)。最后,这个脚本显示一行文字,提示某个用户发送了一篇评论,作为用户输入的反馈。