从零开始搭建简易的异步非阻塞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('\r\n\r\n',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('\r\n')
        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('\r\n\r\n', 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('\r\n')
        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()

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

posted @ 2018-08-23 20:36  码、、码  阅读(748)  评论(0编辑  收藏  举报