MiniWEB项目、模版、使用正则进行替换 {\%content\%} 为内容、路由、装饰器实现路由、AOP面向切面编程、SEO搜索引擎优化 实现伪静态服务器
3.2 MiniWEB项目
学习目标
1. 能够说出路由的概念
2. 能够说出什么是面向切片编程
3. 能够说出模板文件的使用
--------------------------------------------------------------------------------
3.2.1 实现框架
在这部分内容中主要实现框架的搭建,利用模板来实现基本页面的显示(目前的数据还是静态的)
这部分内容中将不在显示静态 API 页面
新创建一个项目,将资源文件夹拷贝到项目的文件夹中去(拷贝方式见前一章节),文件夹中的内容暂时不用考虑
资源文件夹介绍: dynamic 文件夹中用来存放处理动态数据的文件,也就是我们的 WEBFrame 文件 static文件夹中有两个文件夹,CSS和JS,CSS 文件夹存放对页面样式文件,JS 文件夹存放对页面交互动作的文件templates 文件夹中实现好了三个页面的显示模板,实现好了页面的显示基本框架,我们只需要利用框架,向框架里添加显示数据即可
3.2.2 创建工程,导入资源模板文件
实现过程:
1. 打开工程文件位置,将三个文件夹复制到工程文件夹中.将 WEBServer 和 WEBFrame 文件也复制进去
2. 一般的情况下,会将数据处理的 WEBFrame 文件放到 dynamic 目录下
实现代码:
修改导入的相应文件和调用 因为数据文件被创建在 dynamic 这个文件夹下,所以在导入时,需要form-import 方式导入模块
WebServer.py
from dynamic import WEBFrame
# 其它无修改
3.2.3 使用模板 Index 页面实现首页数据显示
页面在显示时,很多时候显示框架都是一样的,只是显示的内容不同
比如新闻页面,都有标题,时间,作者,内容等等固定的显示形式和位置
所以只需要将具体的新闻内容加到写好的框架中显示即可,这就是模板的作用
实现过程:
1. 服务文件到此基本上不会再做修改
2. 主要都是在数据处理文件上进行处理
3. 因现目前的工程已经不在访问API页面,并且的所有的页面布局文件都在static目录下存放
4. 在数据处理文件中根据访问的路径,拼接需要显示的html模板文件,然后拼接文件地址 './templates/index.html'
5. 然后读取文件中的内容
6. 将文件中需要显示内容的地方,使用正则进行替换 {\%content\%} 为内容
7. 可以多加入一些数据
8. 准备插入的数据:
row_str = """
将数据显示到页面中
实现代码:
WebServer.py
# ...
# 前面的代码不需要修改
# ------------- 这里开始修改代码------------
try:
#拼接路径,因现目前的工程已经不在访问API页面,并且的所有的 html 页面模板布局文件都在 static 目录下存放
#为了能正确的读取页面布局文件,所以要将原来 ./html 修改为./static,程序会到 static 目录下找访问的路径对应的模板文件进行读取
f = open("./static" + file_name, "rb")
except:
# 后面的代码不需要修改
# ...
WebFrame.py
# 因为要使用正则替换模板文件中的内容,所以先导入模块
import re
# 实现 WSGI 协议中的 application 接口方法
def application(environ, start_response):
# 从服务器传过来的字典中将访问路径取出来
url_path = environ['PATH_INFO']
# 准备一个用来保存读取模板文件内容的变量
if url_path == '/index.py':
path = './templates/index.html'
# 读取模板文件中的内容
with open(path, 'r') as f:
file_content = f.read()
# 替换的假数据(后面需要从数据库里读取)
row_str = """
# 准备多条数据
all_data = ""
for i in range(20):
all_data += row_str
# 将行数据,利用正则,将数据替换到模板中{%content%} 为了避免打错,建议复制
# 第一个参数为正则,用来匹配模板中的内容
# 参数二为要替换上去的内容
# 参数三是要被替换的字符串
# 替换后重新更新数据并返回
# 正则表达式中,因为{}本身在正则中有限定符的作用,但是这里要替换的字符串包括{},所以在{}前加上\,表示匹配{}
file_content = re.sub(r'\{%content%\}', all_data, file_content)
elif url_path == '/center.py':
file_content = 'Center Page ...'
else:
file_content = 'Other Page ...'
# 回调 start_response 函数,将响应状态信息回传给服务器
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 返回响应数据内容
return file_content
利用模板,将制做的假数据显示到页面成功。
3.2.4 使用模板 Center 页面实现首页数据显示
实现过程:
1. 个人中心页面显示同理首页
2. 在数据处理文件中根据访问的路径,拼接需要显示的html模板文件,然后拼接文件地址 './templates/center.html'
3. 然后读取文件中的内容
4. 将文件中需要显示内容的地方,使用正则进行替换 {\%content\%} 为内容
5. 可以多加入一些数据
6. 准备显示的数据:
row_str = """
将数据显示到页面中
实现代码: WebFrame.py
# ...
# 前面的代码不需要修改
# ------------- 这里开始修改代码------------
elif url_path == '/center.py':
path = './templates/center.html'
with open(path, 'r') as f:
file_content = f.read()
row_str = """
all_data = ''
for i in range(5):
all_data += row_str
file_content = re.sub(r'\{%content%\}', all_data, file_content)
# 后面的代码不需要修改
# ...
3.2.5 代码优化,抽取函数
通过上面的代码可以看出,如果页面很多的话,那么我们就要不断的的将功能实现代码写入到 if-else 中,那么这个一个函数中的代码就会非常大,非常不符合代码开发的规范。
好不容易从服务器文件中分离出来的代码,在这里还是一坨,显然是不合适的。那么怎么将这部分代码再优化一下呢?
可以将每个页面的功能抽取成单独的函数,然后只需要在if中调用访问路径相应的函数执行。如果再加入新功能,只需要再加入新方法就可以了。代码管理非常清晰。
实现过程:
1. 抽取方法
2. 调用函数
实现代码: WebFrame.py
# 因为要使用正则替换模板文件中的内容,所以先导入模块
import re
# 实现 WSGI 协议中的 application 接口方法
def application(environ, start_response):
# 从服务器传过来的字典中将访问路径取出来
url_path = environ['PATH_INFO']
# 根据访问的页面来调用相应的方法
if url_path == '/index.py':
file_content = index()
elif url_path == '/center.py':
file_content = center()
else:
file_content = other()
# 回调 start_response 函数,将响应状态信息回传给服务器
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 返回响应数据内容
return file_content
#首页响应的函数
def index():
path = './templates/index.html'
# 读取模板文件中的内容
with open(path, 'r') as f:
file_content = f.read()
# 替换的假数据(后面需要从数据库里读取)
row_str = """
# 准备多条数据
all_data = ""
for i in range(20):
all_data += row_str
# 将行数据,利用正则,将数据替换到模板中{%content%} 为了避免打错,建议复制
# 第一个参数为正则,用来匹配模板中的内容
# 参数二为要替换上去的内容
# 参数三是要被替换的字符串
# 替换后重新更新数据并返回
file_content = re.sub(r'\{%content%\}', all_data, file_content)
return file_content
# 个人中心页面响应的函数
def center():
path = './templates/center.html'
with open(path, 'r') as f:
file_content = f.read()
row_str = """
all_data = ''
for i in range(5):
all_data += row_str
file_content = re.sub(r'\{%content%\}', all_data, file_content)
return file_content
# 其它页面响应的函数
def other():
return 'Other Page ...'
3.2.6 路由
<1>什么是路由?
路由是指根据请求的地址,决定到目标的路径过程。这是一个计算机通用概念,一般使用在网络中。
在 Web框架中,路由是指客户端把请求发送给 Web 服务器,Web 服务器再把请求发送给程序实例。程序实例需要知道对每个 URL 请求运行哪些代码,所以程序实例保存了一个 URL 到 Python 函数的映射关系。处理URL和函数之间关系的程序称路由。
在上面的代码中,if 函数就实现了简单的路由功能,if 函数通过访问路径来决定调用哪个函数来执行返回。上面的代码虽然抽取了方法,实现了简单的路由功能,但是调用起来依然很繁琐,我们还是要在程序入加入很多的if-else,来实现函数的调用。那么有没有办法更简单的实现路由功能呢?
为了解决这个问题,我们在此基础上,利用字典的键值对实现路由来调用函数
创建一个字典,使用字典保存 访问路径 和 对应方法 的键值对
通过在字典中查找访问的路径来找到对应的函数进行调用实现路由。
实现过程:
1. 创建一个用来保存访问路径和对应函数的字典,并将键值对关系存入字典中
2. 当访问页面时,通过访问路径得到对应的函数,然后执行
3. 如果以后有新的页面添加,那么就向字典中加入新的键值对即可
实现代码: WebFrame.py
def application(environ, start_response):
# 从服务器传过来的字典中将访问路径取出来
url_path = environ['PATH_INFO']
# ------------- 这里开始修改代码------------
# 创建一个路由字典,将访问路径和响应函数的键值对加入到字典中
router_dict = {'/index.py': index, '/center.py': center}
# 判断访问的路径在不在字典的key中
if url_path in router_dict:
# 如果在,在字典中查找传入的访问路径,找到对应的函数
function = router_dict[url_path]
else:
# 如果不在,那么设置一个默认函数,不然页面无法显示,不友好
function = other
# 执行函数得到数据
file_content = function()
# ------------- 这里结束修改代码------------
# 回调 start_response 函数,将响应状态信息回传给服务器
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 返回响应数据内容
return file_content
通过字典来实现路由,使代码更加简单。
3.2.7 扩展装饰器传参
知识点扩展:
装饰器也可以传参
# 定义带参数的装饰器
def set_args(args):
print(args)
def set_fun(func):
print('set_func')
def wrapper(*args,**kwargs):
print('wrapper')
return func(*args,**kwargs)
return wrapper
return set_fun
# 利用装饰器来装饰函数
@set_args('args-string')
def show():
print('show')
# 调用函数
show()
程序执行结果:
args-string
set_func
wrapper
show
装饰器本来就是使用闭包来实现的,而闭包就是一种函数嵌套实现的形式。
带参的装饰器就是在原有装饰闭包函数外又套了一层函数
装饰器在执行装饰过程中,先将装饰器当成一个函数来调用,得到内部函数的引用,然后再将这个内部函数做为真正的装饰器来装饰实际函数。
带参装饰器执行过程:
1. 先执行set_args('参数') 得到 set_args函数中的返回值,也就是set_fun函数的引用
2. 然后返回的引用和@进行组合,变成装饰器形式 @set_fun , 但是这时set_fun 函数因为是返回的闭包引用,所以保留了args的参数值
3. 再调用 show 的时候,show 还是指向的 wrapper 函数,但是在这个函数中,可以使用外面两层函数的变量或参数
4. 无论在何时,闭包有几层,最终被装饰的函数永远指向 wrapper 函数
装饰器有了传参,那么能不能利用这个特性,来改进一下我们的代码呢?
3.2.8 装饰器实现路由
前面版本的代码在实现函数的调用时,还是需要做大量的键值对添加工作
那么我们是不是可以利用装饰器传参来自动进行向字典中添加访问路径和函数之间的关系呢?
如果可以,那么以后,我们只需要完成访问页面的响应函数就可以了,这个也是必须要写的
然后在响应函数前面加上装饰器,让装饰器自动将键值对关系添加到字典中。这样的话就不需要再手动添加键值对关系了。
实现过程:
1. 创建一个用来保存键值对关系的空字典
2. 创建一个带参的闭包装饰器
3. 在闭包装饰器中传入访问的页面路径,然后利用路径,将函数本身加入到字典中
4. 为每个实现的函数都加上这个装饰器
5. 在application函数中,直接调用字典,通过访问路径来得到对应的响应函数调用即可
# ------------- 这里开始修改代码1------------
# 创建一个空字典
router_dict = {}
# ------------- 这里结束修改代码1------------
def application(environ, start_response):
# 从服务器传过来的字典中将访问路径取出来
url_path = environ['PATH_INFO']
# 创建一个路由字典,将访问路径和响应函数的键值对加入到字典中
# router_dict = {'/index.py': index, '/center.py': center}
# 通过在路由字典中查找传入的访问路径,找到对应的函数
if url_path in router_dict:
function = router_dict[url_path]
else:
function = other
# 执行函数得到数据
file_content = function()
# 回调 start_response 函数,将响应状态信息回传给服务器
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 返回响应数据内容
return file_content
# ------------- 这里开始修改代码2------------
# 实现一个带参的装饰器
def router(url_path):
def set_func(func):
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
#将 wrapper 的引用存入字典入,以 url_path 参数做为 key,wrapper 指向就是被装饰函数本身
#一定要注意存入的书写位置,这条语句在执行 @set_func 时执行,将键值关系存入字典,否则就错了
router_dict[url_path] = wrapper
return wrapper
return set_func
# ------------- 这里结束修改代码2------------
# 为每个函数添加装饰器
@router('/index.py')
def index():
pass # 这里功能代码不需要修改,笔记中省略
@router('/center.py')
def center():
pass # 这里功能代码不需要修改,笔记中省略
3.2.9 AOP面向切面编程概念介绍
<1>什么是AOP面向切面编程:
Aspect Oriented Programming AOP面向切面编程
他和面向对象编程一样,也是一种编程思想。使用 AOP 编程思想,更利于程序的扩展
AOP是指在编程过程中,开发人员并不关心这个程序如何开始,如何结束,只需要关心,如何添加一个功能的代码实现
<2>如何实现AOP编程:
在 python 中,使用装饰器来完成AOP编程
当代码添加后,程序就可以自动识别并运行,原理类似多米诺骨,我们并不关心从哪开始,到哪结束,我只需要考虑在哪多加一片,加上这片以后,也不影响骨牌的传递。上个版本的代码中,使用装饰器装饰响应函数,基本上就实现了面向切面编程的思想。不需要知道程序如何执行,只需要在实现页面响应功能函数前加上一个装饰器即可。程序在开发过程中,并不需要考虑程序运行的前因后果,只需要将自己需求的功能实现在函数中即可,程序会自动调用,就像是一张骨牌
3.2.10 SEO 实现伪静态服务器
Search Engine Optimization 搜索引擎优化:
搜索引擎优化是一种利用搜索引擎的搜索规则来提高目前网站在有关搜索引擎内的自然排名的方式。
SEO的目的:
为了从搜索引擎中获得更多的免费流量,从网站结构、内容建设方案、用户互动传播、页面等角度进行合理规划,使网站更适合搜索引擎的索引原则的行为;
在搜索时,静态页面结果排名比动态页面靠前
但是实际效果不大,显示在前面的都是竞价排名的.花钱好办事(比如X田系)
为了让网页地址支持SEO,将访问地址中的 xxx.py 改成 xxx.html 的形式
在实际的页面访问时,并不会有 .py 这种格式的文件,所以为了支持SEO,将动态页面伪装成静态页面,将访问地址改为 .html
实现过程:
1. 服务器文件中用来判断文件类型的位置要进行修改
2. 访问地址修改后,数据处理文件中相应的内容也要进行修改
3. 被装饰的函数要修改
代码实现:
WebServer.py
#判断访问路径的类型
if file_name.endswith('.html'):
WebFrame
# 因为访问页面变成了 .html 的后缀,所以装饰器在传参的时候,也需要传入的是 .html
@set_args('/index.html')
def index():
pass
@set_args('/center.html')
def center():
pass
注意: 模板文件中有两个用来实现页面内跳转的文件连接,也是以 .py 文件名进行调用的,也需要改成 .html
index.html文件
■
center.html文件:
■
3.2.11 小结
至此,我们已经完成了MiniWeb框架的编写工作。
在编程过程中,要理解路由,模板等概念,并且理解使用它们的目的。