• 从零开始搭建简易的异步非阻塞web框架


    主要流程:http请求—>套接字—>初始化请求数据—>路由匹配—>视图函数处理(访问数据库,识别用户等)—>(模板渲染)—>返回HTML或字符串。

    socket服务

    基本思路:通过IO多路复用实现多用户连接

            #windows使用select监听
            from socket import *
            import select   
            sever = socket()
            sever.bind(('127.0.0.1', self.port))
            sever.listen(5)
            sever.setblocking(False) #非阻塞
            inputs = []
            inputs.append(sever)
            while True:
                rlist, wlist, elist = select.select(inputs, [], [], 0.2)
                for items in rlist:
                    if items == sever:
                        conn, addr = items.accept()
                        conn.setblocking(False)
                        inputs.append(conn)
                    else:
                        info = b''
                        while True:
                            try:
                                chunk = items.recv(1024)
                                info += chunk
                            except Exception:
                                break

    初始化请求

    class HTTPRequset(object):
        def __init__(self,request):
            self.request_body = ''
            self.request_header = ''
            self.request = request.decode()
            self.method = ''
            self.url = ''
            self.protocol = ''
            self.header_dict = {}
            self.initialization()
            self.initialization_header()
    
        def initialization(self):
            content_list=self.request.split('
    
    ',1)
            if len(content_list) == 2:
                self.request_header,self.request_body = content_list
            else:
                self.request_header = content_list[0]
    
        def initialization_header(self):
            line=self.request_header.split('
    ')
            if len(line[0].split(' ', 2)) == 3:
                self.method,self.url,self.protocol=line[0].split(' ',2)
            for i in line[1:]:
                try:
                    fields,value=i.split(':',1)
                    self.header_dict[fields] = value.strip()
                except Exception:
                    pass
    http解析 类

     路由匹配

    这里仿照Flask的路由匹配机制:装饰器,并且可以和socket服务器一起封装成一个类

    class EasyWeb():
        def __init__(self,port):
            self.port = port
            self.request = ''
            self.url_rule = {}
        
        #用装饰器进行路由配置
        def route(self,url):
            def deco(func):
                self.url_rule[url] = func #加载函数时自动添加
                return func
            return deco
    
        def run(self):
            self.sever = socket()
            self.sever.bind(('127.0.0.1', self.port))
            self.sever.listen(5)
            self.sever.setblocking(False)
            inputs = []
            inputs.append(self.sever)
            while True:
                rlist, wlist, elist = select.select(inputs, [], [], 0.2)
                for items in rlist:
                    if items == self.sever:
                        conn, addr = items.accept()
                        conn.setblocking(False)
                        inputs.append(conn)
                    else:
                        info = b''
                        while True:
                            try:
                                chunk = items.recv(1024)
                                info += chunk
                            except Exception:
                                break
    
                        request=HTTPRequset(info)  #初始化请求
                        url = request.url
                        print(self.url_rule)
                        func=self.url_rule.get(url)
                        if not func:
                            items.sendall(b'404')
                        else:
                            response=func(request)
                            items.sendall(response.encode('utf8'))
                        items.close()
                        inputs.remove(items)

     以上就可以直接实现给浏览器返回"hello world",这也是常用的同步请求

    再进一步实现异步。

    异步

      如果函数返回的是一个字符串,那么就直接给浏览器返回,如果是一个生成器,就不断开连接,开启新的进程调用回调函数执行耗时间的操作,执行完之后修改某一个状态。完整代码如下

    #!/usr/bin/env python
    # -*- coding:utf8 -*-
    from socket import *
    import select
    from threading import Thread
    
    class Future:
        def __init__(self,callback):
            self.__result = ''
            self.state = False
            self.callback = callback
        def set_result(self,result):
            self.__result = result
    
        @property
        def result(self):
            return self.__result
    
        def finish(self):
            #回调函数的最后需要执行它,用来判断是否需要返回值
            self.state = True  
    
    
    class HTTPRequset(object):
        def __init__(self, request):
            self.request_body = ''
            self.request_header = ''
            self.request = request.decode()
            self.method = ''
            self.url = ''
            self.protocol = ''
            self.header_dict = {}
            self.initialization()
            self.initialization_header()
    
        def initialization(self):
            content_list = self.request.split('
    
    ', 1)
            if len(content_list) == 2:
                self.request_header, self.request_body = content_list
            else:
                self.request_header = content_list[0]
    
        def initialization_header(self):
            line = self.request_header.split('
    ')
            if len(line[0].split(' ', 2)) == 3:
                self.method, self.url, self.protocol = line[0].split(' ', 2)
            for i in line[1:]:
                try:
                    fields, value = i.split(':', 1)
                    self.header_dict[fields] = value.strip()
                except Exception:
                    pass
    
    
    class EasyWeb():
        def __init__(self, port):
            self.port = port
            self.request = ''
            self.url_rule = {}
            self.asyn = {}
            # @root.route('/index/')
        # 用装饰器进行路由配置
        def route(self, url):
            def deco(func):
                self.url_rule[url] = func
                return func
    
            return deco
    
        def run(self):
            self.sever = socket()
            self.sever.bind(('127.0.0.1', self.port))
            self.sever.listen(5)
            self.sever.setblocking(False)
            self.inputs = []
            self.inputs.append(self.sever)
            try:
                while True:
                    rlist, wlist, elist = select.select(self.inputs, [], [], 0.5)
                    for items in rlist:
                        if items == self.sever:
                            conn, addr = items.accept()
                            conn.setblocking(False)
                            self.inputs.append(conn)
                        else:
                            info = b''
                            while True:
                                try:
                                    chunk = items.recv(1024)
                                    info += chunk
                                except Exception:
                                    break
    
                            request = HTTPRequset(info)
                            url = request.url
                            func = self.url_rule.get(url)
                            if not func:
                                items.sendall(b'404')
                                self.close(items)
                            else:
                                response = func(request)
                                #不是字符串时执行
                                if not isinstance(response,str): 
                                    #新线程处理
                                    
                                    #返回Future对象
                                    response = next(response)
                                    
                                    #将request 传给回调函数
                                    t=Thread(target=response.callback,args=(response,request))
                                    t.start()
                                    
                                    #加入轮询字典中
                                    self.asyn[items] = response
                                else:
                                    items.sendall(response.encode('utf8'))
                                    self.close(items)
                    self.asyn_state()
                    
            except Exception:
                pass
            
        def asyn_state(self):
            if not self.asyn:
                return
            for conn,future in self.asyn.items():
                if future.state == False:
                    pass
                else:
                    conn.sendall(future.result.encode('utf8'))
                    self.close(conn)
                    self.asyn.pop(conn)
                    return
    
        def close(self,items):
            items.close()
            self.inputs.remove(items)
    
    def render(templates):
        with open(templates, 'r', encoding='utf8') as f:
            return f.read()
    easyweb

    使用

    from easyweb import EasyWeb,Future
    import time
    e = EasyWeb(8080)
    
    def getdata(futurer,request):
        time.sleep(3)  #模拟取数据
        
        futurer.set_result(request.url)
       futurer.finish() @e.route(
    '/index/') def index(request): if request.method == "GET": future = Future(callback=getdata) yield future @e.route('/main/') def main(request): if request.method == "GET": return 'hello main' if __name__ == '__main__': e.run()

    还有一些功能没有实现,静态文件目录,模板渲染,模板路径等,用到的也是一些后端常用的知识,这篇文章主要介绍就是异步非阻塞的基本原理。

  • 相关阅读:
    整数幂的求解
    非递归实现不重复序列的全排列(二)
    完整的将日期时间转换为汉字的代码
    如何得到某集合的所有子集合?
    再谈八皇后问题
    大数阶乘的计算(六)
    非递归实现不重复序列的全排列(一)
    非递归实现不重复序列的全排列(三)
    大数阶乘的计算(五)
    关于走楼梯的递归算法
  • 原文地址:https://www.cnblogs.com/ifyoushuai/p/9501434.html
Copyright © 2020-2023  润新知