[Python之路] 使用装饰器给Web框架添加路由功能(静态、动态、伪静态URL)
一、观察以下代码
以下来自 Python实现简易HTTP服务器与MINI WEB框架(利用WSGI实现服务器与框架解耦) 中的mini_frame最后版本的代码:
import time def index():
with open("templates/index.html", 'rb') as f:
content = f.read()
return content.decode("utf-8") def login():
return "----login page----\r\n %s" % time.ctime() def register():
return "----register page----\r\n %s" % time.ctime() def application(env, start_response):
file_name = env['PATH_INFO']
if file_name == '/login.py':
start_response('200 OK', [('Content-Tpye', 'text/html')])
return login()
elif file_name == '/register.py':
start_response('200 OK', [('Content-Tpye', 'text/html')])
return register()
elif file_name == '/index.py':
start_response('200 OK', [('Content-Tpye', 'text/html')])
return index()
else:
start_response('404 NOT FOUND', [])
return "Not found page..."
我们可以看到,在前面的代码实现中,application函数中通过if...else判断来对用户的请求做判断,然后决定调用什么函数来进行处理。这是不合理的,如果支持100个请求,那么这样就需要些100个分支。
二、通过字典来实现请求与处理函数之间的映射
添加一个全局字典,用于存放 请求字符串:函数引用,例如 "/index.py" : index。
import time # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
URL_MAP_DICT = dict()
我们假设我们已经手工加入了映射数据到这个字典中,则我们可以将application函数修改为:
def application(env, start_response):
# 从服务器获取到用户的请求字符串
file_name = env['PATH_INFO'] # 手工为字典加入映射数据
URL_MAP_DICT['/index.py'] = index
URL_MAP_DICT['/register.py'] = register
URL_MAP_DICT['/login.py'] = login try:
# 如果在字典中存在着映射,则执行响应的函数并返回结果
return URL_MAP_DICT[file_name]()
except Exception as ret:
# 如果字典中不存在映射,则返回404
start_response("404 NOT FOUND", [])
return "404 异常"
由于mini_frame是我们实现的框架, 框架一旦做好以为,我们不会轻易来修改框架的代码,而application函数是mini_frame的核心。也就是说我们不能老是通过修改application函数来添加功能。
三、使用装饰器来自动添加映射关系
为每一个实际的请求处理函数添加装饰器,在装饰器中实现将映射关系添加到字典中:
整体代码:
import time # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
URL_MAP_DICT = dict() # 实现装饰器
def router(url):
def set_func(func):
URL_MAP_DICT[url] = func def call_func(*args, **kwargs):
return func(*args, **kwargs) return call_func return set_func @router('/index.py')
def index():
with open("templates/index.html", 'rb') as f:
content = f.read()
return content.decode("utf-8") @router('/login.py')
def login():
return "----login page----\r\n %s" % time.ctime() @router('/register.py')
def register():
return "----register page----\r\n %s" % time.ctime() def application(env, start_response):
# 从服务器获取到用户的请求字符串
file_name = env['PATH_INFO'] try:
# 如果在字典中存在着映射,则执行响应的函数并返回结果
return URL_MAP_DICT[file_name]()
except Exception as ret:
# 如果字典中不存在映射,则返回404
start_response("404 NOT FOUND", [])
return "404 异常"
为什么可以使用装饰器来实现?
因为装饰器的执行时间是在被装饰函数定义好之后立即执行,也就是在模块被导入的时候。所以服务器调用application函数的时候,所有的处理函数已经被注册到了全局字典中。
四、静态、动态、伪静态URL
静态URL:例如 www.leeoo.com/110.html,其中110.html是一个静态文件。
动态URL:例如 www.leeoo.com/search.asp?id=5,这种带有"?"的URL,一般称为动态URL,每个动态URL是一个逻辑地址,并不是对应真实服务器目录文件的。
伪静态URL:使用 www.leeoo.com/74.html这种和静态URL非常相似的URL。但是他并不是对应服务器静态文件的。例如74.html,我们可以认为74是页数,返回第74页的内容(数据来自数据库)。
在搜索引擎中,除了给钱使其排名靠前。使用静态和伪静态URL也能使SEO(搜索引擎优化)更加优化,能够提高网页在搜索引擎中的排列顺序。
要实现伪静态URL,我们需要将web_server.py进行修改:
完整服务器代码:
import socket
import re
import multiprocessing from dynamic import mini_frame class WSGIServer(object):
def __init__(self):
self.headers = list()
self.status = ""
# 创建socket实例
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置资源重用
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定IP端口
self.tcp_server_socket.bind(("", 7890))
# 开始监听
self.tcp_server_socket.listen(128) # 请求处理函数
def handle_request(self, new_socket):
# 接收请求
recv_msg = ""
recv_msg = new_socket.recv(1024).decode("utf-8")
if recv_msg == "":
print("recv null")
new_socket.close()
return # 从请求中解析出URI
recv_lines = recv_msg.splitlines()
# 使用正则表达式提取出URI
ret = re.match(r"[^/]+(/[^ ]*)", recv_lines[0])
if ret:
# 获取URI字符串
file_name = ret.group(1)
# 如果URI是/,则默认返回index.html的内容
if file_name == "/":
file_name = "templates/index.html" # 如果请求字符串不是以".html"结尾,则都认为是静态资源,直接在磁盘中读取并返回
if not file_name.endswith(".html"):
try:
# 根据请求的URI,读取相应的文件
fp = open("." + file_name, "rb")
except:
# 找不到文件,响应404
response_msg = "HTTP/1.1 404 NOT FOUND\r\n"
response_msg += "\r\n"
response_msg += "<h1>----file not found----</h1>"
new_socket.send(response_msg.encode("utf-8"))
else:
html_content = fp.read()
fp.close()
# 响应正确 200 OK
response_msg = "HTTP/1.1 200 OK\r\n"
response_msg += "\r\n" # 返回响应头
new_socket.send(response_msg.encode("utf-8"))
# 返回响应体
new_socket.send(html_content)
else:
env = dict()
# 将请求的内容添加到字典中,并传递给application
env['PATH_INFO'] = file_name
body = mini_frame.application(env, self.set_response_header) # 将框架返回的status组合进response header中
header = "HTTP/1.1 %s\r\n" % self.status
# 将框架返回的响应头键值对加入到header中
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
# 最后加上一个分割行
header += "\r\n" # 将响应体数据加在header后面
response = header + body
# 返回给客户端
new_socket.send(response.encode("utf-8")) # 关闭该次socket连接
new_socket.close() # 该成员函数提供给web框架的applicaiton函数调用,并将status,headers设置到服务器中
def set_response_header(self, status, headers):
self.status = status
# 如果要在响应头中添加服务器信息,一定是在服务器代码中加,而不是web框架代码中加
self.headers = [("server", "mini_server v1.0")]
self.headers += headers # 开始无限循环,接受请求
def run_forever(self):
while True:
new_socket, client_addr = self.tcp_server_socket.accept()
# 启动一个子进程来处理客户端的请求
sub_p = multiprocessing.Process(target=self.handle_request, args=(new_socket,))
sub_p.start()
# 这里要关闭父进程中的new_socket,因为创建子进程会复制一份new_socket给子进程
new_socket.close() # 关闭整个SOCKET
tcp_server_socket.close() def main():
wsgi_server = WSGIServer()
wsgi_server.run_forever() if __name__ == "__main__":
main()
在之前的代码中,我们判断请求是否以".py结尾",如果不是".py",则认为是静态资源,例如png、css、js、html等,由服务器直接读取并返回给客户端。
而在这里,我们将file_name.endswith(".py")修改为file_name.endswith(".html"),也就是说出了html,其他的我们都认为是静态资源。当请求以"html"结尾时,我们认为是动态资源,交给mini_frame处理。
在mini_frame的代码中,我们将所有的装饰器参数修改为".html"结尾即可:
import time # 定义一个全局的字典,用来存放请求字符串与处理函数引用之间的映射关系
URL_MAP_DICT = dict() # 实现装饰器
def router(url):
def set_func(func):
URL_MAP_DICT[url] = func def call_func(*args, **kwargs):
return func(*args, **kwargs) return call_func return set_func @router('/index.html')
def index():
with open("templates/index.html", 'rb') as f:
content = f.read()
return content.decode("utf-8") @router('/login.html')
def login():
return "----login page----\r\n %s" % time.ctime() @router('/register.html')
def register():
return "----register page----\r\n %s" % time.ctime() def application(env, start_response):
# 从服务器获取到用户的请求字符串
file_name = env['PATH_INFO'] try:
# 如果在字典中存在着映射,则执行响应的函数并返回结果
return URL_MAP_DICT[file_name]()
except Exception as ret:
# 如果字典中不存在映射,则返回404
start_response("404 NOT FOUND", [])
return "404 异常"
[Python之路] 使用装饰器给Web框架添加路由功能(静态、动态、伪静态URL)的更多相关文章
- 小白的Python之路 day4 装饰器前奏
装饰器前奏: 一.定义: 1.装饰器本质是函数,语法都是用def去定义的 (函数的目的:他需要完成特定的功能) 2.装饰器的功能:就是装饰其他函数(就是为其他函数添加附加功能) 二.原则: 1. 不能 ...
- python之路之装饰器
一 装饰器进化之路1) import time def index(): start_time=time.time() time.sleep() print('welcome to index wor ...
- 小白的Python之路 day4 装饰器高潮
首先装饰器实现的条件: 高阶函数+嵌套函数 =>装饰器 1.首先,我们先定义一个高级函数,去装饰test1函数,得不到我们想要的操作方式 import time #定义高阶函数 def deco ...
- python笔记3 闭包 装饰器 迭代器 生成器 内置函数 初识递归 列表推导式 字典推导式
闭包 1, 闭包是嵌套在函数中的 2, 闭包是内层函数对外层函数的变量(非全局变量)的引用(改变) 3,闭包需要将其作为一个对象返回,而且必须逐层返回,直至最外层函数的返回值 闭包例子: def a1 ...
- Python中利用函数装饰器实现备忘功能
Python中利用函数装饰器实现备忘功能 这篇文章主要介绍了Python中利用函数装饰器实现备忘功能,同时还降到了利用装饰器来检查函数的递归.确保参数传递的正确,需要的朋友可以参考下 " ...
- python函数与方法装饰器
之前用python简单写了一下斐波那契数列的递归实现(如下),发现运行速度很慢. def fib_direct(n): assert n > 0, 'invalid n' if n < 3 ...
- guxh的python笔记三:装饰器
1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...
- Python学习笔记012——装饰器
1 装饰器 1.1装饰器定义 在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator). 1.2 装饰器分类 装饰器:函数装饰器,类装饰器,函数的装饰器,类的装饰器 装饰器:函数装饰函 ...
- python设计模式之内置装饰器使用(四)
前言 python内部有许多内建装饰器,它们都有特别的功能,下面对其归纳一下. 系列文章 python设计模式之单例模式(一) python设计模式之常用创建模式总结(二) python设计模式之装饰 ...
随机推荐
- Kick Start 2019 Round H. Elevanagram
设共有 $N = \sum_{i=1}^{9} A_i$ 个数字.先把 $N$ 个数字任意分成两组 $A$ 和 $B$,$A$ 中有 $N_A = \floor{N/2}$ 个数字,$B$ 中有 $N ...
- 怎样理解 Vue 项目的目录结构?
Vue 项目的目录结构如下, 我们将会在后面逐个去了解它们的作用: 01. build - 存储项目构建相关的代码, 比如 webpack. 02. config - Vue 的配置目录,包括端口 ...
- Tomcat中的服务器组件和 服务组件
开始学习Tocmat时,都是学习如何通过实例化一个连接器 和 容器 来获得一个Servlet容器,并将连接器 和 servlet容器相互关联,但是之前学习的都只有一个连接器可以使用,该连接器服务80 ...
- ACM算法练习-——ZJU1164-Software CRC
具体的题目描述点此链接 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1164 这道题,说实话 ...
- Navicat连接CentOS7中的MariaDB
Step 1:首先登录数据库设置开启远程连接 mysql -u root -p Step 2:使用改表法实现远程连接 use mysql; update user set host = '%' whe ...
- Centos7下查看端口占用
netstat -nap #会列出所有正在使用的端口及关联的进程/应用 netstat -lnp|grep 5000 这条语句的作用是查询占用5000端口的应用和进程,把5000端口替换成你要过滤的端 ...
- php判断
<?php $str = '我是张三?'; preg_match("/张三/", $str, $match); if($match) { echo ' 张三在文本中'; } ...
- imx6 yocto移植 环境搭建
系统:ubuntu14.04 LTS 切换软件下载源,确保下载资源是最快. 安装必要软件工具: ~$ apt-get install vim ~$ apt-get install openssh-se ...
- 09-【el表达式和jstl标签库】
el表达式和jstl标签库 一:el表达式:表达式语言,jsp页面获取数据比较简单1.el表达式的语法(掌握)el表达式通常取值是获取作用域对象中的属性值:${属性名}=>是el表达式的简写的形 ...
- mysql高级:触发器、事务、存储过程、调用存储过程
一.触发器 二.pymysql事务测试 三.存储过程 四.pymysql调用存储过程 一.触发器 在某个时间发生了某个事件时 会自动触发一段sql语句 create trigger cmd_ins ...