wsgi的实质

wsgi的实质

wsgi其实就是一个接口,作用就是连接服务器和框架的文件,必须按照一定的协议来写

为了理解其原理,先不管协议有什么规定,先来看看内在的处理问题实质是什么

不定导包

这个具体叫啥专业名字我也没有查,简单说就是平时我们导入一个包的语法为

import 包名

当然这个包可以是一个py文件,这种导的方式就是直接导入了整个py文件里面的定义的所有函数和所有类

服务器存在这种需求,如果存在很多静态文件和动态文件,用不同的文件夹来装,那么每次去找路径非常麻烦,而且会存在每次客户端需要访问的网页名不一样

一般的少侠想到的是使用if来进行判断

然后骚一点的会创建一个列表把有的py文件名放进去,使用in方法来判断

种里使用另一种常用的

__import__(名)

这种方式使用变量进行代替,变量就是客户端提供的需要访问的文件名,import只是导入了对应的文件

那好,这种方式就直接将py文件放在一个文件夹下,然后导入这个文件夹下面的py文件.但是这个文件夹并没有在系统默认的导包路径里面,直接导这种导入会报错

好的,现在解决这个问题

import sys
sys.path.append('装py文件的文件夹')

这样的话就把对应的文件夹放在了导入路径path里面,那么只要你的名字命名不重名,在文件夹下面的py文件都能够导入,所以前面的不定名称的导入方式就能够直接导入对应的py文件

接下来就是py文件里面函数和类,这里没所谓的,函数和类都需要注意,如果你在py文件里面定义的函数名很随意,每个py文件里面的函数名都不相同,则这就导致导入的的时候会出问题

__import__(py文件名) # 这就已经导入了py文件
__import__(py文件名).函数名() # 这就表示调用py文件里面的函数

每个py文件就已经有自己的文件名了,现在如果里面的函数名不相同,试想,客户端申请的文件名已经对应上了,剩下的就是你服务端去找出文件调用函数完成功能,那么函数名如果一个文件就对应一个函数名,试想每次都导入文件夹后再判断函数名列表,是不是很不和谐

如果所有的py文件里面都去定义同一个函数名方法,比如定义一个

def show():
    # 写对应文件所需要的功能
    pass

那么在发生调用的时候就统一写上一个函数名,只要他们的py文件名不同就不会是同一个函数,这样就区分开不同文件的不同功能

__import__(py文件名).show()

这样就行了,以上就完成了随便你客户端怎么输入请求,只要把你请求的文件名分离出来给我,我再去找对应名字的py文件就可以了,如果文件不存在则发生调用的地方就会报错,这里直接捕获异常就行了.

理解了这种思路之后再来看看wsgi的原理

wsgi原理

wsgi作为一个接口需要进行连接服务器和框架.

这就意味着服务器和框架之间是分离的,我服务器不管你那么多逼逼事情,我只需要调用框架中的入口文件,并传入参数,你入口文件自己解析去

这就是服务器这里需要做的事情,那么服务器调用

import 接口文件名
response_data = 接口文件名.application(env, server_function)

服务端就这么写,为什么是两个参数,兄弟,客户端发送的请求只有服务端才能收到,服务端收到请求后解析请求的文件名,你总得告诉框架这个文件名吧,不然框架怎么知道要去调用哪个文件来执行相应的功能,除此之外别人请求中的参数也需要给框架,比如别人登录,你都不给框架框架怎么去数据库中查有没有这个用户?

所以第一个参数env就是包含请求的一些信息和参数,反正服务端很懒,直接装成字典扔给接口文件

至于第二个参数是服务端这边的一个函数的名称,将函数名传过去其实传递的这个函数的功能,至于这个函数有几个参数别人接口文件怎么知道?

这里先理解为什么要传函数过去,这个函数实际上就是在接口文件中执行,因为你传递了客户端请求信息过去,接口文件根据请求信息做出相应的反应,去调用对应的py文件,如果你客户端这边只给了请求的文件名,那么接口文件直接完成并返回就行了,根本不用server_function,一个参数完全足够

但是,如果这边还有用户的cookie之类的信息,或者其他信息呢,你也可以说在接口文件那边调用相应的py文件执行完了返回过来就是了,想的很不错,但是如果这个信息是需要在服务器这边执行并且修改服务器这边的类属性或者对象属性呢,你这全部交给接口文件去做然后再返回?然后服务这边再写相应的代码更改,对,这个方法可以行,完全没有问题,但是朋友,如果服务器是你写的你就这个搞没问题,但是服务器是Apache或者Nginx,你哪有权限去写别人的服务器代码?

所以别人直接把方法传给你,你去把参数搞出来执行了就行了,至于需要几个参数,这是看服务器这边的规定,你得根据服务器这边的规定去写

那么为了统一规范,于是大家都约定好,application就是你接口文件里面必须写的函数,你自己的服务器的话随便你写什么,反正你用别人的服务器就必须这么写,然后就是两个参数,第一个是一个字典,里面存着客户端的请求内容信息,第二个是服务器这边的执行函数,丢给接口这边去执行,规定好了,就这两个参数

