Skip to content

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 解释器的路径。该路径以注释形式写入:

#!D:\Programs\ActivePython\Python2.7.2\python.exe

读者应根据自身环境调整此路径。

12.1. 日期/时间客户端/服务器应用程序

我们的第一个Web服务将是一个日期和时间服务:客户端将接收当前的日期和时间。

12.1.1. 服务器


该程序(web_02)


#!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 解释器运行。这有助于排查可能的语法或运行错误。结果如下:

1
2
3
4
cmd>%python% web_02.py
Content-Type: text/plain

24/06/11 11:16:55

在完成上述测试后,可将脚本放置于 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头部:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
Date: Fri, 24 Jun 2011 09:35:02 GMT
Server: Apache/2.2.6 (Win32) PHP/5.2.5
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

第7-8行是Python脚本发送的HTTP头,前面的几行则由Apache Web服务器生成。

12.1.3. 一个编程实现的客户端


程序(client_web_02)

现在编写一个脚本,作为前述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. 结果

send: 'GET /cgi-bin/web_02.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Tue, 14 Feb 2012 14:51:07 GMT
header: Server: Apache/2.2.17 (Win32) PHP/5.3.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
------
14/02/12 15:51:07
-----

Jour=14,Mois=02,An=12,Heures=15,Minutes=51,Secondes=07

注:

  • 第1行:客户端发送给Web服务器的HTTP头;
  • 第2-6行:Web服务器响应中的HTTP头部;
  • 第 8 行:服务器发送的文档;
  • 第11行:对其处理的结果;

12.2. 服务器获取客户端发送的参数

在HTTP协议中,客户端有两种方法向Web服务器传递参数:

  1. 它以

GET url?param1=val1&param2=val2&param3=val3… HTTP/1.0

其中 vali 中的值必须事先经过编码,以便将某些保留字符替换为相应的十六进制值。

  1. 以以下形式请求服务的 URL:
POST url HTTP/1.0

随后在发送至服务器的HTTP头部中加入以下头部:

Content-length: N

客户端发送的后续头部信息以空行结尾。随后,它可以将数据以以下形式发送:

val1&param2=val2&param3=val3…

其中 vali 必须像 GET 方法那样预先进行编码。发送给服务器的字符数必须为 N,其中 N 是标头中声明的值:

Content-length: N

12.2.1. Web服务

以下Web服务从客户端接收3个参数:nomprenomage。 它将这些参数存储在一个名为 cgi.FieldStorage 的字典中,该字典由模块 cgi 提供。 参数 parami 的值 vali 是通过 vali=cgi.FieldStorage().getlist("parami") 获取的。如果客户端请求中不包含参数 parami,则会得到一个包含:

  • 如果客户端请求中不存在参数 parami,则为 0 个元素;
  • 若客户端请求中包含 1 次参数 parami,则返回 1 个元素;
  • 如果客户端请求中包含 parami 参数 n 次,则返回 n 个元素。

获取参数后,脚本将它们返回给客户端。


程序 (web_03)


#!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。请注意存在三个参数:nomprenomage。 在 [2] 中,这是 Web 服务的响应。

12.2.2. 客户端 GET


