Django框架之web框架优化
一、web应用框架简介及手撸web框架
软件开发架构详细:网络编程之网络架构及其趋势 - Xiao0101 - 博客园 (cnblogs.com)
软件开发架构分为两种:
- c/s架构:客户端软件(client)—服务端软件(server)
- b/s架构 :浏览器(Browser)------服务端软件(server)
总结:BS本质上也是CS架构
1、web应用程序是什么?
WEB应用程序一般是B/S模式。Web应用程序首先是“应用程序”,和用标准的程序语言,如C、C++等编写出来的程序没有什么本质上的不同。然而Web应用程序又有自己独特的地方,就是它是基于Web的,而不是采用传统方法运行的。换句话说,它是典型的浏览器/服务器架构的产物。
本质上:浏览器是一个socket客户端,服务器是一个socket服务端
2、web框架
(1)知识回顾
HTTP协议详细:前端基础之HTTP协议介绍 - Xiao0101 - 博客园 (cnblogs.com)
(2)web框架介绍
Web框架(Web framework)是一种开发框架,用来支持动态网站、网络应用和网络服务的开发。这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方法。web框架已经实现了很多功能,开发人员使用框架提供的方法并且完成自己的业务逻辑,就能快速开发web应用了。浏览器和服务器的是基于HTTP协议进行通信的。也可以说web框架就是在以上十几行代码基础张扩展出来的,有很多简单方便使用的方法,大大提高了开发的效率。
(3)web框架的本质
所有的Web应用其实就是一个socket服务端, 而用户使用的浏览器就是一个socket客户端程序, 明白了Web框架的本质, 我们就可以实现自己的Web框架了。
(4)根据之前学的知识形成的自定义web框架
import socket server = socket.socket() # 默认就是基于网络的TCP协议 server.bind(("127.0.0.1",8080)) server.listen(5) while 1: conn,addr = server.accept() data = conn.recv(1024) print(data) # 将请求打印出来 conn.send(b"HTTP?1.1 200 OK\r\n\nOur destiny is our own!") conn.close()
将服务端运行,客户端访问服务端,运行结果如下:
HTTP协议规定了让大家发送消息、接收消息的时候有个格式依据以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
pycharm输出结果如下:
# 请求首行 b'GET / HTTP/1.1\r\n # 请求头(都是一大堆的K:V键值对) Host: 127.0.0.1:8080\r\n Connection: keep-alive\r\n Cache-Control: max-age=0\r\n sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"\r\n sec-ch-ua-mobile: ?0\r\n sec-ch-ua-platform: "Windows"\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 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.7\r\n Sec-Fetch-Site: none\r\n Sec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\n Sec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br, zstd\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: csrftoken=9qUCCe8NhifmyhgtznpwF9dtBc3Qi3mreGFBPup3hNpwtSNDaGdzpvQACQxUX8Je; sessionid=3o16gvni7tw8kvq01ydx0pwaunvt8r6m\r\n\ # 换行 r\n ' # 请求体 b'GET /favicon.ico HTTP/1.1\r\n Host: 127.0.0.1:8080\r\n Connection: keep-alive\r\n sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"\r\n sec-ch-ua-mobile: ?0\r\n User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36\r\n sec-ch-ua-platform: "Windows"\r\n Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\r\n Sec-Fetch-Site: same-origin\r\n Sec-Fetch-Mode: no-cors\r\n Sec-Fetch-Dest: image\r\nReferer: http://127.0.0.1:8080/\r\n Accept-Encoding: gzip, deflate, br, zstd\r\n Accept-Language: zh-CN,zh;q=0.9\r\n Cookie: csrftoken=9qUCCe8NhifmyhgtznpwF9dtBc3Qi3mreGFBPup3hNpwtSNDaGdzpvQACQxUX8Je; sessionid=3o16gvni7tw8kvq01ydx0pwaunvt8r6m\r\n\r\n'
可以说web服务的本质都是基于这简单的套接字程序扩展出来的。
(5)根据不同的路径返回不同的内容
通过以上,我们是实现了一个简易版的web框架
但是存在以下问题:
在用户访问不同网页时候,如何让我们的Web服务根据用户请求的URL不同而返回不同的内容呢?
解决方案:
-
HTTP请求数据
-
/favicon.ico
直接忽略 不影响判断 -
利用字符串切割和索引取值获取相应数据
其实很简单,我们可以从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断…
''' 根据不同的URL返回不同的内容 ''' import socket server = socket.socket() # 默认就是TCP协议 server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() # 三次四次挥手 data = conn.recv(1024) res = data.decode('utf8') conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 请求首行,请求头,空行 path = res.split(' ')[1] # 字符串切割获取地址 if path == '/index': # 判断地址 # conn.send(b'index') # 1.如果判断成功则发送请求体 with open(r'1.html','rb') as f: # 2.或者打开文件一内容作为请求体发送 data = f.read() conn.send(data) elif path == '/login': # 1.如果判断为login conn.send(b'login') # 2.就发送b'login'的请求体 else: conn.send(b'404 error') # 没匹配到则返回404 conn.close()
1.html
内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>title</title> </head> <body> <h1 style="color: #31b0d5">嗨,朋友!你来了,你好啊!</h1> </body> </html>
存在的问题:
- 如果网址路径很多,服务端代码重复(因为if…else…会变得非常多。)
- 手动处理http数据格式过于繁琐,并且只能拿到url后缀,其他数据获取繁琐
- 并发的问题
服务器和应用程序
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI是Python Web应用程序和Web服务器之间的一种标准接口,它定义了一个简单而通用的接口,使得不同的Web应用程序框架(如Flask、Django等)可以与不同的Web服务器(如Apache、Nginx等)进行交互。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
二、基于 wsgiref 模块搭建web框架
1、wsgiref 模块介绍
wsgiref
模块是Python标准库中的一个模块,用于实现WSGI(Web Server Gateway Interface)规范。
wsgiref
模块提供了一些工具和类,帮助开发者快速构建符合WSGI规范的应用程序和服务器。主要功能包括:
- WSGI应用程序开发:
wsgiref
模块提供了一些类和函数,帮助开发者编写符合WSGI规范的应用程序。开发者可以定义一个WSGI应用程序,接收HTTP请求,并返回HTTP响应。 - WSGI服务器实现:
wsgiref
模块还提供了一个简单的WSGI服务器实现,可以用于在开发和测试阶段运行WSGI应用程序。这样开发者可以在本地快速搭建一个简单的Web服务器来运行和测试他们的应用程序。 - 辅助功能: 除了WSGI应用程序和服务器之外,
wsgiref
模块还提供了一些辅助函数和类,用于处理HTTP请求、构建HTTP响应等操作。
2、使用wsgiref 模块的好处
-
wsgiref模块帮助我们封装了socket 代码
-
帮我们处理 http 格式的数据
-
请求来的时候帮助你自动拆分http格式数据并封装成非常方便处理的数据格式(类似于字典)
-
响应走的时候帮你将数据再打包成符合http格式的数据
3、实现代码
from wsgiref.simple_server import make_server # 以函数形式定义功能,扩展方便 def index_func(request): return 'index' def login_func(request): return 'login' def error(request): return '404 Not found' # 地址与功能的对应关系 urls = [ ('/index', index_func), ('/login', login_func) ] def run_server(request, response): """ 函数名定义什么都无所谓,这里我们使用run_server :param request:请求相关的所有数据,一个类似字典的形式,"PATH_INFO"正好就是我们要找的地址 wsgiref模块帮我们处理好HTTP格式的数据,封装成了字典让你更加方便的操作 :param response:响应相关的所有数据 :return:返回给浏览器的数据,返回格式必须是'return [二进制格式的数据]' 这种样式 """ response('200 OK', []) # 响应首行, 响应头 current_path = request.get("PATH_INFO") # 找到路径 func = None # 定义一个变量, 存储匹配到的函数名 for url in urls: if current_path == url[0]: func = url[1] # 如果匹配到了则将函数名赋值给func break # 匹配之后立刻结束循环 if func: # 然后判断一下func是否被赋值了(也就是是否匹配到了) data = func(request) # 执行函数拿到结果,request可有可无,但放进去以后好扩展 else: data = error(request) return [data.encode('utf-8')] if __name__ == '__main__': server = make_server('127.0.0.1', 8080, run_server) # 一旦被访问将会交给run_server处理 ''' 会实时监听127.0.0.1:8080地址,只要客户端来了 都会交给run函数处理(加括号触发run函数的运行) flask启动源码 make_server('127.0.0.1',8080,obj) __call__ ''' server.serve_forever() # 启动服务端并一直运行
产生的问题
- 网址很多的情况下如何匹配
- 网址多匹配如何解决
- 功能复杂代码块如何解决
看起来上面的代码还是要挨个写if判断,怎么办?
三、封装处理优化
1、根据功能划分模块
- 根据功能的不同拆分成不同的py文件
(1)views.py
- 存储路由与函数对应关系
# 功能函数 def register(request): return 'register' def login(request): return 'login' def index(request): return 'index' def error(request): with open(r'templates/error.html', 'r', encoding='utf8') as f: return f.read()
(2)urls.py
- 存放路径与功能的对应关系
from views import * # 后缀匹配 urls = ( ('/register', register), ('/login', login), ('/index', index), )
(3)server.py
- 存储启动及分配代码
from wsgiref.simple_server import make_server from urls import urls from views import * def run_server(request,response): """ :param request:请求相关的所有数据,一个类似字典的形式,"PATH_INFO"正好就是我们要找的地址 :param response:响应相关的所有数据 :return: """ response('200 OK',[]) # 响应首行, 响应头 current_path = request.get("PATH_INFO") # 找到路径 func = None # 定义一个变量, 存储匹配到的函数名 for url in urls: if current_path == url[0]: func = url[1] # 如果匹配到了则将函数名赋值给func break # 匹配之后立刻结束循环 if func: # 然后判断一下func是否被赋值了(也就是是否匹配到了) data = func(request) # 执行函数拿到结果,request可有可无,但放进去以后好扩展 else: data = error(request) return [data.encode('utf-8')] if __name__ == '__main__': server = make_server('127.0.0.1', 8080, run_server) # 一旦被访问将会交给run_server处理 server.serve_forever() # 启动服务端并一直运行
总结:
拆分后好处在于要想新增一个功能,只需要在views.py中编写函数,urls.py添加对应关系即可
3、模板文件与静态文件
(1)templates文件夹
- 存储html文件
(2)static文件夹
- 存储html页面所需静态资源
四、返回动静态页面
1、静态网页
- 页面上的数据是直接写死的,万年不变
- 如果不想仅仅返回几个字符串, 而是想给浏览器返回完整的HTML内容, 对此我们只需要通过 open 打开 HTML文件将内容读出来再发送给浏览器就行了
# 静态网页制作 def login_func(request): with open(r"./login.html", "r", encoding="utf-8")as f: res = f.read() # 打开文件读出内容,再返回文件内容 return res
2、动态网页
- 数据是实时获取的
- 后端获取当前时间展示到html页面上
- 数据是从数据库中获取的展示到html页面上
# 动态网页制作 def get_time(env): current_time = datetime.datetime.now().strftime('%Y-%m-%d %X') # 如何将后端获取到数据"传递"给html文件? with open(r'templates/03 mytime.html', 'r', encoding='utf8') as f: data = f.read() # 得到的data就是一堆字符串 data = data.replace('fwefwef', current_time) # 在后端将html页面处理好之后再返回给前端 return data
3、练习
将一个字典传递给html文件,并且可以在文件上方便快捷的操作字典数据
def get_dict(env): user_dic = {'username': 'xiao', 'age': 18} with open(r'templates/04 get_dict.html','r',encoding='utf8') as f: data = f.read() tmp = Template(data) res = tmp.render(user=user_dic) # 给get_dict.html传递了一个值,页面上通过变量名user就能够拿到user_dict return res
五、模版语法之Jinja2
举例演示模版语法与之前的后端到前端传输局方式的不同
1、原始方式
- 页面展示当前时间
(1)后端
def get_time(request): # 1.获取当前时间 import time c_time = time.strftime('%Y-%m-%d %X') # 2.读取html文件 with open(r'templates/get_time.html','r',encoding='utf8') as f: data = f.read() # 3.思考:如何给字符串添加一些额外的字符串数据>>>:字符串替换 new_data = data.replace('random_str',c_time) return new_data
(2)前端
<h1>展示后端获取的时间数据</h1> <span>random_str</span>
2、jinja2模板语法
(1)下载安装
- 第三方模块需要先下载后使用
pip3 install jinja2
(2)功能和语法
① 功能
支持将数据传递到html页面并提供近似于后端的处理方式简单快捷的操作数据
② 语法
变量插值: 使用双大括号{{ }}
来插入变量值到模板中。
Hello, {{ name }}!
表达式: 可以在{{ }}
中使用表达式。
{{ 2 + 2 }}
过滤器:使用管道符|
应用过滤器对变量进行处理。
{{ name|capitalize }}
控制结构: 使用{% %}
来表示控制结构,如条件语句和循环。
{% if condition %} Content to show if condition is true. {% endif %} {% for item in items %} {{ item }} {% endfor %}
模板继承: 使用{% extends %}
和{% block %}
来实现模板继承和重写。
{% extends "base.html" %} {% block content %} Content specific to this template. {% endblock %}
宏(Macro): 定义可重用的代码块。
{% macro input(name, value='') %} <input type="text" name="{{ name }}" value="{{ value }}"> {% endmacro %}
(3)views.py
from jinja2 import Template def get_dict(request): user_dict = {'name': 'xiao', 'pwd': 123, 'hobby': 'read'} new_list = [11, 22, 33, 44, 55, 66] with open(r'templates/get_dict.html', 'r', encoding='utf8') as f: data = f.read() temp_obj = Template(data) res = temp_obj.render({'user':user_dict,'new_list':new_list}) return res
(4)templates
- --get_dict.html
<h1>字典数据展示</h1> <p>{{ user }}</p> <p>{{ user.name }}</p> <p>{{ user['pwd'] }}</p> <p>{{ user.get('hobby') }}</p> <h1>列表数据展示</h1> <p> {% for i in new_list%} <span>元素:{{ i }}</span> {% endfor %} </p>
pip install jinja2 """模版语法是在前端起作用的,后端不支持""" # 模版语法 {{ user }} {{ user.get('username') }} {{ user.age }} {{ user['hobby'] }} {% for user_dict in user_list %} <tr> <td>{{ user_dict.id}}</td> <td>{{ user_dict.username}}</td> <td>{{ user_dict.password}}</td> <td>{{ user_dict.hobby}}</td> </tr> {% endfor %}
六、自定义简易版本web框架请求流程图
流程图流程
浏览器客户端 wsgiref模块 请求来:处理浏览器请求,解析浏览器HTTP格式的数据,封装成大字典(PATH_INFO中存放的用户访问资源的路径) 响应去:将数据打包成符合HTTP格式,在返回给浏览器 后端: urls.py:找出用户输入的路径有么有与视图层的对应关系,如果有则取到views.py找对应的视图函数。 view.py: 功能1(静态):视图函数找templates中的html文件,返回给wsgiref做HTTP格式的封装处理,再返回给浏览器. 功能2(动态):视图函数通过pymysql链接数据库, 通过jinja2模板语法将数据库中取出的数据在tmpelates文件夹下的html文件做一个数据的动态渲染, 最后返回给wsgiref做HTTP格式的封包处理, 再返回给浏览器. 功能3(动态):也可以通过jinja2模板语法对tmpelates文件夹下的html文件进行数据的动态渲染, 渲染完毕, 再经过wsgiref做HTTP格式的封包处理, 再返回给浏览器. templates:html文件 数据库
七、总结
1、urls.py
- 后缀与函数名对应关系
- ('/index',register)
- 后缀专业名词称之为'路由'
- 函数名专业名词称之为'视图函数'
- urls.py专业名词称之为'路由层'
2、views.py
- 专门编写业务逻辑代码
- 可以是函数 也可以是类
- 函数专业名词称之为'视图函数'
- 类专业名词称之为'视图类'
- views.py专业名词称之为'视图层'
3、templates文件夹
- 专门存储html文件
- html文件专业名词称之为'模板文件'
- templates文件夹专业名词称之为'模板层'
4、static文件夹
- 专门存储静态文件资源
- 页面所需css文件、js文件、图片文件、第三方文件可统称为'静态资源'
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现