200行代码搭建异步非阻塞Web框架

最近在看Tornado源码给了我不少启发,心血来潮决定自己试着只用python标准库来实现一个异步非阻塞web框架。花了点时间感觉还可以,一百多行的代码已经可以撑起一个极简框架了。

一、准备工作

需要的相关知识点:

  • HTTP协议的请求和响应
  • IO多路复用
  • asyncio

掌握上面三个点的知识就完全没有问题,不是很清楚的同学我也推荐几篇参考文章

  HTTP协议详细介绍(https://www.cnblogs.com/haiyan123/p/7777924.html

  Python篇-IO多路复用详解(https://www.jianshu.com/p/818f27379a5e

  Python异步IO之协程(一):从yield from到async的使用(https://blog.csdn.net/SL_World/article/details/86597738

实验环境:

python 3.7.3

 由于在框架中会使用到async/await关键字,所以只要确保python版本在3.5以上即可。

 二、框架功能目标

我们的框架要实现最基本的几个功能:

  • 封装HTTP请求响应
  • 路由映射
  • 类视图和函数视图
  • 协程支持

 当然一个完善的web框架需要实现的远远不止这些,这里我们现在只需要它能跑起来就足够了。

三、封装HTTP协议

HTTP是基于TCP/IP通信协议来实现数据传输,与一般的C/S相比,它的特点在于当客户端(浏览器)向服务端发起HTTP请求,服务端响应数据后双方立马断开连接,服务端无法主动向客户端发送数据。HTTP协议数据传输内容分为请求头和请求体,请求头和请求体之间使用"\r\n\r\n"进行分隔。在请求头中,第一行包含了请求方式,请求路径和HTTP协议,此后每一行以key: value的形式传输数据。

对于我们的web服务端来说,需要的就是解析http请求和处理http响应。

我们通过写两个类,HttpRequest和HttpResponse来实现。

3.1 HttpRequest

HttpRequest设计目标是解析从socket接收request数据

 1 class HttpRequest(object):
 2     def __init__(self, content: bytes):
 3         self.content = content.decode('utf-8')
 4         self.headers = {}
 5         self.GET = {}
 6         self.url = ''
 7         self.full_path = ''
 8         self.body = ''
 9         try:
10             header, self.body = self.content.split('\r\n\r\n')
11             temp = header.split('\r\n')
12             first_line = temp.pop(0)
13             self.method, self.url, self.protocol = first_line.split(' ')
14             self.full_path = self.url
15             for t in temp:
16                 k, v = t.split(': ', 1)
17                 self.headers[k] = v
18         except Exception as e:
19             print(e)
20         if len(self.url.split('?')) > 1: # 解析GET参数
21             self.url = self.full_path.split('?')[0] # 把url中携带的参数去掉
22             parms = self.full_path.split('?')[1].split('&')
23             for p in parms: # 将GET参数添加到self.GET字典
24                 k, v = p.split('=')
25                 self.GET[k] = v

在类中,我们实现解析http请求的headers、method、url和GET参数,其实还有很多事情没有做,比如使用POST传输数据时,数据是在请求体中,针对这部分内容我并没有开始写,原因在于本文主要目的还是异步非阻塞框架,目前的功能已经足以支持我们进行下一步实验了。

3.2 HttpResponse

HTTP响应也可以分为响应头和响应体,我们可以很简单的实现一个response:

 1 class HttpResponse(object):
 2     def __init__(self, data: str):
 3         self.status_code = 200 # 默认响应状态 200
 4         self.headers = 'HTTP/1.1 %s OK\r\n'
 5         self.headers += 'Server:AsynicWeb'
 6         self.headers += '\r\n\r\n'
 7         self.data = data
 8 
 9     @property
10     def content(self):
11         return bytes((self.headers + self.data) % self.status_code, encoding='utf8')

HttpResponse中并没有做太多的事情,接受一个字符串,并使用content返回一个满足HTTP响应格式的bytes。

从用户调用角度,可以使用return HttpResponse("欢迎来到AsynicWeb")来返回数据。

我们也可以简单的定义一个404页面:

Http404 = HttpResponse('<html><h1>404</h1></html>')
Http404.status_code = 404

四、路由映射

路由映射简单理解就是从一个URL地址找到对应的逻辑函数。举个例子,我们访问http://127.0.0.1:8000这个页面,在http请求中它的url是"/",在web服务器中有一个函数index,web服务器能够由url地址"/"找到函数index,这就是一个路由映射。

其实路由映射实现起来非常简单。我们只要定义一个映射列表,列表中的每个元素包含url和逻辑处理两部分,当一个http请求到达的时候,遍历映射列表,使用正则匹配每一个url,如果请求的url和映射表中的相同,我们就可以取出对应的逻辑处理函数。

路由映射表是完全由用户来定义映射关系的,它应该使用一个我们定义的标准结构,比如

routers = [
    ('/$', IndexView),
    ('/home', asy)
]

未完待续。。。

猜你喜欢

转载自www.cnblogs.com/lazyfish007/p/11772677.html