详解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请求的理解。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你! 

你可能感兴趣的:(软件测试,python,前端,开发语言,功能测试,软件测试,自动化测试,程序人生)