从零开始搭建简易的异步非阻塞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()
还有一些功能没有实现,静态文件目录,模板渲染,模板路径等,用到的也是一些后端常用的知识,这篇文章主要介绍就是异步非阻塞的基本原理。