zoukankan      html  css  js  c++  java
  • 从零开始搭建简易的异步非阻塞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()

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

  • 相关阅读:
    随机森林算法参数调优
    BAYES和朴素BAYES
    阿里云 金融接口 token PHP
    PHP mysql 按时间分组 表格table 跨度 rowspan
    MySql按周,按月,按日分组统计数据
    PHP 获取今日、昨日、本周、上周、本月的等等常用的起始时间戳和结束时间戳的时间处理类
    thinkphp5 tp5 会话控制 session 登录 退出 检查检验登录 判断是否应该跳转到上次url
    微信 模板消息
    php 腾讯 地图 api 计算 坐标 两点 距离 微信 网页 WebService API
    php添加http头禁止浏览器缓存
  • 原文地址:https://www.cnblogs.com/ifyoushuai/p/9501434.html
Copyright © 2011-2022 走看看