当前位置: 首页 > article >正文

详解Python web框架到底是怎么来的?

前言

咱都知道软件开发的架构有两种,分别是C/S架构与B/S架构,本质上都是借助socket实现网络通信,因此Django作为一个web框架本质上也是一个socket服务端,浏览器则是客户端,我们可以自己实现简易的web框架来更好的理解Django。

Web框架推导

在介绍socket时,我们可以自定义一个socket服务端,这个socket服务端是最最原始的web框架,web服务的本质是基于下述代码扩展出来的。

import socket


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(1024)
    print(data)
    conn.send(b'web frame')
    conn.close()

但是使用上述代码开启服务端,在浏览器上访问127.0.0.1:80返回的结果是发送的相应无效,原因就是上述socket服务端向客户端发送的数据格式不符合HTTP协议的要求,如果想让客户端浏览器收到服务端发送的数据,就要遵循HTTP协议,因此服务端在向客户端发送数据的时候必须按照HTTP协议的规则加上相应状态行。

# 符合http协议的socket服务端
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(1024)
    print(data)
    
    # 响应状态行
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    conn.send(b'web frame')
    conn.close()

上述代码就是web框架的本质,虽然简陋但是可以对上述代码进行装饰。在使用浏览器上网的时候,访问不同的网址就会返回不同的页面或者是内容,根据已有的代码就可以通过if判断实现客户端访问不同的url返回不同的数据或者页面。因此如何获取用户访问的url是什么就是需要解决的问题了,可以将浏览器向服务端发送请求时的数据打印出来进行分析,比如浏览器请求的url如下127.0.0.1:8080/index得到的data数据如下所示,发现第一行开始就可以看到/index,因此可以使用代码获取客户端访问的url:

'''
b'GET /index HTTP/1.1\r\n   # \r\n表示换行
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\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/81.0.4044.138 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;q=0.9\r\n
Sec-Fetch-Site: cross-site\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'
'''
# if判断实现根据不同的url返回不同的数据或页面
import socket


server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,addr = server.accept()
    data = conn.recv(1024)
    print(data)
    data_list = data.decode('utf-8').split(' ')  # 将数据以空格切分
    print(data_list)
    current_path = data_list[1]  # 列表中的第二个元素就是路由
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    if current_path == '/index':
        conn.send(b'index')
    elif current_path == '/login':
        conn.send(b'login')
    else:
        conn.send(b'web frame')
    conn.close()

使用if判断虽然解决了访问不同url返回不同数据的需求,但是如果有非常多的路径要怎么办嘞?难道一个一个的挨个判断吗?显然不现实,所以可以采用函数与列表结合实现更简洁的实现不同url返回不同数据的需求。

import socket


def index(url):
    s = 'this is {}'.format(url)
    return bytes(s, encoding='utf-8')


def login(url):
    s = 'this is {}'.format(url)
    return bytes(s, encoding='utf-8')

# 定义一个url与函数功能的对应关系的列表
url_list = [
    ('/index', index),
    ('/login', login)
]

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    data = conn.recv(1024)
    print(data)
    data_list = data.decode('utf-8').split(' ')
    print(data_list)
    current_path = data_list[1]
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

    # 定义一个变量名用以保存函数的内存地址
    func = None
    for i in url_list:
        if i[0] == current_path:
            func = i[1]
            break
    if func:
        res = func(current_path)
    else:
        res = b'404 not found'
    conn.send(res)
    conn.close()

访问不同的url时,服务端不仅可以返回不同的数据也可以返回HTML页面:

import socket
import datetime

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

def index(url):
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
    with open('time.time.html','r',encoding='utf-8') as f:

        data = f.read()
        
    # 在网页上定义好特殊符号,用字符串方法替换
    s = data.replace('sfsd',current_time)
    return bytes(s, encoding='utf-8')

def login(url):
    with open('myhtml.html','r',encoding='utf-8') as f:
        res = f.read()
    return bytes(res,encoding='utf-8')

# 定义一个url与函数功能的对应关系的列表
url_list = [
    # (路由,视图函数)
    ('/index',index),
    ('/login',login)
]
while True:
    conn,addr = server.accept()
    data = conn.recv(1024)
    print(data)
    data_list = data.decode('utf-8').split(' ')
    print(data_list)
    current_path = data_list[1]
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')

    # 定义一个变量名用以保存函数的内存地址
    func = None
    for i in url_list:
        if i[0] == current_path:
            func = i[1]
            break
    if func:
        res = func(current_path)
    else:
        res = b'404 not found'
    conn.send(res)
    conn.close()

通过上述的web框架推导过程已经可以实现客户端浏览器访问不同的url服务端能够返回不同的数据或者页面了,但是,重点来了,但是代码重复,每个开发web服务端的人都需要重新洗这段代码,并且是手动处理的http格式的数据,并且还只能拿到url后缀,还有就是并发的问题,说这么多并不是说上述代码推导过程没用,上述代码推导只是让小伙伴们更加理解web框架的本质,而提到的问题可以借助其他模块解决即wsgiref模块

wsgiref模块

借助wsgiref模块对http格式的数据进行处理时会方便很多,首先看一下该模块的基本用法:

from wsgiref.simple_server import make_server


def run(env,response):
    '''

    :param env: 请求相关的所有数据
    :param response: 响应相关的所有数据
    :return: 返回给浏览器的数据,return [b'']
    '''
    # 响应首行 响应头
    response('200 OK', [])

    # env就是字典格式的HTTP数据
    print(env)
    current_path = env.get('PATH_INFO')  # 获取客户端浏览器请求的路由
    # wsgiref模块帮你处理号HTTP格式数据,封装成成字典
    if current_path == '/index':
        return [b'index']
    elif current_path == '/login':
        return [b'welcome']
    else:
        return [b'404 error']


