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规范的应用程序和服务器。主要功能包括:

  1. WSGI应用程序开发: wsgiref模块提供了一些类和函数,帮助开发者编写符合WSGI规范的应用程序。开发者可以定义一个WSGI应用程序,接收HTTP请求,并返回HTTP响应。
  2. WSGI服务器实现: wsgiref模块还提供了一个简单的WSGI服务器实现,可以用于在开发和测试阶段运行WSGI应用程序。这样开发者可以在本地快速搭建一个简单的Web服务器来运行和测试他们的应用程序。
  3. 辅助功能: 除了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添加对应关系即可

2、模板文件与静态文件

(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文件、图片文件、第三方文件可统称为’静态资源’
  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值