接下来就是把接口文件写好

撸接口文件

def index():
    return '这是动态首页'

def detail():
    return '这里是动态详情页'

def application(env, out_function):
    # env是从外部传入的字典,可能存在多个参数
    function_name = env.get('file_name')
    # 强制执行
    try: # 有可能函数不存在,则进行捕获异常
        response_data = eval(function_name+'()')
        # 表示执行成功
        end_string = '200 ok'
    except:
        # 如果报异常,则表明函数不存在
        end_string = '404 not found'
        response_data = '' # 回复内容直接给空
    response_head = out_function(end_string)
    return response_head + response_data

首先在接口文件里面的application是必须定义的,另外两个函数是自定义的,我这里直接返回的是字符串,真正的框架在这里的函数会继续调用存放在框架上的静态文件和动态文件,这些都可以直接import导入,或者直接使用with open去打开文件拿去内容返回出去,这些都没有关系

关键看application函数,我在这里只接收客户端请求的文件名,在env中使用get取,这个打死不会报错,但是不一定有,没有就是空嘛,无所谓

eval()这个函数是直接强制执行,拿到请求文件名后,我自己定义了一个和文件名相同的函数,这里应该调用这个函数,比如别人访问的是首页:index,我拿到的虽然是index但是这个是字符串,兄弟,字符串能用字符串()这种方式执行函数么?谁教你这么搞的,不信你就试试

这里使用文件名和字符串()拼接成 index() 这个字符串,还是字符串,不能直接执行,那么eval的用处就是将字符串直接当成代码执行.那么如果是代码index()就表示函数,当然可以直接执行

如果输入的文件名这里没有对应的函数名,那么就是客户端输入的文件名有误,则进行对应的404处理

out_function对应的就是从服务器那边传过来的方法,end_srting就是执行状态码,这里直接调用函数,当然真正的服务器那边是没有返回值的,只要执行,服务器那边就把对应需要返回的头文件合成好了,我这里是自己写的,所以这里就直接拼接响应内容了

核心就在于接口文件中的application方法将返回对应的数据.只要服务器接收后自己拼接一下就能返回给用户

小结一下

wsgi实际上就是一个接口,对接客户端和框架的,原来没有框架,服务器想要规范化处理动态文件和静态文件,则使用的是不定导包的方式,使用变量来搞,后面别人框架写的更规范,功能更牛逼,服务器这边一看,好嘛,你那么牛逼,那就面向对象的思想,你搞一个接口文件,我调用这个接口文件,这样我们就把这个锅甩给框架了.

当然我们要自己写框架的可能性几乎为零,我认为我还是没有那个实力搞,搞懂这个只是去搞懂他们之间的原理以及怎么实现的,尤其是导包这种巧妙的解决方式,并且能将一个py文件中的函数功能传递出去扔给其他py文件去执行,这些思想非常巧妙,多学习,以后对面向对象的思想理解就更深入

以下给出服务端代码,随意写的

from socket import *
from multiprocessing import Process
import sys


# 类的封装先创建一个类
class Static_Server(object):
    # 在外面定义服务器查询的根目录
    SERVER_ROOT = './static'

    def __init__(self):
        # 初始化将装动态文件的文件夹添加到系统的导包路径里面去
        sys.path.append('file_package')

    def response_function(self, new_server, client_info):
        data = new_server.recv(1024)
        if not data:  # 客户端断开连接的时候会发送一个空,此时直接关闭
            new_server.close()
            return
        # print(f'来自{client_info}的请求:{data}')
        # 服务器回复
        # 先解析对应的请求信息,拆分成为列表,并且进行取出第一行,转码后取出请求的文件名
        request_file_name = data.splitlines()[0].decode('utf-8').split(' ')[1]
        # 如果请求的页面不存在则需要进行更改回复内容
        # 如果访问的是动态网页,这里就只写以py结尾的
        print(request_file_name)
        if request_file_name.endswith('.py'):
            # 表明是要访问的动态网页,进行导包方式访问,导包,然后执行函数,然后编码
            try:
                response_body = __import__(request_file_name[1:-3]).show().encode('utf-8')  # 0索引是/ 最后三个是.py 只取中间的名字
            except:  # 如果浏览器给的py文件名本身就不存在则需要返回404
                response_data = self.reduce_404()
                new_server.send(response_data)
                new_server.close()
            else:  #
                # 拿到执行的结果后进行对应的服务器响应
                # 封装成方法
                self.server_response(new_server, response_body)
        else:  # 如果不是py文件则表示使用的是静态的访问方式
            try:
                # 进行文件打开
                with open(self.SERVER_ROOT + request_file_name, 'rb') as f:
                    # 读取出文件信息
                    response_body = f.read()
            except:
                # 如果报错则返回404
                response_data = self.reduce_404()
                new_server.send(response_data)  # 把动态文件执行的数据进行范围
                new_server.close()
            else:
                self.server_response(new_server, response_body)

    def server_response(self, new_server, response_body):
        response_heads = b'HTTP/1.1 200 OK\r\n'
        # 配置解释的类型
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        # 发送响应消息
        response_data = response_heads + response_body
        new_server.send(response_data)  # 把动态文件执行的数据进行范围
        new_server.close()

    def reduce_404(self):
        response_heads = b'HTTP/1.1 404 not find\r\n'
        # 配置解释的类型
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        # 响应内容
        response_body = '没得'.encode('utf-8')
        # 拼接响应内容
        response_data = response_heads + response_body
        return response_data

    def main(self):
        # 创建对象
        server = socket(AF_INET, SOCK_STREAM)
        # 配置断开释放端口功能
        server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        # 挂起服务器
        server.bind(('', 8888))
        # 配置为监听状态
        server.listen()
        while True:
            # 客户端连接上就创建新对象
            new_server, client_info = server.accept()
            # 创建进程
            p = Process(target=self.response_function, args=(new_server, client_info))
            # 开启进程
            p.start()
            # 关闭外部的新对象
            new_server.close()


