引入web框架以及优化过程
简单的web框架搭建
软件开发目录规范
- C/S架构
- B/S架构
ps: B/S架构的本质就是一个C/S结构
搭建web框架
我们都知道如果我们想开发一个B/S的架构的软件,就必须要依赖于浏览器.
而为了让我们的页面能够被浏览器识别,我们就必须得遵循别人的协议标准.
当然你也可以不遵循,但是你得自己开发一个客户端,用于你自己的服务器上,要是你这个需求量也很大,别人都要下载你的客户端程序,而不是浏览器,当然它们就可以遵循你的标准协议使用了.
那么我们就可以想到有一个现成的客户端,并且功能还那么强大,我们就没必要重复造轮子了.
所有我们就基于浏览器去开发服务器就好了.
那么这个浏览器的协议是什么呢??
HTTP协议
我们就从http协议开始入手
http协议有四大特点
-
它是基于TCP/IP协议中的应用层,是在我们的应用程序中的协议
-
它是基于请求响应的协议
什么意思?
我们要知道我们的浏览器是一个“超级”客户端,在浏览器的网址栏中输入一个url就是一个请求,而服务端收到请求就会有一个响应去回复浏览器的请求信息.
都是客户端去和服务端发送请求的
-
无状态的
就是它不会记住你的服务器状态,你每一次来它都当作你是第一次来,并不会因为你来了几次就记住你
就好比说纵然见你千百遍,我却始终待你如初见.
这可得讲讲它的历史了,以前的浏览器啊,里面打开一个网页就是一写文章和报纸一样的,但是随着互联网慢慢的发展,报纸格式也就是说全文字的网页以及满足不了用户了,就变成了现在的支付宝啊,淘宝啊.这个在我们使用的过程中.我们是不是登录了下次就不用登录啦.你淘宝里面的购物车信息,下次登录还在.还有那个扫描二维码登录的.
这都是为了解决无连接的.我们会用到cookie,session.和taken技术了.这都可以起到保存我们的用户状态的作用.
-
短链接&无链接
就是每次浏览器和服务器连接之后,默认都是交互完毕之后就自动断开连接了.所有我们每次操作浏览器都会有很多的次的断开连接,再断再连的操作过程.
而还有一种就是长连接:
顾名思义,长连接就是一个默认不会断开俩者之间连接的.一般可以基于websocket实现.这个实现的还可以让服务器端给你浏览器发送请求. 就比如一写小网站些弹出广告啊,那些的.
那了解了http的四大特性,里面什么是请求什么是响应?
什么是响应
请求就是浏览器向服务器发送get/post请求,接收/上传 服务器的数据
响应就是服务器接收到客户端的请求信息,给予的回应,比如返回 一个 200 ok 的页面信息
我们再讲讲http的组成,为了方便管理和处理,我们的请求格式和响应格式组成的方式是差不多一样的.
请求格式
- 请求首行: =====> http的版本信息 和
请求方式
- 请求头部 ====> 放的是一堆客户端的信息字典,一大堆的k,v键值对
- /r/n/
- 请求体 (并不是所有的请求方式都有,get没有post有,post存放的是一些提交的敏感数据,比如说账号和密码)
请求格式图解
http 响应格式
响应首行 ====> 标识http协议的版本, 响应状态码
响应头 ===== > 一大堆k,v键值对
/r/n
响应体 =====> 返回给浏览器展示给用户看的数据.
响应格式图解:
介绍网络名词
请求方式:
1.get请求
- 朝服务器要数据
- eg:输入url获取对应的数据
2.post请求
- 朝服务器提交数据
- eg: 用户登录,向服务器提交用户名和密码数据 后端接收到数据就那些校验
3.get请求和post请求的区别
-
get请求发送的数据是在url中携带的,以?分割url和数据,并且数据与数据之间以&符号链接,所以是暴露在网页中的
-
post请求发送的数据是在一个data的字典对象数据包里面的中,是另外存储的,并且是有加密手段的,更加的安全
- eg:
- eg:
-
get请求不如post请求安全,但一般的网络请求都是get.post一般用于敏感数据.
url
定义为: 统一资源定位符
格式:scheme:[//[user:password@]host:[:port][/]path[?query-string][#anchor]
参数介绍:
- scheme:协议(例如:http/https/ftp.等)
- user:password@ 用户的登录名和密码
- host:服务器的ip地址或者域名
- port: 服务器的端口号 (如果用的是协议,比如http(默认端口80),https(默认端口为443))
- path: 访问资源的路径
- query-string: 参数,一般为客户端发送给服务端的数据
- anchor:锚(跳转到指定的锚点位置)
例如:url: https://www.cnblogs.com/jkeykey/p/14521413.html
对应关系
- scheme: https
- host:www.cnblogs.com
- port: 443
- path: /jkeyjkey/p/14521413.html
开始推理web框架
介绍了http的相关知识,现在我们终于可以搭建我们的web框架了
未遵循浏览器标准
未遵循浏览器标准的代码如下:
mport socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
data_bytes = conn.recv(8192)
print(data_bytes) # 将浏览器发来的消息打印出来
"""
===================== HTTP协议的请求数据格式4部份 =====================
b'GET / HTTP/1.1\r\n # 请求首行(标识HTTP协议版本,当前请求方式)
Host: 127.0.0.1:8080\r\n # 请求头(一大堆k,v键值对)
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
\r\n' # \r\n
# 请求体(并不是所有的请求方式都有. get没有post有, post存放的是请求提交的敏感数据)
b''
"""
conn.send(b'ok')
conn.close()
我们这样如果是用自己的客户端,发送回收打印是可以的.但是如果在浏览器中打开,是查看不到ok这个字段的.
那如何才能在浏览器上显示我们的内容,或者说html文件呢?
这就要看看浏览器的格式了.
我们以访问博客园服务器为例
我们访问: https://www.cnblogs.com/
右键检查,点击network,然后刷新一下页面,抓取到网页的数据包信息
响应相关信息可以在浏览器调试窗口的network标签页中看到。
点击view source之后显示如下图:
那我们知道了,响应格式
遵循http协议
那我们的代码就应该也要遵循他的协议
代码改成
import socket
server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5)
while True:
conn,addr = server.accept()
data_bytes = conn.recv(1024)
print(data_bytes)
# 先发送我们的格式的首行信息和 相应状态 以及换行.
# 为什么是俩个 \r\n,那是因为第一个\r\n是状态码的换行,第二个才是与代码体的换行
conn.send(b"http/1.1 200 ok \r\n\r\n")
conn.send(b"hello world")
conn.close()
客户端访问的数据格式
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nPragma: no-cache\r\n
Cache-Control: no-cache\r\n
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"\r\n
sec-ch-ua-mobile: ?0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36\r\n
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
Sec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n'
此时我们访问的页面,就可以看到我们的 hello word 信息了
web框架之显示html文件
然后现在我们就可以对我们的服务端进行优化了
现在有一个需求,让我们模拟,百度冲浪,输入一个url就可以获取到对应的页面内容
那这要怎么实现呢?
我们可以先拿到客户端数据中的url中的页面信息
代码演示
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, client_addr = server.accept()
data_bytes = conn.recv(8192)
print(data_bytes)
'''
b'GET /index HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nC
br\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
\r\n'
'''
path = data_bytes.split()[1]
print(path) # b'/index'
conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 因为要遵循HTTP协议,所以回复的消息也要加状态行
if path == b'/index':
response = b'you can you do not bb: %s' % path
elif path == b'/login':
response = b'you can you not bb: %s' % path
else:
response = b'404 Not Found'
conn.send(response)
conn.close()
访问index和login时
访问一个不存在的url时
优化web框架之urls_list
那么这时候,问题又来了. 一个url就要对应一个if分支,那么我如果有100个呢?
那么要写100j个if判断吗??这肯定是不行的,所以我们要通过字典或者列表来优雅的替代多分支
代码如下
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 将返回不同的内容部分封装成函数
def index(path):
s = "This is {} page table!".format(path)
return s
def login(path):
s = "This is {} page table!".format(path)
return s
def error(path):
return '404 not found: {}'.format(path)
# 定义一个url和实际要执行的函数的对应关系
urls = [
('/index', index),
('/login', login),
]
while True:
conn, client_addr = server.accept()
data_bytes = conn.recv(8192)
print(data_bytes)
path = data_bytes.split()[1].decode('utf-8') # 分离出的访问路径, 再把收到的字节类型的数据转换成字符串
print(path) # '/index'
conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 因为要遵循HTTP协议,所以回复的消息也要加状态行
func = None # 定义一个保存将要执行的函数名的变量
for url in urls:
if path == url[0]:
func = url[1]
break
if func:
response = func(path)
else:
response = error(path)
# 返回具体的响应消息
conn.send(response.encode('utf-8'))
print(response.encode('utf-8'))
conn.close()
后缀/index路径访问
后缀/login
路径不存在时
知识点
动静态网页
静态网页
静态网页就是我们之前用的前端的知识搭建的,内容是写死
的,比如<p>111</p>
用这样的标签搭建的网页就是静态网页.
那么动态网页就是和这个相反的,标签内的内容是活
,即是后端获取的, 比如 <p>{{ name }}</p>
,用这样的标签搭建的网页就是动态网页.
最大的区别就是,静态的网页,不改文本的内容是不会变的,而动态页面可以通过改后端的数据来改变网页中的内容.
静态网页详细介绍: 返回具体的html文件
我们之前用渲染给客户端的数据都是str字符串类型的,那我们怎么讲html文件中的内容渲染到浏览器上呢?
一样的思想,可以先将html文件中的内容全部拿到,一起发送给浏览器,让其渲染网页即可
代码为
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def get_f(path):
import os
path = r"./templates"+path+".html"
with open(path,'rb') as f:
html_data = f.read()
return html_data
# 将返回不同的内容部分封装成函数
def index(path):
s = get_f(path)
return s
def login(path):
s = get_f(path)
return s
def error(path):
return bytes('404 not found: {}'.format(path).encode("utf-8"))
# 定义一个url和实际要执行的函数的对应关系
urls = [
('/index', index),
('/login', login),
]
while True:
conn, client_addr = server.accept()
data_bytes = conn.recv(8192)
print(data_bytes)
path = data_bytes.split()[1].decode('utf-8') # 分离出的访问路径, 再把收到的字节类型的数据转换成字符串
print(path) # '/index'
conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 因为要遵循HTTP协议,所以回复的消息也要加状态行
func = None # 定义一个保存将要执行的函数名的变量
for url in urls:
if path == url[0]:
func = url[1]
break
if func:
response = func(path)
else:
response = error(path)
# 返回具体的响应消息
conn.send(response)
conn.close()
当为index时
当为login时
动态网页
动态网页: 后端获取当前的时间展示到index.html页面上
代码如下
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
# 将返回不同的内容部分封装成函数
def _open_read(filename):
with open(filename, 'rt', encoding='utf-8') as f:
return f.read()
def index(path):
return _open_read(r'.\templates\index.html')
from datetime import datetime
def login(path): # !!!动态网页!!: 后端获取当前时间展示到html页面上
data = _open_read(r'.\templates\login.html')
data = data.replace('login', "login" + datetime.now().strftime('%Y-%m-%y %X'))
return data
def error(path):
return _open_read(r'.\templates\error.html')
# 定义一个url和实际要执行的函数的对应关系
urls = [
('/index', index),
('/login', login),
]
while True:
conn, client_addr = server.accept()
data_bytes = conn.recv(8192)
print(data_bytes)
path = data_bytes.split()[1].decode('utf-8') # 分离出的访问路径, 再把收到的字节类型的数据转换成字符串
print(path) # '/index'
conn.send(b"HTTP/1.1 200 OK\r\n\r\n") # 因为要遵循HTTP协议,所以回复的消息也要加状态行
func = None # 定义一个保存将要执行的函数名的变量
for url in urls:
if path == url[0]:
func = url[1]
break
if func:
response = func(path)
else:
response = error(path)
# 返回具体的响应消息
conn.sendall(response.encode('utf-8'))
print(response.encode('utf-8'))
conn.close()
优化web框架之wsgiref模块
使用wsgiref模块代替之前 web框架的socket server 部分
我们之前写的简单的web框架有俩个缺陷
-
代码重复
服务端socket代码需要我们自己写.如果好多服务端就要写好多个socket模块的服务端
-
http格式的数据得自己处理
并且只能拿到url后缀 其他数据获取得通过re正则去header那个字符串里面取,太麻烦了.
而利用wsgiref模块优势
- wsgiref模块帮助我们封装了socket代码
- 帮我们处理http格式的数据
wsgiref模块就是一个web服务端网关接口"
- 请求来的时候会帮助你自动拆分http格式数据并封装成非常方便处理的数据格式 --->env大字典
- 响应走的时候会帮你将数据再打包成符合http格式的数据.
ps: 所以wsgiref模块会为我们做服务器对客户端的请求和响应数据,就不需要我们指定编码和解码了.而且取数据很方便
我们之前的代码都是写在一个文件内,我们都知道这是不允许的,所以我们应该对这些方法和代码进行区分,分文件存.
我们总结一下,我们之前的功能除了服务器发送还有三个
1.urls_list 一个url后缀和函数(类也可以)对应关系
2.views url对应的函数(类)的函数书写
3.templates 模块.存放一些html文件
所以我们现在可以讲这三个功能查分开来
这样做的好处是解耦合,拓展方便,只要按照 urls.py---> view.py ---->templates文件夹下的html 就可以搭建我们的web了.
现在我们的服务端启动文件可以为
from wsgiref.simple_server import make_server
from views import *
from urls import *
def run_server(request, response):
"""
函数定义什么都无所谓
:param request: 请求相关的所有数据
是一个大字典,wsgires模块帮你处理好http格式的数据,封装成一个字典方便你的后续操作
:param response: 响应相关的所有数据
:return: 返回给浏览器的数据,返回的格式必须为一个二进制格式
"""
response("200 OK", []) # 响应首行 响应头
print(request)
"""
字典的其中一部分
'PATH_INFO': '/login', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_HOST': '127.0.0.1:8080'
"""
path_info = request.get("PATH_INFO")
print(path_info)
func = None
for url in urls_list:
if path_info == url[0]:
func = url[1]
break
if func:
data = func(request)
else:
data = error(request)
return [data.encode("utf-8")]
if __name__ == '__main__':
server = make_server("127.0.0.1", 8080, run_server)
"""
会实时监听 127.0.0.1:8080 地址 只要有客户端来了
都会交给run函数处理(加括号触发run函数的运行)
flask启动源码
make_server("127.0.0.1",8080,obj)
__call__
"""
server.serve_forever() # 服务器一直开启
urls.py
"""
url后缀对应函数的对应关系文件
"""
from views import *
urls_list = [
("/index", index),
("/login", login),
]
views.py
"""
函数脚本,处理url对应的函数
"""
def _open_read(filename):
with open(filename, 'rt', encoding='utf-8') as f:
return f.read()
def index(request):
return _open_read('templates/index.html')
def login(request): # !!!动态网页!!: 后端获取当前时间展示到html页面上
from datetime import datetime
data = _open_read('templates/login.html')
data = data.replace('login', datetime.now().strftime('%Y-%m-%y %X'))
return data
def error(request):
return _open_read('templates/error.html')
优化方案之jinja2模块
到现在我们使用的网页大部分都是静态网页,动态网页也是通过python的方法
那我们优化这种局部的渲染的.
那就要讲到另外一个模块了jinja2
,
jinja2作用: jiaja2的原理就是字符串替换,我们只要在html页面中遵循jinja2的语法规范写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容效果.
要使用这个模块,我们得事先安装,安装了flask的不用安装,flask自动携带jinja2,默认使用的就是这个.
安装命令:通过清华的地址,下载会快一点
pip3 install -ihttps://pypi.tuna.tsinghua.edu.cn/simple jinja2
jinja2的模板语法
// 定义变量使用双花括号
{{ 变量名称 }} // 该变量可以是python内的所有数据类型
// 执行for循环,if流程控制 为花括号内左右%
{% for i in list %}
{{ i.id }} # 不仅支持python的数据类型的语法,还支持点类属性取值的方法
{% endfor %}
那我们将这个运用到我们的框架中
使用jinja2优化网页内容
例子: 将user表中的数据渲染到 user.html网页中
创建user表
create database db777 charset utf8;
use db777
create table user(
id int primary key auto_increment,
username varchar(16),
pwd varchar(14),
sex enum("男","女"),
hobbies set("吃饭","睡觉","打土匪","打劫")
);
insert into user(username,pwd,sex,hobbies) values
("jkey","123","男","吃饭,打土匪"),
("土匪","123","男","吃饭,打劫"),
("派大星","123","男","打土匪");
执行文件不需要更改
所以执行文件用上面的即可
urls.py
"""
url后缀对应函数的对应关系文件
"""
from views import *
urls_list = [
("/index", index),
("/login", login),
("/user", user)
]
views.py
"""
函数脚本,处理url对应的函数
"""
def _open_read(filename):
with open(filename, 'rt', encoding='utf-8') as f:
return f.read()
def index(request):
return _open_read('templates/index.html')
def login(request): # !!!动态网页!!: 后端获取当前时间展示到html页面上
from datetime import datetime
data = _open_read('templates/login.html')
data = data.replace('login', datetime.now().strftime('%Y-%m-%y %X'))
return data
def _open_mysql():
import pymysql
conn = pymysql.connect(
host="127.0.0.1",
port=3306,
user="root",
passwd="jzd123",
db="db777",
charset='utf8',
)
cursor = conn.cursor(pymysql.cursors.DictCursor)
affected_rows = cursor.execute("select * from user;")
if affected_rows:
user_dict_list = cursor.fetchall()
return user_dict_list
else:
print("对不起你要查找的数据不存在")
cursor.close()
conn.close()
def user(request):
from jinja2 import Template
data = _open_read("templates/user.html")
user_dict_list = _open_mysql()
if user_dict_list:
temp = Template(data)
res = temp.render(user_dict_list=user_dict_list)
return res
return error(request)
def error(request):
return _open_read('templates/error.html')
templates/user.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Title</title>
</head>
<body>
<div class="container">
<div class="row">
<h1 class="text-center">用户表信息</h1>
<table class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>密码</th>
<th>性别</th>
<th>爱好</th>
</tr>
</thead>
<tbody>
{% for user_dict in user_dict_list %}
<tr>
<td> {{user_dict.id}}</td>
<td> {{user_dict.username}}</td>
<td> {{user_dict.pwd}}</td>
<td> {{user_dict.sex}}</td>
<td> {{user_dict.hobbies}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>
ps:注意,浏览器只会识别html和css以及JavaScript,这个模板解析是在python后端处理好的,变成一个html然后再发送给浏览器渲染的,不是浏览器帮我们解析的模块语法.
致此我们的web简单的框架搭建就已经推理完毕了,这个过程最重要的就是对知识点的学习,以及整个的推导过程,代码不是很重要.重要的是推理过程.
思想一定要想,学习起来才快
总结
1.自定义简易版本web框架请求流程图
服务端开启使用wsgiref模块搭建的启动文件,一般配置了就不需要改变.
所以我们需要修改的文件以及步骤为 urls.py
,views.py
,templates模板下的html文件
.
其中需要注意的是views.py中获取数据库的数据,渲染到html文件中,可以传参进去,可以是任何的python数据类型,模板语法也需要注意.
一定要敲,才能发现自己的薄弱点.
流程图流程
浏览器客户端.
wsgiref模块
- 请求来: 处理浏览器请求,解析浏览器http格式的数据,封装成大字典(PATH_INFO中存放的就是url的访问资源的路径)
- 相应去: 将数据打包成符合http协议的格式,再返回给浏览器
后端
-
urls.py
---> 找用户输入的路径有没有与视图函数对应关系的,有就去views.py内找到对应的函数
-
views.py
功能1(静态): 视图函数找templates中的html文件,返回给wsgiref做http格式的封包处理,再返回给浏览器.
功能2(动态):视图函数通过pymysql链接数据库,通过jinja2模板语法将数据库中的数据在templates文件夹下的html文件做一个数据的动态渲染, 最后返回给wsgiref做HTTP格式的封包处理, 再返回给浏览器.
功能3(动态): 也可以通过jinja2模板语法对tmpelates文件夹下的html文件进行数据的动态渲染, 渲染完毕, 再经过wsgiref做HTTP格式的封包处理, 再返回给浏览器.
基本使用流程
# wsgiref模块: socket服务端(后端)
from wsgiref.simple_server import make_server
def run_server(request, response):
"""
函数名定义什么都无所谓, 我们这里就用run_server.
:param request: 请求相关的所有数据.
是一个大字典, wsgiref模块帮你处理好http格式的数据 封装成了字典让你更加方便的操作
:param response: 响应相关的所有数据.
:return: 返回给浏览器的数据, 返回个格式必须是'return [二进制格式的数据]' 这种样式
"""
response('200 OK', []) # 响应首行 响应头
return [二进制格式的数据]
if __name__ == '__main__':
server = make_server(host, port, app) # app=run_server
server.serve_forever()
# urls.py:路由与视图函数对应关系
urls = [(路由,视图函数)]
# views.py: 视图函数
def 视图函数(request):
pass
# pymysql模块: socket服务端(后端)与数据库交互
import pymysql
conn = pymysql.connection(host, port, user, password, database, charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
affected_rows = cursor.execute(sql);
cursor.fetchall()
cursor.close()
conn.close()
# jinja2模块:后端与http文件交互,本质是一种替换操作
from jinja2 import Template
temp = Template(data) # data为要操作的替换的数据
retuen temp.render(user=user_date) #user 是传入给html页面的操作变量
# templates 文件夹:管理html文件
html中使用jinja2模块的语法:
定义变量: {{ user }}
for循环/if流程 : {% for i in user %} ... {% endfor %}
如果i是一个对象,那么可以通过i.xx或者i[xx]来获取值.