47. web框架
1. web框架本质
web框架本质上可以看成一个功能强大的socket服务端,用户的浏览器可以看成拥有可视化界面的socket客户端
通过网络请求实现数据交互,可简单的将web框架看做是对前端、数据库的全方位整合

2. 手写简易版web框架
2.1 回顾-http协议
tcp服务端无法响应客户端
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9000))
server.listen(5)
while True:
sock, addr = server.accept()
data = sock.recv(1024)
print(data.decode('utf-8'))
sock.send(b'hi,here is server')
浏览器无法访问

浏览器发起访问时后端有打印数据

原因:sock.send(b'hi,here is server') 服务端返回的数据没有遵循http协议
2.2 简易web框架
[1] 服务端
搭建简易版本服务器:socket模块
遵循http协议:数据格式、响应状态码
import socket
server = socket.socket()
server.bind(('127.0.0.1', 9009))
server.listen(5)
while True:
sock, addr = server.accept()
data = sock.recv(1024)
data_str = data.decode('utf-8') # 将客户端请求数据转为字符串(即浏览器发起访问时后端打印的数据:请求首行、请求头、请求体)
print(data_str) # 打印客户端浏览器的请求数据
sock.send(b'HTTP1.1 200 OK\r\n\r\n') # 服务端响应的数据需要符合HTTP响应格式
ask_path = data_str.split(' ')[1] # 使用空字符串切割,索引为1的元素即浏览器请求的路径
if ask_path == '/login':
sock.send(b'login exists') # TCP协议是流式协议,会与以上send合并发送
elif ask_path == '/register':
sock.send(b'register exists')
else:
sock.send(b'404 Not Found')

[2] 基于不同的url后缀响应不同的内容
(1)当端口之后不带路径时

浏览器发起访问时后端的打印

(2)同时打印的/favicon.ico可以忽略,与url后缀的值无关

(3)当端口之后带有路径时
路径存在:


路径不存在:


以上简易版web框架的缺点:
(1)服务端socket代码过于重复
(2)HTTP数据请求没有更多的处理方式(只能切割空字符)
(3)无法实现服务端高并发
3. 基于wsgiref模块搭建web框架
[1] 基础版
from wsgiref import simple_server
def run(request, response):
"""
:param request: 请求相关的数据
:param response: 响应相关的数据
:return: 返回给客户端展示的数据
"""
print(request) # 模块将http请求相关数据处理成字典类型,便于后续获取
response("200 OK", []) # 固定格式
return [b'hi here is server']
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9002, run)
server.serve_forever()
端口后面没有后缀访问
任意后缀访问


浏览器发起访问时后端打印的数据(字典形式)

[2] 完整版
from wsgiref import simple_server
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
if ask_path == "/":
return [b'empty']
elif ask_path == "/login":
return [b'login exists']
else:
return [b'404 error']
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9000, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()
不带路径时


带有路径时



4. wsgiref优化
4.1 问题
如果网站很多,就要添加很多个elif
每个分支下的功能都不相同,可能会有比较复杂的逻辑
4.2 优化1:函数优化
将路径后缀匹配和功能,封装成元组和函数
from wsgiref import simple_server
# 功能封装成函数
def login():
return "login exists"
def register():
return "register exists"
def error():
return "error"
# 路径后缀匹配封装成元组
urls = (("/login", login), ("/register", register))
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
func_name = None
for url_one in urls:
if ask_path == url_one[0]: # 将请求的路径与已存在的路径进行比对
func_name = url_one[1] # 将路径名作为值赋值给函数名
break # 匹配成功则停止比对
if func_name: # for循环完毕之后,func_name有可能是None,因此要进行判断
res = func_name() # 有则运行对应函数
else:
res = error() # 没有则运行错误函数
return [res.encode("utf8")]
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9000, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()






新的问题是,所有功能函数放在一个py文件中过于冗余,不便于后期管理维护
4.3 优化2:模块优化
根据功能的不同拆分成不同的py文件
urls.py 对应关系的存储
views.py 业务逻辑的编写
在上方函数优化1的基础上,将功能函数放入views.py文件,将urls对应关系放入urls.py文件