if __name__ == '__main__':
    # 实例化对象
    class_server = Static_Server()
    # 对象调用方法
    class_server.main()

使用wsgi的思路改写代码

from socket import *
from multiprocessing import Process
import sys


# 类的封装先创建一个类
class StaticServer(object):
    # 在外面定义服务器查询的根目录
    SERVER_ROOT = './static'

    def __init__(self):
        # 初始化将装动态文件的文件夹添加到系统的导包路径里面去
        # sys.path.append('file_package')
        # 使用wsgi的话就不需要使用动态导包的方式
        pass

    def response_function(self, new_server, client_info):
        data = new_server.recv(1024)
        if not data:  # 客户端断开连接的时候会发送一个空,此时直接关闭
            new_server.close()
            return
        # 服务器回复
        # 先解析对应的请求信息,拆分成为列表,并且进行取出第一行,转码后取出请求的文件名
        request_file_name = data.splitlines()[0].decode('utf-8').split(' ')[1]
        # 如果请求的页面不存在则需要进行更改回复内容
        # 如果访问的是动态网页,这里就只写以py结尾的
        if request_file_name.endswith('.py'):
            # 表明是要访问的动态网页,进行导包方式访问,导包,然后执行函数,然后编码
            try:
                # 使用wsgi在这里进行更改
                # 准备好字典
                env = {
                    'file_name': request_file_name[1:-3],
                }
                import wsgi.appl
                # 获得返回值 其实按照原来的写法这里已经结束了,不需要第二个参数,但是wsgi的规范中必须有,所以还是多余的写一个函数来处理前面的响应头的问题
                response_data = wsgi.appl.application(env, self.wsgi_need_function).encode('utf-8')
            except:  # 如果浏览器给的py文件名本身就不存在则需要返回404
                response_data = self.reduce_404()

            new_server.send(response_data)
            new_server.close()
        else:  # 如果不是py文件则表示使用的是静态的访问方式
            try:
                # 进行文件打开
                with open(self.SERVER_ROOT + request_file_name, 'rb') as f:
                    # 读取出文件信息
                    response_body = f.read()
            except:
                # 如果报错则返回404
                response_data = self.reduce_404()
                new_server.send(response_data)  # 把动态文件执行的数据进行范围
                new_server.close()
            else:
                self.server_response(new_server, response_body)

    def wsgi_need_function(self, end_string):
        # 处理头文件
        response_heads = f'HTTP/1.1 {end_string}\r\n'
        # 配置解释的类型
        response_heads += 'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        return response_heads

    def server_response(self, new_server, response_body):
        response_heads = b'HTTP/1.1 200 OK\r\n'
        # 配置解释的类型
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        # 发送响应消息
        response_data = response_heads + response_body
        new_server.send(response_data)  # 把动态文件执行的数据进行范围
        new_server.close()

    def reduce_404(self):
        response_heads = b'HTTP/1.1 404 not find\r\n'
        # 配置解释的类型
        response_heads += b'Content-Type: text/html; charset=UTF-8\r\n\r\n'
        # 响应内容
        response_body = '没得'.encode('utf-8')
        # 拼接响应内容
        response_data = response_heads + response_body
        return response_data

    def main(self):
        # 创建对象
        server = socket(AF_INET, SOCK_STREAM)
        # 配置断开释放端口功能
        server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        # 挂起服务器
        server.bind(('', 8888))
        # 配置为监听状态
        server.listen()
        while True:
            # 客户端连接上就创建新对象
            new_server, client_info = server.accept()
            # 创建进程
            p = Process(target=self.response_function, args=(new_server, client_info))
            # 开启进程
            p.start()
            # 关闭外部的新对象
            new_server.close()


if __name__ == '__main__':
    # 实例化对象
    class_server = StaticServer()
    # 对象调用方法
    class_server.main()

猜你喜欢

转载自blog.csdn.net/weixin_43959953/article/details/85040451