当前位置: 首页 > 后端技术 > Python

一个文件,Python实现了一个迷你的web框架!

时间:2023-03-25 20:49:37 Python

目前,互联网就像我们周围的空气。它以无数种方式改变了我们的生活,但互联网的核心技术却几乎没有改变。随着开源文化的蓬勃发展,诞生了很多优秀的开源web框架,让我们的开发变得更加简单。但同时,也让我们不敢停止学习新的框架。事实上,所有的变化都保持不变。我们只要了解Web框架的核心技术部分,当一个新的框架出来的时候,基本的部分都是大同小异的,我们只需要重点理解:它有什么特点,用什么技术解决什么痛点?这样在接受和理解新技术时会更加得心应手,不会疲于奔命。而那些只会用web框架的同学,是不是打开了无数次框架的源码,想要学习提升却无从下手?今天我们将抽丝剥茧,化繁为简,用一个文件实现一个迷你Web框架,将其核心技术讲解清楚,配套源码开源。闲话少说,开始今天的提升之旅吧。一、介绍原理说到Web,就不得不提到网络协议。如果我们从OSI的七层网络模型开始,我敢肯定不会有超过30%的内容被读到!那么今天我们就直接讲顶层,也就是web框架接触最多的HTTP应用层。至于TCP/IP部分,我们在讲套接字的时候会简单提一下。期间会在没必要的时候刻意敲代码讲解技术细枝末节,把离本期主题较远的技术话题删掉,一个文件只讲一个技术点!不拖沓,请放心阅读。首先,让我们回顾一下平时浏览网站的过程。如果把上网比作在教室里听课,那么老师就是服务器,学生就是客户。当学生有问题时,他们会先举手(要求建立TCP)。教师发现学生的提问请求,同意学生对问题的回答。学生站起来提出问题(发送请求)。如果老师承诺给提问的学生一个班级成绩,那么在提问的时候,需要有一个高效的提问方式(请求格式),即:先报学号再提问。格式(responseformat)如下:按学号回答问题加分!有了约定好的出题格式(protocol),老师就可以省去老师每次都要问学生的学号,高效严谨。最后,老师在回答完问题后请学生坐下(关闭连接)。其实我们在网络上的通信过程大致是一样的:只是机器的实现比较严格,大家按照一定的协议来开发软件,这样才能在一定的协议下实现通信,而这个网络通信协议是它被称为HTTP(超文本传输??协议)。而我们要做的web框架就是处理上面的过程:建立连接,接收请求,解析请求,处理请求,返回请求。原理部分就这些了。目前你只需要记住网络上的通信分为两个步骤:建立连接(进行通信)和处理请求。所谓框架就是处理大部分情况下需要处理的事情,所以我们要写的web框架就是处理两个事情,即:处理连接(socket)和处理请求(request)一定要记住:连接和请求是两个东西,连接只能在请求建立之前发送。如果要建立连接来发起通信,需要通过socket来实现(建立连接)。一个socket可以理解为两个虚拟笔记本(文件句柄),通信双方各有一个。它既可以读也可以写,只要将传输的内容写在notebook(处理请求)中,对方就可以看到。接下来我将把web框架分成两部分进行讲解,所有的代码都会用通俗易懂的Python3来实现。2、Web框架代码+注释一共457行,绝对简单易懂,请放心。2.1处理连接(HTTPServer)这里需要简单说一下sockets。在编程语言层面,它是一个负责处理连接和建立网络通信的类库。但本质上是一个提供系统级通信的进程,一台计算机可以建立多条通信线路,所以每个端口号后面都有一个socket进程,相互独立,互不干扰,这也是我们启动服务时指定端口号的原因。最后,上面说的服务器其实是一台性能比较好的一直开着的电脑,而客户端是浏览器、手机、电脑,都有socket(操作系统层面的进程)。看不懂上面这段话不要紧,只要能看懂下图,就一定要了解socket处理连接的步骤和过程,才能写出web框架处理连接的部分。基于socket编写的server.py和client.py代码如下所示。#编码:utf-8#服务器端代码(server.py)importsocketprint('我是服务器!')HOST=''PORT=50007s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#创建TCPsocketObjects.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#重启时释放端口s.bind((HOST,PORT))#绑定地址s.listen(1)#监听TCP,1表示:操作系统可以挂起的最大连接数(未处理请求时的等待状态)。该值至少为1print('listeningport:',PORT)while1:conn,_=s.accept()#开始被动接受来自TCP客户端的连接。data=conn.recv(1024)#接收TCP数据,1024表示缓冲区大小print('Received:',repr(data))conn.sendall(b'Hi,'+data)#向客户端发送数据conn.close()因为HTTP建立在比较可靠的TCP协议之上,所以这里创建的是一个TCP套接字对象。#coding:utf-8#Clientcode(client.py)importsocketprint('Iamaclient!')HOST='localhost'#服务器的IPPORT=50007#要连接的服务器端口=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect((HOST,PORT))print("Send'HelloGitHub'")s.sendall(b'HelloGitHub')#发送'HelloGitHub'到服务器data=s.recv(1024)s.close()print('received',repr(data))#打印从服务器接收到的数据。运行效果如下:结合上面的代码,更容易理解socket建立通信的过程:socket:创建socketbind:绑定端口号listen:开始监听accept:接收请求recv:接收数据close:关闭连接所以,在web框架中处理连接的HTTPServer类中做什么是呼之欲出的。即:首先在__init__方法中创建一个socket,然后绑定端口(server_bind)然后开始监听端口(server_activate)#处理数据通信的连接类HTTPServer(object):def__init__(self,server_address,RequestHandlerClass):自己。server_address=server_address#服务器self.requesthandlerclass=requestHandlerClass#处理类类#类#tcpsocketsocketself.socket=socket.socket.socket.socket(socket.aff_inet,socket.socke.socke.socke.sock.socke.sock_stream)#端口self.server_activate()从传入的RequestHandlerClass参数可以看出处理请求和建立连接是分开处理的。接下来就是启动接收请求的服务,即HTTPServer启动方法serve_forever,包括接收请求、接收数据、开始处理请求、结束请求的全过程。defserve_forever(self):whileTrue:ready=selector.select(poll_interval)#当客户端请求的数据到位后,执行下一步ifready:#如果有准备好的可读文件句柄,与clientisestablishedrequest,client_address=self.socket.accept()#可以处理下面的处理请求,通过RequestHandlerClass独立处理请求和连接self.RequestHandlerClass(request,client_address,self).ets(close)close像这样的连接,就是HTTPServer处理连接和建立HTTP连接的全部代码,仅此而已?正确的!是不是很简单?代码中的RequestHandlerClass形参就是处理请求的类。下面将深入讲解其对应的HTTPRequestHandler是如何处理HTTP请求的。2.2处理请求(HTTPRequestHandler)还记得上面介绍的socket是如何实现两端通信的吗?通过两个可读可写的“虚拟笔记本”。此外,为保证沟通的高效性和严谨性,还需要相应的“沟通格式”。因此处理请求只需要三步:setup:初始化两个书本读取请求的文件句柄(rfile),并写入response的文件句柄(wfile)handle:读取并解析请求,处理请求,构造response,写finish:返回response,销毁两个notebook释放资源,尘归尘,等待下一个请求对应的代码:#ProcessingrequestclassHTTPRequestHandler(object):def__init__(self,request,client_address,server):self.request=request#Receivedrequest(socket)#1.初始化两个notebooksself.setup()try:#2.读取、解析、处理请求,构造响应self.handle()finally:3,release#Resourceself.finish()defsetup(self):self.rfile=self.request.makefile('rb',-1)#读取请求书self.wfile=self.request.makefile('wb',0)#写响应书defhandle(self):#根据HTTP协议,解析请求#S具体的处理逻辑,即业务逻辑#构造response并写入bookdeffinish(self):#返回responseself.wfile.flush()#关闭request和response的句柄,释放资源框架与具体的业务代码(处理逻辑)分离。在解析HTTP之前,您需要查看一个实际的HTTP请求。当我打开helloithub.com网站首页时,浏览器发送的HTTP请求如下:整理总结HTTP请求格式如下:{HTTP方法}{PATH}{HTTP版本}\r\n{headerfieldname}:{fieldvalue}\r\n...\r\n{requestbody}已经获取到请求格式,然后有handle方法解析请求。defhandle(self):#---开始解析---#self.raw_requestline=self.rfile.readline(65537)#读取请求中第一行数据,即请求头requestline=str(self.raw_requestline,'iso-8859-1')#转码requestline=requestline.rstrip('\r\n')#去掉换行和空行#可以得到“GET/HTTP/1.1”请求头,下面开始解析self.command,self.path,self.request_version=requestline.split()#根据空格拆分字符串得到("GET","/","HTTP/1.1")#command对应HTTP方法,path对应请求Path#request_version对应HTTP版本,不同版本解析规则不同。这里就不解释了self.headers=self.parse_headers()#解析请求头也是处理字符串,但是比较复杂的标准库有工具函数这里略过#---业务逻辑---##do_HTTP_method对应具体的处理函数mname=('do_'+self.command).lower()method=getattr(self,mname)#调用相应的处理方法method()#---返回响应---#self.wfile.flush()defdo_GET(self):#根据路径不同处理ifself.path=='/':self.send_response(200)#状态码#添加响应头self.send_header("Content-Type",";文本/html;charset=utf-8")self.send_header("Content-Length",str(len(content)))self.end_headers()#结束头部分,即:'\r\n'self.wfile.write(content.encode('utf-8'))#写入响应体,即:页面内容defsend_response(self,code,message=None):#响应体格式"""{HTTPversion}{statuscode}{statusphrase}\r\n{headerfieldname}:{fieldvalue}\r\n...\r\n{responsebody}"""#Writeresponseheaderlineself.wfile.write("%s%d%s\r\n"%("HTTP/1.1",code,message))#添加响应头self.send_header('Server',"HG/Python")self.send_header('Date',self.date_time_string())以上就是handle处理请求并返回响应的核心代码片段,至此,HTTPRequestHandler的所有内容已经解释完毕,下面演示运行效果2.3RunclassRequestHandler(HTTPRequestHandler):#处理GET请求defdo_get(self):#根据路径对应具体的处理方式ifself.path=='/':self.handle_index().pathelif(/favicon'):self.handle_favicon()else:self.send_error(404)if__name__=='__main__':server=HTTPServer(('',8080),RequestHandler)here#Inheritedtostarttheserviceviaserve_forserver.)子类Web框架的HTTPRequestHandler实现的RequestHandler重写了do_get方法,将业务代码与框架分离。这样保证了框架的灵活性和解耦性。然后服务就正常运行了,效果如下:本文中涉及web框架的代码都进行了简化,方便阅读。这个框架没有包含一个web框架应该有的丰富功能。旨在通过最简单的代码实现一个迷你的web框架,方便不了解web框架基本结构的同学了解。如果本文的内容已经引起了你对web框架的兴趣,你也想对更全面的适用于生产环境、代码和结构的web框架有更深入的了解。我建议的学习路径:Python3HTTPServer,BaseHTTPRequestHandlerbottle:单文件,无第三方依赖,持续更新,一个开源的可以用于生产环境的web框架:werkzeug->flaskstarlette->uvicorn->fastapi有时阅读框架源码代码不是为了写一个新的框架,而是为了向前人学习和靠拢。最后,新技术总是层出不穷。掌握了核心技术原理,不仅可以在接受新知识时快人一步,而且在解决问题时也能一针见血。不知道这种文档是不是说明了一个技术点,力求通过简单的文字和简化的代码来描述原理,抹去期间技术的细枝末节,专注于一个技术,最后给出一篇完整的可以运行的open源代码。你的胃口?这篇文章是我对一个新系列的尝试,欢迎大家的指点和批评。如果觉得文章还不错,欢迎关注公众号:Python编程学习圈,或者去编程学习网了解更多编程技术知识,还有海量干货学习资料!