server.py
from wsgiref import simple_server
from urls import urls
from views import error
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
func_name = None
for url_one in urls:
if ask_path == url_one[0]: # 将请求的路径与已存在的路径进行比对
func_name = url_one[1] # 将路径名作为值赋值给函数名
break # 匹配成功则停止比对
if func_name: # for循环完毕之后,func_name有可能是None,因此要进行判断
res = func_name() # 有则运行对应函数
else:
res = error() # 没有则运行错误函数
return [res.encode("utf8")]
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9000, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()
想要新增功能,只需要在urls.py中添加对应关系,view.py中编写业务逻辑函数
4.4 优化3:html文件优化
在优化2的基础上,新的问题是,业务函数代码中,可能会频繁的使用到不同的HTML页面
from wsgiref import simple_server
from urls import urls
from views import error
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
func_name = None
for url_one in urls:
if ask_path == url_one[0]: # 将请求的路径与已存在的路径进行比对
func_name = url_one[1] # 将路径名作为值赋值给函数名
break # 匹配成功则停止比对
if func_name: # for循环完毕之后,func_name有可能是None,因此要进行判断
res = func_name() # 有则运行对应函数
else:
res = error() # 没有则运行错误函数
return [res.encode("utf8")]
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9000, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()





为了避免文件类型的混乱,单独创建一个文件夹存储所有的HTML文件
templates文件夹 存储项目所需的HTML文件

新的问题是,项目中的HTML文件,也有可能需要用到css、js、第三方框架文件
4.5 优化4:静态优化
HTML所需要的css、js、第三方框架文件都是很少改变的文件
所以可以统一放在某个文件夹下
static文件夹 存储项目所需的“静态文件”

总结:拆分后如果想要新增一个功能,只需要在views.py中编写函数,urls.py中添加对应关系即可。
5. 动静态网页
5.1 概念
静态网页:数据和布局在编写 HTML 文件时就已经固定下来,不随用户的请求或时间的变化而改变。
动态网页:在服务器端运行脚本语言(如 PHP、Python、JavaScript 或 Java 等)来生成和处理数据,然后再将处理后的数据以 HTML 格式发送至浏览器。
eg:1.页面上展示当前时间(后端获取传递给前端页面)
2.页面上展示数据库数据(后端连接数据库查询数据再传递给页面)
5.2 案例1
需求:页面上展示当前时间在4.4的基础上,定义一个获取时间的函数,并添加对应关系
views.py
def get_time():
c_time = time.strftime("%Y-%m-%d %X") # 这里的c_time是后端生成的时间
with open(r'templates/get_time.html', 'r', encoding='utf-8') as f:
data = f.read() # 读取出来的数据是整个HTML代码
new_data = data.replace('abcdefg', c_time) # 将整个HTML代码的指定字符串内容替换为时间数据
return new_data
urls.py
from views import *
urls = (("/login", login), ("/register", register), ("/get_time", get_time))
get_time.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>abcdefg</p>
</body>
</html>


5.3 案例2
需求:将字典传递到页面上,并且在页面上还可以使用字典的操作方法
在4.4 的基础上,定义一个获取字典的函数,并添加对应关系
views.py
def get_dict():
info = {'name': 'avril', 'pwd': 123, 'hobby': 'sing'}
with open(r'templates/get_dict.html', 'r', encoding='utf-8') as f:
data = f.read()
new_data = data.replace('abcdefg', str(info)) # 将字典转换成了字符串格式
return new_data
urls.py
from views import *
urls = (("/login", login), ("/register", register), ("/get_time", get_time), ("/get_dict", get_dict))
get_dict.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>abcdefg</p>
</body>
</html>


