12. Python 网络服务
![]() |
Python脚本可由服务器WEB执行。该服务器将负责监听客户端请求。从客户端的角度来看,调用服务WEB等同于请求该服务的URL。 客户端可以使用任何语言编写,特别是 Python。我们需要学会与 WEB 服务“对话”,即理解 Web 服务器与其客户端之间的 Http 通信协议。这就是后续程序的目标。
Web服务脚本将由WampServer的Apache Web服务器执行。需将其放置在特定目录中: <WampServer>\bin\apache\apachex.y.z\cgi-bin,其中 <WampServer> 是 WampServer 的安装目录,x.y.z 是 Apache Web 服务器的版本号。
![]() |
仅将 Python 脚本放置在 <cgi-bin> 文件夹中是不够的。脚本的第一行必须指定要使用的 Python 解释器的路径。该路径以注释形式写入:
读者应根据自身环境调整此路径。
12.1. 日期/时间客户端/服务器应用程序
我们的第一个Web服务将是一个日期和时间服务:客户端将接收当前的日期和时间。
12.1.1. 服务器
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import time
# 头信息
print "Content-Type: text/plain\n"
# 向客户端发送时间
# 本地时间:自 1970 年 1 月 1 日以来的毫秒数
# “日期和时间显示格式
# d:2位数的日期
# m:2位数的月份
# y:2位数的年份
# H:小时 0,23
# M:分钟
# S:秒
print time.strftime('%d/%m/%y %H:%M:%S',time.localtime())
注:
- 第 6 行:脚本必须自行生成部分发给客户端的 HTTP 响应头。这些头部将附加在 Apache 服务器自身生成的 HTTP 头部之后。第 6 行的 HTTP 头部告知客户端,我们将向其发送格式为 text/plain 的资源,即未格式化的文本。 请注意头部末尾的“\n”,它将在头部之后生成一个空行。这是必需的:正是这一空行向HTTP客户端标记响应HTTP头部的结束。随后是客户端请求的资源,此处为未格式化的文本;
- 第 18 行:发送给客户端的资源是一段显示当前日期和时间的文本。
12.1.2. 两个测试
如前所述,上述脚本可直接在命令行窗口中通过 Python 解释器运行。这有助于排查可能的语法或运行错误。结果如下:
在完成上述测试后,可将脚本放置于 Apache 服务器的 <cgi-bin> 目录中(参见第 12 节)。现在启动 WampServer 应用程序。这将同时启动 Apache 网页服务器和 MySQL 数据库管理系统。 目前我们仅使用Web服务器。随后,通过浏览器访问以下URL页面:http://localhost/cgi-bin/web_02.py:
![]() |
- 在 [1] 中:请求的 URL;
- 在 [2] 中:浏览器显示的响应;
- 在 [3] 中:Web 浏览器接收到的源代码。这确实是 Python 脚本发送的内容。
借助某些工具(此处为Firebug,一款Firefox浏览器插件),我们可以查看与服务器交换的HTTP头部信息。上文所示,网页浏览器接收到了以下HTTP头部:
第7-8行是Python脚本发送的HTTP头,前面的几行则由Apache Web服务器生成。
12.1.3. 一个编程实现的客户端
现在编写一个脚本,作为前述Web服务的客户端。利用httplib模块的功能,该模块简化了HTTP客户端的编写。
# -*- coding=utf-8 -*-
import httplib,re
# 常量
HOST="localhost"
URL="/cgi-bin/web_02.py"
# 连接
connexion=httplib.HTTPConnection(HOST)
# 跟踪
connexion.set_debuglevel(1)
# 发送请求
connexion.request("GET", URL)
# 处理响应
reponse=connexion.getresponse()
# 内容
contenu=reponse.read()
# 关闭连接
connexion.close()
print "------\n",contenu,"-----\n"
# 获取时间相关元素
elements=re.match(r"^(\d\d)/(\d\d)/(\d\d) (\d\d):(\d\d):(\d\d)\s*$",contenu).groups()
print "Jour=%s,Mois=%s,An=%s,Heures=%s,Minutes=%s,Secondes=%s" % (elements[0],elements[1],elements[2],elements[3],elements[4],elements[5])
注:
- 第 3 行:re 模块用于正则表达式,httplib 模块用于 HTTP 客户端功能;
- 第 9 行:通过第 6 行定义的 HOST 的 80 端口建立 Http 连接;
- 第11行:跟踪功能可查看客户端请求和服务器响应的HTTP头信息;
- 第13行:调用Web服务中的URL。 调用该服务有两种方式:使用 Http 命令 GET 或 POST。两者的区别将在后文说明。此处将使用 Http 命令 GET 进行调用;
- 第15行:读取服务器的响应。此处获取的是完整的响应内容:包括HTTP头部和客户端请求的资源。在响应中,服务器可能要求客户端进行重定向。在此情况下,客户端httplib会自动执行重定向。因此,最终获得的响应是重定向后的结果;
- 第17行:响应由HTTP头和客户端请求的文档组成。若仅需获取HTTP头,可使用[reponse].getHeaders()。若需获取文档,则使用[reponse].read();
- 第19行:获取到Web服务器的响应后,关闭与服务器的连接;
- 已知服务器发送的文档是一行格式为 15/06/11 14:56:36 的文本。第 22-26 行,使用正则表达式提取该行中的各个元素。
12.1.4. 结果
注:
- 第1行:客户端发送给Web服务器的HTTP头;
- 第2-6行:Web服务器响应中的HTTP头部;
- 第 8 行:服务器发送的文档;
- 第11行:对其处理的结果;
12.2. 服务器获取客户端发送的参数
在HTTP协议中,客户端有两种方法向Web服务器传递参数:
- 它以
GET url?param1=val1¶m2=val2¶m3=val3… HTTP/1.0
其中 vali 中的值必须事先经过编码,以便将某些保留字符替换为相应的十六进制值。
- 以以下形式请求服务的 URL:
随后在发送至服务器的HTTP头部中加入以下头部:
客户端发送的后续头部信息以空行结尾。随后,它可以将数据以以下形式发送:
其中 vali 必须像 GET 方法那样预先进行编码。发送给服务器的字符数必须为 N,其中 N 是标头中声明的值:
12.2.1. Web服务
以下Web服务从客户端接收3个参数:nom、prenom、age。 它将这些参数存储在一个名为 cgi.FieldStorage 的字典中,该字典由模块 cgi 提供。 参数 parami 的值 vali 是通过 vali=cgi.FieldStorage().getlist("parami") 获取的。如果客户端请求中不包含参数 parami,则会得到一个包含:
- 如果客户端请求中不存在参数 parami,则为 0 个元素;
- 若客户端请求中包含 1 次参数 parami,则返回 1 个元素;
- 如果客户端请求中包含 parami 参数 n 次,则返回 n 个元素。
获取参数后,脚本将它们返回给客户端。
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import cgi
# 头部信息
print "Content-Type: text/plain\n"
# 服务器接收客户端发送的信息
# 此处名字=P&姓氏=N&年龄=A
formulaire=cgi.FieldStorage()
# 将其发回给客户端
print "informations recues du service web [prenom=%s,nom=%s,age=%s]" % (formulaire.getlist("prenom"),formulaire.getlist("nom"),formulaire.getlist("age"))
可通过网页浏览器进行测试:
![]() |
在 [1] 中,是 Web 服务的 URL。请注意存在三个参数:nom、prenom、age。 在 [2] 中,这是 Web 服务的响应。
12.2.2. 客户端 GET
# -*- coding=utf-8 -*-
import httplib,urllib
# 常量
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42
# 参数在发送至服务器前必须进行编码
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# 参数被放置在URL的末尾
URL+="?"+params
# 连接
connexion=httplib.HTTPConnection(HOST)
# 跟踪
connexion.set_debuglevel(1)
# 发送请求
connexion.request("GET",URL)
# 处理响应
reponse=connexion.getresponse()
# 内容
contenu=reponse.read()
print contenu,"\n"
# 关闭连接
connexion.close()
注:
- 第 8-10 行:发送给 Web 服务的 3 个参数的值;
- 第13行:需对参数进行编码。此操作通过模块urllib中的方法urlencode实现。该模块在第3行被导入。该方法接受一个字典参数 {param1:val1, param2:val2, ...};
- 第 15 行:在 GET 命令(第 21 行)中,客户端需将编码后的参数放置在 Web 服务 URL 的末尾;
- 后续行已在前文提及。
12.2.3. 结果
注:
- 第 2 行:注意参数(姓、名、年龄)的编码;
- 第 8 行:Web 服务的响应。
12.2.4. 客户端 POST
客户端 POST 与客户端 GET 类似,只是编码后的参数不再属于目标 URL。 这些参数作为 POST 请求的第三个参数传递(第 19 行)。
# -*- coding=utf-8 -*-
import httplib,urllib
# 常量
HOST="localhost"
URL="/cgi-bin/web_03.py"
PRENOM="Jean-Paul"
NOM="de la Huche"
AGE=42
# 参数在发送至服务器前必须进行编码
params = urllib.urlencode({'nom': NOM, 'prenom': PRENOM, 'age': AGE})
# 连接
connexion=httplib.HTTPConnection(HOST)
# 跟踪
connexion.set_debuglevel(1)
# 发送请求
connexion.request("POST",URL,params)
# 处理响应
reponse=connexion.getresponse()
# 内容
contenu=reponse.read()
print contenu,"\n"
# 关闭连接
connexion.close()
12.2.5. 结果
注:
- 请注意第 2 行,客户端 POST 用于发送编码参数的方法:
- HTTP头Content-Length指明了将发送给Web服务的字符数;
- 该HTTP头之后紧跟一行空行,表示HTTP头结束;
- 随后发送了39个字符的编码参数。
- 第 8 行:Web 服务的响应。
12.3. 从 Web 服务中获取环境变量
12.3.1. Web服务
Python CGI脚本在具有属性的系统环境中运行。其属性及其值可在字典os.environ中获取。
#!D:\Programs\ActivePython\Python2.7.2\python.exe
import os
# 头信息
print "Content-Type: text/plain\n"
# 环境信息
for (cle,valeur) in os.environ.items():
print "%s : %s" % (cle,valeur)
注:
- 第 3 行:必须导入 os 模块才能使用“系统”变量。
如果直接运行上述脚本(c.a.d,作为控制台脚本而非 CGI 脚本),控制台将显示以下结果:
在网页浏览器中(此时执行的是 CGI 脚本),将得到以下结果:
![]() |
需要注意的是,根据执行环境的不同,生成的环境变量并不相同。
12.3.2. 预设客户端
# -*- coding=utf-8 -*-
import httplib
# 常量
HOST="localhost"
URL="/cgi-bin/web_04.py"
# 连接
connexion=httplib.HTTPConnection(HOST)
# 发送请求
connexion.request("GET", URL)
# 响应处理
reponse=connexion.getresponse()
# 内容
print reponse.read()
12.3.3. 结果
需要注意的是,编程客户端收到的响应与网页浏览器并不完全相同。这是因为浏览器向Web服务器发送了信息,而Web服务器利用这些信息生成响应。在此,编程客户端并未发送任何关于自身的信息。