程序 (client_web_03_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. 结果

1
2
3
4
5
6
7
8
dos>%python% client_03_GET.py
send: 'GET /cgi-bin/web_03.py?nom=de+la+Huche&age=42&prenom=Jean-Paul HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding: identity\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Wed, 15 Jun 2011 13:22:15 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du client [['Jean-Paul'],['de la Huche'],['42']]

注:

  • 第 2 行:注意参数(姓、名、年龄)的编码;
  • 第 8 行:Web 服务的响应。

12.2.4. 客户端 POST

客户端 POST 与客户端 GET 类似,只是编码后的参数不再属于目标 URL。 这些参数作为 POST 请求的第三个参数传递(第 19 行)。


该程序(client_web_03_POST)


# -*- 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. 结果

1
2
3
4
5
6
7
8
dos>%python% client_03_POST.py
send: 'POST /cgi-bin/web_03.py HTTP/1.1\r\nHost: localhost\r\nAccept-Encoding:identity\r\nContent-Length: 39\r\n\r\nnom=de+la+Huche&age=42&prenom=Jean-Paul'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Fri, 24 Jun 2011 12:03:31 GMT
header: Server: Apache/2.2.6 (Win32) PHP/5.2.5
header: Transfer-Encoding: chunked
header: Content-Type: text/plain
informations recues du service web [prenom=['Jean-Paul'],nom=['de la Huche'],age=['42']]

注:

  • 请注意第 2 行,客户端 POST 用于发送编码参数的方法:
    • HTTP头Content-Length指明了将发送给Web服务的字符数;
    • 该HTTP头之后紧跟一行空行,表示HTTP头结束;
    • 随后发送了39个字符的编码参数。
  • 第 8 行:Web 服务的响应。

12.3. 从 Web 服务中获取环境变量

12.3.1. Web服务

Python CGI脚本在具有属性的系统环境中运行。其属性及其值可在字典os.environ中获取。


程序 (web_04)


#!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 脚本),控制台将显示以下结果:

Content-Type: text/plain

TMP : C:\Users\SERGET~1\AppData\Local\Temp
COMPUTERNAME : GPORTPERS3
USERDOMAIN : Gportpers3
VS100COMNTOOLS : D:\Programs\dotnet\Visual Studio 10\Common7\Tools\
VISUALSTUDIODIR : D:\Documents\Visual Studio 2010
PSMODULEPATH : C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
COMMONPROGRAMFILES : C:\Program Files (x86)\Common Files
PROCESSOR_IDENTIFIER : Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
PROGRAMFILES : C:\Program Files (x86)
PROCESSOR_REVISION : 2a07
SYSTEMROOT : C:\Windows
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
PROGRAMFILES(X86) : C:\Program Files (x86)
WINDOWS_TRACING_FLAGS : 3
TEMP : C:\Users\SERGET~1\AppData\Local\Temp
COMMONPROGRAMFILES(X86) : C:\Program Files (x86)\Common Files
PROCESSOR_ARCHITECTURE : x86
ALLUSERSPROFILE : C:\ProgramData
LOCALAPPDATA : C:\Users\Serge TahÚ\AppData\Local
HOMEPATH : \Users\Serge TahÚ
PROGRAMW6432 : C:\Program Files
USERNAME : Serge TahÚ
LOGONSERVER : \\GPORTPERS3
PROMPT : $P$G
SESSIONNAME : Console
PROGRAMDATA : C:\ProgramData
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
FP_NO_HOST_CHECK : NO
WINDIR : C:\Windows
PYTHON : D:\Programs\python\python2.7.2\python
WINDOWS_TRACING_LOGFILE : C:\BVTBin\Tests\installpackage\csilogfile.log
HOMEDRIVE : C:
SYSTEMDRIVE : C:
COMSPEC : C:\Windows\system32\cmd.exe
NUMBER_OF_PROCESSORS : 8
VBOX_INSTALL_PATH : D:\Programs\systeme\Oracle\VirtualBox\
APPDATA : C:\Users\Serge TahÚ\AppData\Roaming
PROCESSOR_LEVEL : 6
PROCESSOR_ARCHITEW6432 : AMD64
COMMONPROGRAMW6432 : C:\Program Files\Common Files
OS : Windows_NT
PUBLIC : C:\Users\Public
USERPROFILE : C:\Users\Serge TahÚ

在网页浏览器中(此时执行的是 CGI 脚本),将得到以下结果:

 

需要注意的是,根据执行环境的不同,生成的环境变量并不相同。

12.3.2. 预设客户端


程序(client_web_04)


# -*- 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. 结果

SERVER_SOFTWARE : Apache/2.2.17 (Win32) PHP/5.3.5
SCRIPT_NAME : /cgi-bin/web_04.py
SERVER_SIGNATURE :
REQUEST_METHOD : GET
SERVER_PROTOCOL : HTTP/1.1
QUERY_STRING :
SYSTEMROOT : C:\Windows
SERVER_NAME : localhost
REMOTE_ADDR : 127.0.0.1
SERVER_PORT : 80
SERVER_ADDR : 127.0.0.1
DOCUMENT_ROOT : D:/Programs/sgbd/wamp/www/
COMSPEC : C:\Windows\system32\cmd.exe
SCRIPT_FILENAME : D:/Programs/sgbd/wamp/bin/apache/Apache2.2.17/cgi-bin/web_04.py
SERVER_ADMIN : admin@localhost
PATH : D:\Programs\ActivePython\Python2.7.2\;D:\Programs\ActivePython\Python2.7.
2\Scripts;C:\Program Files\Common Files\Microsoft Shared\Windows Live;...
HTTP_HOST : localhost
PATHEXT : .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.py;.pyw
REQUEST_URI : /cgi-bin/web_04.py
WINDIR : C:\Windows
GATEWAY_INTERFACE : CGI/1.1
REMOTE_PORT : 58468
HTTP_ACCEPT_ENCODING : identity

需要注意的是,编程客户端收到的响应与网页浏览器并不完全相同。这是因为浏览器向Web服务器发送了信息,而Web服务器利用这些信息生成响应。在此,编程客户端并未发送任何关于自身的信息。