以上代码将字典以字符串的形式在页面上展示,但是无法使用字典的操作方法
为了解决这个问题,引入了jinja2模块
6. jinja2模块
6.1 操作字典
安装jinja2模块:pip install jinja2
模块功能介绍:在编写前后端不分离项目的时候,使用该模块提供的模板语法简单快速的在HTML页面可以用类似于后端的代码语法操作数据
在4.4与5.3的基础上,对get_dict的py文件与html文件稍作修改
server.py
from wsgiref import simple_server
from urls import urls
from views import error
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
func_name = None
for url_one in urls:
if ask_path == url_one[0]: # 将请求的路径与已存在的路径进行比对
func_name = url_one[1] # 将路径名作为值赋值给函数名
break # 匹配成功则停止比对
if func_name: # for循环完毕之后,func_name有可能是None,因此要进行判断
res = func_name() # 有则运行对应函数
else:
res = error() # 没有则运行错误函数
return [res.encode("utf8")]
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9000, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()
views.py
from jinja2 import Template
def error():
with open(r'templates/error.html', 'r', encoding='utf8') as f: # 只能用r模式,不能用rb模式
return f.read()
def get_dict():
info = {'name': 'avril', 'pwd': 123, 'hobby': 'sing'}
with open(r'templates/get_dict.html', 'r', encoding='utf-8') as f:
data = f.read()
temp_proj = Template(data)
res = temp_proj.render({'user_dict': info}) # 给页面传递一个变量名是user_dict,值为info(info的值是字典)的数据
return res
jinja2模块渲染HTML代码,并将后端的数据传递给前端进行渲染
data是HTML源码文件读出来的字符串数据
temp_proj是文本数据,要对数据进行编码
纯文本的数据是基于gbk编码,浏览器默认的编码就是gbk
但是渲染的HTML代码不能使用gbk编码---HTML的meta属性约束了编码方式是utf8
因此res.encode("utf8")编码成二进制数据返回
urls.py
from views import *
urls = (("/get_dict", get_dict),)
get_dict.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>字典数据展示</p>
<p>{{user_dict}}</p>
<p>{{user_dict.name}}</p>
<p>{{user_dict['pwd']}}</p>
<p>{{user_dict.get('hobby')}}</p>
</body>
</html>

以上HTML代码使用python语法操作数据

6.2 访问数据库

server.py
from wsgiref import simple_server
from urls import urls
from views import error
def run(request, response):
"""
:param request:与请求相关数据
:param response: 与响应相关数据
:return: 返回给客户端的数据
"""
# print(request) # 字典类型的数据(HTTP请求相关)
response("200 OK", []) # 固定格式
ask_path = request.get("PATH_INFO")
func_name = None
for url_one in urls:
if ask_path == url_one[0]: # 将请求的路径与已存在的路径进行比对
func_name = url_one[1] # 将路径名作为值赋值给函数名
break # 匹配成功则停止比对
if func_name: # for循环完毕之后,func_name有可能是None,因此要进行判断
res = func_name() # 有则运行对应函数
else:
res = error() # 没有则运行错误函数
return [res.encode("utf8")]
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 9001, run) # 一直监听本机9000端口,一旦有请求访问,自动触发run函数的运行
server.serve_forever()
urls.py
from views import *
urls = (("/get_db", get_db),)
views.py
from jinja2 import Template
import pymysql
def error():
with open(r'templates/error.html', 'r', encoding='utf8') as f: # 只能用r模式,不能用rb模式
return f.read()
def get_db():
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123456',
db='test01',
charset='utf8mb4',
autocommit=True
)
cursor = conn.cursor()
sql = "select * from user;"
cursor.execute(sql)
data_list = cursor.fetchall()
with open(r'templates/get_db.html', 'r', encoding='utf8') as f:
data = f.read()
temp_proj = Template(data)
res = temp_proj.render({'data_list2': data_list})
return res
get_db.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>666</p>
<p>{{data_list2}}</p>
</body>
</html>


7. 框架请求流程
7.1 流程图

7.2 关键字解释
[1] urls.py
urls = (("/login", login), ("/register", register), ("/get_time", get_time))
括号里路径专业名词称之为"路由"
括号里函数名专业名词称之为"视图函数"
urls.py专业名词称之为"路由层"
[2] views.py
def login():
return "login exists"
def register():
return "register exists"
该文件用来编写业务逻辑代码,可以是函数、也可以是类
函数称之为"视图函数",类则称之为"视图类"
views.py称之为"视图层"
[3] templates文件夹
用来存储HTML文件
HTML文件称之为"模板文件"
templates文件夹称之为"模板层"
[4] static文件夹
用来存储静态文件资源
页面所需的css文件、js文件、图片、第三方文件可统称为"静态资源"
8. python主流web框架
Django框架官网:https://www.djangoproject.com/
Flask框架官网:https://flask.palletsprojects.com/en/3.0.x/
Fastapi框架官网:https://fastapi.tiangolo.com/
Pyramind框架官网:https://trypyramid.com/
Tornado框架官网:https://www.tornadoweb.org/en/stable/
Sanic框架官网:https://github.com/sanic-org/sanic
Fastapi框架官网:https://fastapi.tiangolo.com/
Aiohttp框架官网:https://docs.aiohttp.org/en/stable/

浙公网安备 33010602011771号