if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    # 实时监听127.0.0.1:8080地址,只要有客户端来了都会交给run函数处理,即触发run函数的运行
    server.serve_forever()  # 启动服务端

借助wsgiref模块对http数据的处理,可以将web服务端的代码进行修改:

from wsgiref.simple_server import make_server
import datetime

# 将env将参数传给视图函数后,视图函数就可以根据浏览器发送给服务端的数据完成各种事件
def index(env):
    with open(r'template/myhtml.html','r',encoding='utf-8') as f:
        res = f.read()
        return res

def longin(env):
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
    with open(r'template/time.time.html','r',encoding='utf-8') as f:
        res = f.read()
    res = res.replace('sfsd',current_time)
    return res

urls = [
    ('/index',index),
    ('/loging',longin)
]


def run(env,response):
    '''

    :param env: 请求相关的所有数据
    :param response: 响应相关的所有数据
    :return: 返回给浏览器的数据,return [b'']
    '''
    # 响应首行 响应头
    response('200 OK', [])

    # env就是字典格式的HTTP数据
    print(env)
    current_path = env.get('PATH_INFO')
    # wsgiref模块帮你处理号HTTP格式数据,封装成成字典
    func = None
    for i in url_list:
        if current_path == i[0]:
            func = i[1]
            break
    if func:
        res = func(env)
    else:
        res = '404 not found'
    return [res.encode()]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    # 实时监听127.0.0.1:8080地址,只要有客户端来了都会交给run函数处理,即触发run函数的运行
    server.serve_forever()  # 启动服务端

看来看去上述代码还可以再改改,代码全部放在一个py文件中是不合理的,后期功能如果增多的话代码的管理或者功能的扩展都会变得困难,因此需要分文件,可以按照下述方式进行分文件,不同的文件中放不同的代码:

urls.py				路由与视图函数对应关系
views.py			视图函数的逻辑(后端业务逻辑)
templates文件夹	  专门存储html文件
sever.py			服务端文件

具体代码如下:

# sever.py
from wsgiref.simple_server import make_server
from urls import  urls
from views import *



def run(env,response):
    '''

    :param env: 请求相关的所有数据
    :param response: 响应相关的所有数据
    :return: 返回给浏览器的数据,return [b'']
    '''
    # 响应首行 响应头
    response('200 OK', [])

    # env就是字典格式的HTTP数据
    print(env)
    current_path = env.get('PATH_INFO')
    # wsgiref模块帮你处理号HTTP格式数据,封装成成字典
    func = None
    for i in urls:
        if current_path == i[0]:
            func = i[1]
            break
    if func:
        res = func(env)
    else:
        res = '404 not found'
    return [res.encode()]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    # 实时监听127.0.0.1:8080地址,只要有客户端来了都会交给run函数处理,即触发run函数的运行
    server.serve_forever()  # 启动服务端
    
    
# urls.py
import views

urls = [
    ('/index',views.index),
    ('/loging',views.longin)
]


# views.py
import datetime

def index(env):
    with open(r'template/myhtml.html','r',encoding='utf-8') as f:
        res = f.read()
        return res

def longin(env):
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %X')
    with open(r'template/time.time.html','r',encoding='utf-8') as f:
        res = f.read()
    res = res.replace('sfsd',current_time)
    return res

总结

上文对web服务端是如何出现的进行了简单的推导,在实际开发中无需使用上述代码进行开发,使用现有的web框架比如django或者flask就可以完成,这两种框架都是基于socket服务端并且符合http协议的web框架,介绍上述推导过程希望可以帮助自己对web请求的理解。


http://www.kler.cn/news/16441.html

相关文章:

  • 设计 模式
  • C#手麻系统源码, 基于前端Winform+后端WCF +sqlserver 开发
  • KALI入门到高级【第五章】
  • Android 11.0 系统systemui状态栏下拉左滑显示通知栏右滑显示控制中心模块的流程分析
  • MySQL表的插入详解
  • 12 网络管理的封装
  • SpringBoot 多数据源及事务解决方案
  • 实验5 彩色图像处理与图像变换
  • C语言学习第一次总结
  • Qt 信号与槽机制
  • keepalived脑裂现象
  • Android Input系统事件分发分析
  • 题目 3166: 蓝桥杯2023年第十四届省赛真题-阶乘的和--不能完全通过,最好情况通过67.
  • “双碳”目标下二氧化碳地质封存技术应用前景及模型构建实践方法与讨论
  • 设备仪器仪表盘读数识别算法 yolov5
  • Eplan 部件库导入部件的方法
  • 自动化运维工具Ansible之playbooks剧本
  • Nginx原理解析
  • (基础算法)高精度加法,高精度减法
  • 【C语言】struct结构体
  • Linux拓展:链接库
  • 数据结构(六)—— 二叉树(3)
  • 【Linux多线程编程-自学记录】05.取消线程
  • Tomcat8和Tomcat9乱码问题
  • 浪潮之巅 OpenAI有可能是历史上第一个10万亿美元的公司
  • 一篇带你了解大厂都在用的DDD领域驱动设计
  • 【Canvas入门】从零开始在Canvas上绘制简单的动画
  • 高性能定时器介绍及代码逐行解析--时间堆
  • 走进小程序【十一】微信小程序【使用Echarts 和 腾讯地图】
  • R语言 | 数据框