这个项目主要用的是的CGI进行远程编辑——在另一台机器上通过Web来编辑 文档。你在一台机器上存储了一个文档,希望能够在另一台机器上通过Web来编辑它。这让多个用 户能够协作编辑一个文档,且无需使用FTP或类似的文件传输技术,也无需操心同步多个副本的 问题。要编辑文件,只要有Web浏览器就行。
这个简单的程序的逻辑大概如下:
- 获取CGI参数text(默认为数据文件的当前内容)
- 将text的值保存到数据文件中
- 打印表单,其中的文本区域包含text的值
要让脚本能够写入数据文件,必须先创建这样的文件(如simple_edit.dat)。这个文件可以为 空,也可包含初始文档(纯文本文件,其中可能包含一些标记,如XML或HTML)。
运行之前,首先小编先介绍一下如何在Tomcat中运行Python脚本:
① 修改Tomcat的配置文件:web.xml
<servlet>
<servlet-name>cgi</servlet-name>
<servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>cgiPathPrefix</param-name>
<param-value>WEB-INF/cgi</param-value>
</init-param>
<init-param>
<param-name>executable</param-name>
<param-value>C:\Users\Administrator\AppData\Local\Programs\Python\Python36-32\python.exe</param-value>
</init-param>
<init-param>
<param-name>passShellEnvironment</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>5</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cgi</servlet-name>
<url-pattern>/cgi-bin/*</url-pattern>
</servlet-mapping>
#passShellEnvironment: 与Python解析器解析CGI脚本有关,但是一定要配置好Python的环境变量;
#cgiPathPrefix: 配置访问的脚本目录
#executable: (本地python的安装路径)配置Python的解析器
② 修改context.xml
在标签中加入:
由于上面web.xml是这样配置的:
所以在Tomcat的webapps中创建一个cgitest,然后在cgitest中创建一个WEB-INF,然后在WEB-INF中创建一个cgi文件夹,将编写的Python脚本放入cgi文件夹中:
脚本:test.py
最终路径:webapps/cgitest/WEB-INF/cgi/test.py
然后访问时:http://localhost:8080/cgitest/cgi-bin/test.py
注意这里的cgi-bin是:
③ 重启Tomcat
#脚本:
#C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
import cgi
form = cgi.FieldStorage()
text = form.getvalue('text', open('simple_edit.dat').read())
f = open('simple_edit.dat', 'w')
f.write(text)
f.close()
print("Content-type: text/html\n\n")
print("""
<html>
<head>
<title>A Simple Editor</title>
</head>
<body>
<form action='simple_edit.py' method='POST'>
<textarea rows='10' cols='20' name='text'>{}</textarea><br />
<input type='submit' />
</form>
</body>
</html>
""".format(text))
效果:
当在输入框中编辑然后提交后,内容会更新到simple_edit.dat中。
###(4) 再次实现
至此,第一个原型已编写好,它还缺什么呢?应让用户能够编辑多个文件,并使用密码保护 这些文件。相比于第一个原型,再次实现的主要不同在于,你将把它分成两个CGI脚本,分别对应于系 统支持的两种操作。新的原型包含如下文件:
#C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
print('Content-type: text/html\n\n')
import cgi, sys
form = cgi.FieldStorage()
print('''
<html>
<head>
<title>File Editor</title>
</head>
<body>
<form action='edit.py' method='POST'>
<b>File name:</b><br/>
<input type='text' name='filename'/>
<input type='submit' value='Open'/>
</form>
</body>
</html>
''')
文本框名为filename,这确保其内容将通过CGI参数filename提供给脚本edit.cgi,如果在文本框中输入文件名,再 单击Open按钮,将运行脚本edit.cgi。
② 编写编辑器脚本
脚本edit.cgi显示的页面应包含一个文本区域和一个文本框,其中前者包含当前编辑的文件的 内容,而后者用于输入密码。这个脚本需要的唯一输入是文件名,它是从index.html中的表单中获得的。然而,可在不提交index.html中表单的情况下直接运行脚本edit.py。在这种情况下, cgi.FieldStorage的字段将是未设置的。因此,你需要检查是否获得了文件名;如果获得了,就 打开指定目录中的这个文件。我们将这个目录命名为data(当然,你必须创建这个目录)。
③ 编写保存脚本
这个简单系统的最后一部分是执行保存的脚本。它接收文件名、密码和一些文本,并检查密 码是否正确;如果正确,就将这些文本存储到指定的文件中。我们将使用模块sha来处理密码。
本项目实现的是基于Web的论坛,虽然其功能与复杂的社交媒体平台相距甚远,但提供了评论系统的基本功能。
在这个项目中,你将创建一个通过Web发布和回复消息的简单系统,它可作为论坛使用。这 个系统非常简单,但提供了基本的功能,并能够处理大量的帖子。
本章介绍的技术不仅可用于开发独立论坛,还可用于实现更通用的协作系统、问题跟踪系统、 带评论功能的博客等。通过将支持在消息下方以缩放的方式显示回复CGI(或类似的技术)和可靠的数据库(这里是SQL数据库)结合 起来使用,可实现非常强大的功能,而且用途非常广泛。
最终这个项目要实现的功能:
-- 创建MySQL数据库
CREATE TABLE messages (
id INT NOT NULL AUTO_INCREMENT,
subject VARCHAR(100) NOT NULL,
sender VARCHAR(15) NOT NULL,
reply_to INT,
text MEDIUMTEXT NOT NULL, PRIMARY KEY(id)
);
字段介绍:
#连接数据库脚本:
import pymysql
def create_table(cursor,table_name,create_sql):
# 使用 execute() 方法执行 SQL,如果表存在则删除
drop_table = 'drop table if EXISTS '+table_name
cursor.execute(drop_table)
# 建表
cursor.execute(sql)
def insert(cursor,table_name):
reply_to = input('Reply to: ')
subject = input('Subject: ')
sender = input('Sender: ')
text = input('Text: ')
sql='insert into {}(reply_to, sender, subject, text) values({},"{}","{}","{}")'\
.format(table_name,reply_to, sender, subject, text)
cursor.execute(sql)
if __name__=='__main__':
server_host="localhost"
user="xxxx"
passwd= "xxxx"
db_name="test"
# 打开数据库连接
db = pymysql.connect(server_host, user,passwd,db_name)
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()
#建表
sql = '''
CREATE TABLE messages (
id INT NOT NULL AUTO_INCREMENT,
subject VARCHAR(100) NOT NULL,
sender VARCHAR(15) NOT NULL,
reply_to INT,
text MEDIUMTEXT NOT NULL, PRIMARY KEY(id)
)
'''
create_table(cursor,"messages",sql)
#插入数据
insert(cursor,db_name+".messages")
db.commit()
# 关闭连接
cursor.close()
#cgi脚本:
'''
公告板主页
'''
print('Content-type: text/html\n')
import cgitb;
cgitb.enable()
import pymysql
server_host = "localhost"
user = "root"
passwd = "123456"
db_name = "test"
# 打开数据库连接
db = pymysql.connect(server_host, user, passwd, db_name)
curs = db.cursor()
print("""
<html>
<head>
<title>The FooBar Bulletin Board</title>
</head>
<body>
<h2>The FooBar Bulletin Board</h2>
""")
toplevel = []
children = {}
curs.execute('SELECT * FROM test.messages')
rows = curs.fetchall()
for row in rows:
parent_id = row[3]
if parent_id is None:
toplevel.append(row)
else:
children.setdefault(parent_id, []).append(row)
def format(row):
print(row[1]+'</br>')
try:
kids = children[row[0]]
except KeyError:
pass
else:
print('<blockquote>')
for kid in kids:
format(kid)
print('</blockquote>')
print('<p>')
for row in toplevel:
format(row)
print("""
</p>
</body>
</html>
""")
初次实现的功能很有限,用户甚至不能发布消息。这里我们将内容丰富一些:
本项目是一个简单的文件共享应用程序。我们将使用的主要技术是XML-RPC,这是一种远程调用过程(函数)的协议, 这种调用可能是通过网络进行的。如果你愿意,可使用普通的套接字编程轻松地实现这个项目的功能。
我们要创建一个P2P(peer-to-peer)文件共享程序。大致而言,文件共享意味着在运行于不 同计算机上的程序之间交换文件。在P2P交互中,任何对等体(peer)都可连接到其他对等体。在 这样一个由对等体组成的网络中,不存在中央权威,这让网络更健壮,因为除非你关闭大部分对等体,否则这样的网络不可能崩溃。
项目满足条件:
这个RPC简单的说就是远程调用服务端的方法,接下来我们简单是实现以下,看看效果:
#Server:
'''
对下面案例的解释:
方法register_instance注册一个实现了其“远程方法”的实例,也
可使用方法register_function注册各个函数。为运行服务器做好准备(让它能够响应来自外部的
请求)后,调用其方法serve_forever ,方法serve_forever解释器看起来就像“挂起”了一样,但实际上它是在等待RPC请求。
'''
from xmlrpc.server import SimpleXMLRPCServer
s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242
def twice(x):
return x*2
s.register_function(twice) # 给服务器添加功能,也就是客户端调用的函数
s.serve_forever() # 启动服务器
#Server:
'''
对下面案例的解释:
方法register_instance注册一个实现了其“远程方法”的实例,也
可使用方法register_function注册各个函数。为运行服务器做好准备(让它能够响应来自外部的
请求)后,调用其方法serve_forever ,方法serve_forever解释器看起来就像“挂起”了一样,但实际上它是在等待RPC请求。
'''
from xmlrpc.server import SimpleXMLRPCServer
s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242
def twice(x):
return x*2
s.register_function(twice) # 给服务器添加功能,也就是客户端调用的函数
s.serve_forever() # 启动服务器
先运行server在运行client,我们发现:
在server中出现:
而client成功调用了sever的方法,并输出了结果:4。
是不是挺简单的,好像URL请求一样,接下来我们就要根据上面的需求实现相应的功能了:
① 定义node
回顾需求,我们关心的主要有两点:Node 必须存储哪些信息(属性);Node必须能够执行哪些操作(方法)。
所有node需要有以下属性:
- 目录名:让Node知道到哪里去查找文件或将文件存储到哪里。
-密码:供其他节点用来将自己标识为可信任方。
- 一组已知的对等体(URL)。
- URL:可能加入到查询历史记录中或提供给其他节点
Node能执行什么操作呢,必须定义一些方法:
- 用于查询的方法(query)
- 获取和存储文件的方法(fetch)
- 向其他节点介绍自己的方法(hello)
② fetch方法解读
这个方法必须接受参数query和 secret,其中secret是必不可少的, 可避免节点被其他节点随便操纵。调用fetch将导致节 点下载一个文件。如果提供的密码不同于(启动时指定的)self.secret,fetch将直接返回FAIL; 否则它将调 用query来获取指定的文件。调用query时,你希望能够知道查询是否 成功,并在成功时返回指定文件的内容。因此,我们将query的返回值定义为元组(code, data), 其中code的可能取值为OK和FAIL,而data是一个字符串。如果code为OK,这个字符串将包含找到 的文件的内容;否则为一个随意的值,如空字符串。如果查询成功,并且返回OK,则开始创建一个文件并开始向其中写入内容。
③ query方法解读
query的结构:query→self._handle→_broadcast
首先调用_handle方法,它负责查询的内容处理(检查节点是否包含指定的文件,获取数据 等),它像query一样返回一个编码和一些数据。如果code为OK,则直接返回,如果为FAIL,就需要其他节点的帮助因此它需要将self.url 添加到history中,然后通过history向方法_broadcast向所有已知的对等体广播查询,它迭代self.known的副本,如果当前对等体包含在history中, 就使用continue语句跳到下一个对等体,否则创建一个ServerProxy对象,并对其调用方法query。 如果方法query成功,就将其返回值作为_broadcast的返回值。
由于代码比较长这里就不给出了,可以在小编的git中下载。
最后我们演示一定这个初级版的程序如何运行:
mypeer:python xxx.py http://localhost:16888 file1 123456
otherpeer:python xxx.py http://localhost:16889 file2 123456
#创建mypeer
它访问file2下的文件:
然后创建otherpeer:
它访问file1下的文件:
把mypeer介绍给otherpeer:
然后在访问file1下的文件:
通过otherpeer拉去file1下的文件:
发现file2下有test1.txt:
有朋友想问,这个有啥用啊,我们不妨拓展一下思维,如果这里是一个集群,我们用client去访问server中的文件,但是文件分布在server集群的某个节点上,想想我们可不可通过这种方法,把文件传递到访问的节点上,然后从访问节点中拿取,我的天一不小心了解分布式的原理,低调低调。
最终版我们需要在初次实现中修改一些内容:
① 创建客户端界面
客户端界面是使用模块cmd中的Cmd类实现,我们在这里只实现命令fetch(下载文件)和exit(退出程序)。命令fetch调用服务器的方 法fetch,并在文件没有找到时打印一条错误消息。命令exit打印一个空行(这只是出于美观考 虑)并调用sys.exit。
② 引发异常
不返回表示成功还是失败的编码,而是假定肯定会成功,并在失败时引发异常。这里我们使用200正常的失败(请求未得到处理)和500表示 请求被拒绝(拒绝访问)。
UNHANDLED = 200
ACCESS_DENIED = 500
class UnhandledQuery(Fault):
"""
表示查询未得到处理的异常
"""
def __init__(self, message="Couldn't handle the query"):
super().__init__(UNHANDLED, message)
class AccessDenied(Fault):
"""
用户试图访问未获得授权的资源时将引发的异常
"""
def __init__(self, message="Access denied"):
super().__init__(ACCESS_DENIED, message)
异常是xmlrpc.client.Fault的子类。在服务器中引发的异常将传递到客户端,并保持 faultCode不变。如果在服务器中引发了普通异常(如IOException),也将创建一个Fault类实例, 因此你不能在服务器中随意地使用异常。
③ 验证文件名
检查指定的文件是否包含在指定的目录中。我们这里采用较为简单的方法实现:
根据目录名和文件名创建绝对路径(例如,这将把'/foo/bar/../ baz'转换为'/foo/baz'),将目录名与空文件名合并以确保它以文件分隔符(如'/')结尾,再检 查绝对文件名是否以绝对路径名打头。如果是这样的,就说明指定的文件包含在指定的目录中。
这里在pychram中运行有诸多bug,使用cmd感觉不是那么好使,或者可以在Linux下运行试试,这里小编也给出了一个Cmd运行的dome,大概是这样的:
在Miller2>后面输入,定义的do_xxx的方法中的xxx,它就会执行相应方法中的内容。
最后小编在这里说一下这个项目的运行规则:
运行:python client.py urls.txt directory http://servername.com:16888
执行错误操作:
首先查询所有的能关联的所有的urls列表,没有的返回Couldn't find the file 123。
输入exit 或者EOF会退出程序:
这个项目较小,你将看到给既 有Python程序添加GUI非常容易。
开发的文件共享系统:添加GUI客户端,让它使用起来更 容易。这意味着可能有更多的人选择使用它,他实现的需求是:
开发这个项目时,最好是将项目8做一遍之后,然后在熟悉熟悉GUI的工具包的使用。
由于这里是结合着项目8的代码,这里小编给出运行结果:
第一个原型非常简单,它确实实现了文件共享功能,但对用户不太友好。如果用户能够知道 有哪些文件可用(这些文件可能是程序启动时就位于文件目录中,也可能是后来从其他节点那里 下载的),将大有裨益。再次实现将实现这种列出文件的功能,要获取节点包含的文件的列表。
到了最后一个项目了,这也是Python权威指南的最后一课了,经过一个多月的学习对Python也有了大致的了解,希望以后可以运用到工作中,至少目前看代码时完全没有问题。
最后一个项目,应该说是所有项目中代码量最多的,而且也比较有趣,这个项目将学习如何使用Pygame,这个扩展让你能够使用Python编写功能齐备的全屏街机游戏。
这个游戏中,我们将让玩家控制一支香蕉。这支香蕉要躲开从天而 降的16吨铅锤,尽力在防御战中活下来。这个项目的目标是围绕着游戏设计展开的。这款游戏必须像设计的那样:香蕉能够移动,16 吨的铅锤从天而降。另外,与往常一样,代码必须是模块化的,且易于扩展。一个重要的需求是, 设计应包含一些游戏状态(如游戏简介、关卡和“游戏结束”状态),同时可轻松地添加新状态。
这个项目需要的工具只有一个,那就是pygame。模块pygame自动导入其他所有的Pygame模块,因此只要在程序开头包含语句import pygame, 就能使用其他模块,如pygame.display和pygame.font。
模块pygame包含函数Surface,它返回一个新的Surface对象。Surface对象其实就是一个指定 尺寸的空图像,可用来绘画和传送。传送(调用Surface对象的方法blit)意味着在Surface之间传输内容。
函数init是Pygame游戏的核心,必须在游戏进入主事件循环前调用。这个函数自动初始化其他所有模块(如font和image)。
下载:pip install pygame
如果要捕获Pygame特有的错误,就需要使用error类。
接下来我们再来了解几个pygame重要的函数:
在初次实现的时候,我们只实现其中一些简单的功能:
# C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
# -*- coding: utf-8 -*-
'''
简单的“铅锤从天而降”动画
'''
import sys,pygame
from random import randrange
from pygame.locals import *
class Weight(pygame.sprite.Sprite):
def __init__(self,speed):
pygame.sprite.Sprite.__init__(self)
self.speed=speed
#绘制Sprite对象时要用到的图像和矩形:
self.image=weight_image
self.rect=self.image.get_rect()
self.reset()
def reset(self):
"""
将铅锤移到屏幕顶端的一个随机位置
"""
self.rect.top=-self.rect.height
self.rect.centerx=randrange(screen_size[0])
def update(self):
"""
更新下一帧中的铅锤
"""
if self.rect.top> screen_size[1]:
self.reset()
self.rect.top += self.speed
#初始化
pygame.init()
screen_size=1366,768
pygame.display.set_mode(screen_size,FULLSCREEN)
pygame.mouse.set_visible(0)
# 加载铅锤图像
image_path="images/jack.jpg"
weight_image=pygame.image.load(image_path)
weight_image=weight_image.convert() # 以便与显示匹配
# 你可能想设置不同的速度
#speed=5
# 创建一个Sprite对象编组,并在其中添加一个Weight实例
sprites = pygame.sprite.RenderUpdates()
#同时添加三个铅块
sprites.add(Weight(speed=2))
sprites.add(Weight(speed=3))
sprites.add(Weight(speed=5))
# 获取并填充屏幕表面
screen=pygame.display.get_surface()
bg=(255, 255, 255) #白色
#以白色填充屏幕表面
screen.fill(bg)
#显示所做的修改
pygame.display.flip()
clock=pygame.time.Clock() # 设置时钟,限制移动速度
# 用于清除Sprite对象:
def clear_back(surf,rect):
surf.fill(bg, rect)
while True:
clock.tick(60) # 然后在while循环中设置多长时间运行一次,每秒执行60次
# 检查退出事件:
for event in pygame.event.get():
if event.type==QUIT:
sys.exit()
if event.type== KEYDOWN and event.key == K_ESCAPE:
sys.exit()
# 清除以前的位置:
sprites.clear(screen,clear_back)
# 更新所有的Sprite对象:
sprites.update() #方法update调用Weight实例的方法update
# 绘制所有的Sprite对象:
updates=sprites.draw(screen) #调用sprites.draw并将屏幕表面作为参数,以便在当前位置绘制铅锤
# 更新必要的显示部分:
pygame.display.update(updates)
最终我们将实现这样的一个游戏:
这里我们将实现这样几个模块:
config.py:可根据偏好随意修改配置变量,如果游戏的节奏太快或太慢,可尝试修改与速度相关的变量
objects.py:这个模块包含游戏Squish使用的游戏对象
squish.py:这个模块包含游戏Squish的主游戏逻辑
开始界面:
游戏界面:
闯关界面:
结束界面:
到此所有的项目都已结束!!!
所有的代码 小编都上传到gitlab上:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。