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')

浏览器无法访问

image

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

image

原因: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')

image

[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()

端口后面没有后缀访问

image任意后缀访问

image

image

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

image

[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文件

image

image

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()

image

 

image

image

 

image

image

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

templates文件夹        存储项目所需的HTML文件

image

  新的问题是,项目中的HTML文件,也有可能需要用到css、js、第三方框架文件

4.5 优化4:静态优化

  HTML所需要的css、js、第三方框架文件都是很少改变的文件

  所以可以统一放在某个文件夹下

static文件夹    存储项目所需的“静态文件”

image

总结:拆分后如果想要新增一个功能,只需要在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>

image

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>

image

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

6.2 访问数据库

image

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>

image

image

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/

 

posted @ 2025-01-12 20:01  pythondjango  阅读(108)  评论(0)    收藏  举报