基于Python的主流Web开发框架研究及实现,python web框架主要的三大基本框架
仅供学习,转载请注明出处。
之前我用TCP协议返回HTTP数据,实现了web静态页面返回的服务器端功能。
但这并不能满足大部分的功能需求。
首先你要知道浏览器在进行http请求时,不仅会请求静态资源,还会请求动态页面。
那么什么是静态资源和动态页面呢?
静态资源:如html文件、图片文件、css、js文件等。可以视为静态资源。
动态页面:当诸如登录页面、查询页面、注册页面等页面被请求时,需要动态页面。可能会改变。
哦,好像很厉害。
浏览器请求动态页面的过程可以通过下图来理解,如下图:
我之前开发的web静态服务器只是中间部分,只用来返回静态资源。那么后台应用框架就是处理动态请求的页面。
比如浏览器发送http://172.16.5.81:7788/login.py的请求,会返回一个关于登录浏览器的页面,包括服务器当前时间等。
还可以看到web服务器用wsgi协议调用应用程序框架。在这里,我们先不说什么是wsgi协议。让我们来看看我之前写的静态web服务器。
您可以通过以下方式访问之前开发的web静态服务器:
Python web服务器开发,多进程优化Python web服务器开发,多线程
那么,我先取这两个代码中的一个进行优化开发,所以我采用多进程版本。
优化的下一步就是把原来面向过程的代码修改成面向对象的代码,封装好。
查看多进程web服务器代码-面向进程#coding=utf-8
从套接字导入*
进口re
导入多重处理
定义句柄_客户端(客户端_套接字):
“为客户服务”
#接收对方发送的数据
recv _ data=client _ socket . recv(1024)。Decode (UTF-8) # 1024表示这次接收的最大字节数
#打印从客户端发送的数据内容
#print(客户端接收:,接收数据)
request _ header _ lines=recv _ data . split lines()
对于请求标题行中的行:
打印(行)
#返回浏览器数据
#设置内容正文
#使用常规匹配来匹配文件路径。
打印(-,request_header_lines[0])
打印(文件路径-,。/html/ re.match(r[^/]/([^\s]*),request_header_lines[0]).组(1))
/([^\s]*),request_header_lines[0 re.match(r[^/])
如果ret:
文件路径=。/html/ ret.group(1)
if file_path==。/html/:
文件路径=。/html/index.html
打印(文件路径*******,文件路径)
尝试:
#设置返回的头信息头
response _ headers= http/1.1 200 ok \ r \ n # 200表示已经找到该资源。
Response_headers=\r\n #正文中有一个空行
#读取html文件内容
File_name=file_path #设置读取文件的路径
F=open(文件名, rb) #以二进制读取文件的内容
response_body=f.read()
f.close()
#向浏览器返回数据
client _ socket . send(response _ headers . encode( utf-8 )#对utf-8进行代码转换,并将数据发送到浏览器。
client _ socket . send(response _ body)#转码utf-8并将数据发送到浏览器
除了:
#如果文件未找到,则打印404未找到
#设置返回的头信息头
Response _ headers= http/1.1 404未找到\ r \ n # 200表示已找到该资源
Response_headers=\r\n #正文中有一个空行
response_body= h1对不起,找不到文件/h1
response=response_headers响应_正文
client _ socket . send(response . encode( utf-8 ))
# client _套接字. close()
def main():
#创建套接字
服务器套接字=套接字(AF_INET,SOCK_STREAM)
#先设置服务器关闭后立即释放资源,即服务器波动4次,保证下次运行程序时能立即绑定7788端口。
server _ SOCKET . setsockopt(SOL _ SOCKET,SO_REUSEADDR,1)
#设置服务器提供的服务的端口号。
server_socket.bind(( ,7788))
#使用socket创建的socket的默认属性是active,使用listen监听连接将其更改为passive。
Server_socket.listen(128) #最多可以监听128个连接
#打开while循环以处理访问请求。
虽然正确:
#如果有一个新的客户端链接到服务器,那么将创建一个新的套接字来服务这个客户端。
# client_socket用于服务此客户端。
# server_socket可以被保存以等待其他新的客户端连接,而True:
client_socket,client addr=server _ socket . accept()
# handle_client(客户端套接字)
#设置子流程
new_process=多重处理。进程(target=handle_client,args=(client_socket,))
New_process.start() #启动子进程
#因为子进程已经复制了父进程的套接字和其他资源,所以父进程不会通过调用close来关闭它们对应的链接。
client _套接字. close()
if __name__==__main__ :
Main()我们先来回顾一下操作:
好了,看到操作也正常了,我们来分析一下如何把代码封装成对象。
封装分析首先我需要定义一个webServer类,然后封装访问静态资源的功能。
#编码=utf-8
从套接字导入*
进口re
导入多重处理
类web服务器:
def __init__(self):
#创建套接字
self . server _ socket=socket(AF _ INET,SOCK_STREAM)
#先设置服务器关闭后立即释放资源,即服务器波动4次,保证下次运行程序时能立即绑定7788端口。
self . server _ SOCKET . setsockopt(SOL _ SOCKET,SO_REUSEADDR,1)
#设置服务器提供的服务的端口号。
self.server_socket.bind(( ,7788))
#使用socket创建的socket的默认属性是active,使用listen监听连接将其更改为passive。
self . server _ socket . listen(128)#最多可以侦听128个连接
def start_http_service(self):
#打开while循环以处理访问请求。
虽然正确:
#如果有一个新的客户端链接到服务器,那么将创建一个新的套接字来服务这个客户端。
# client_socket用于服务此客户端。
# self.server_socket可以被保存以等待其他新的客户端连接,而True:
client_socket,client addr=self . server _ socket . accept()
# handle_client(客户端套接字)
#设置子流程
new_process=多重处理。进程(target=self.handle_client,args=(client_socket,))
New_process.start() #启动子进程
#因为子进程已经复制了父进程的套接字和其他资源,所以父进程不会通过调用close来关闭它们对应的链接。
client _套接字. close()
定义handle_client(self,client_socket):
“为客户服务”
#接收对方发送的数据
recv _ data=client _ socket . recv(1024)。Decode (UTF-8) # 1024表示这次接收的最大字节数
#打印从客户端发送的数据内容
#print(客户端接收:,接收数据)
request _ header _ lines=recv _ data . split lines()
对于请求标题行中的行:
打印(行)
#返回浏览器数据
#设置内容正文
#使用常规匹配来匹配文件路径。
打印(-,request_header_lines[0])
打印(文件路径-,。/html/ re.match(r[^/]/([^\s]*),request_header_lines[0]).组(1))
/([^\s]*),request_header_lines[0 re.match(r[^/])
如果ret:
文件路径=。/html/ ret.group(1)
if file_path==。/html/:
文件路径=。/html/index.html
打印(文件路径*******,文件路径)
尝试:
#设置返回的头信息头
response _ headers= http/1.1 200 ok \ r \ n # 200表示已经找到该资源。
Response_headers=\r\n #正文中有一个空行
#读取html文件内容
File_name=file_path #设置读取文件的路径
F=open(文件名, rb) #以二进制读取文件的内容
response_body=f.read()
f.close()
#向浏览器返回数据
client _ socket . send(response _ headers . encode( utf-8 )#对utf-8进行代码转换,并将数据发送到浏览器。
client _ socket . send(response _ body)#转码utf-8并将数据发送到浏览器
除了:
#如果文件未找到,则打印404未找到
#设置返回的头信息头
Response _ headers= http/1.1 404未找到\ r \ n # 200表示已找到该资源
Response_headers=\r\n #正文中有一个空行
response_body= h1对不起,找不到文件/h1
response=response_headers响应_正文
client _ socket . send(response . encode( utf-8 ))
def main():
webserver=WebServer()
webserver.start_http_service()
if __name__==__main__ :
Main()好了,从上面的代码来看,我已经把之前面向过程的代码修改成面向对象的了。
运行它以查看是否有任何错误:
安静地坐着。正常请求成功。
思考:所以,它被封装成了一个对象。接下来应该优化什么?
好了,请求静态资源的页面已经准备好了,那么如果请求动态页面呢?
如果web服务器是用java写的,那么http请求通常是http: xxxx/xxx.jsp,如果web服务器是用php写的,那么http请求通常是http: xxxx/xxx.php,所以,既然我这次用python写,那么我可以把动态资源的请求定义为http: xxxx/xxx.py。
如何识别和执行http:xxxx/xxx.py的请求?
增加识别动态资源请求的功能需求:识别并返回http: xxxx/xxx.py的请求。
让我考虑一下。让我先做一些简单的事情。比如我请求一个http请求,http:xxxx/time.py向浏览器返回当前服务器的时间。
那么如果http请求一个以py结尾的请求,我需要在哪里处理呢?
我还可以用什么方法来判断文件。py后缀?
使用常规匹配?
其实可以用endswith(文件后缀)来判断处理。
识别文件名后缀file_name.endswith()的方法。py’)的测试和使用如下:
In [1]: file_name=time.py
#匹配的后缀是。html,结果是假的。
In [3]: file_name.endswith(。html’)
Out[3]:假
#如果匹配后缀是。py,是真的。
In [4]: file_name.endswith(。py’)
Out[4]:真,那我们就可以写这里判断的处理分支了。
运行测试:
首先请求HTML等静态资源页面。
请求动态资源页面
然后在收到动态资源请求时,将返回的数据写入浏览器。
让我们简单地写一串关于当前服务器时间的HTML。
if file_path.endswith(。py’):
#请求动态资源
Print(这是对动态资源的请求!)
#设置返回的头信息头
response _ headers= http/1.1 200 ok \ r \ n # 200表示已经找到该资源。
Response_headers=\r\n #正文中有一个空行
#设置返回给浏览器的正文内容。
response_body= h1你好,我是xxx.py /h1 br
response_body=time.ctime()
response=response_headers响应_正文
#向浏览器返回数据
client _ socket . send(response . encode( UTF-8 ))运行测试以查看:
动态页面的内容可以从这里正常返回。
思考:如果在web服务器上连续编写动态处理页面的代码,代码会很庞大。可以拆分写在另一个模块里吗?
这就涉及到web服务器和业务处理服务器之间的一个协议,这个业界通用的协议就是WSGI协议。
为什么需要WSGI协议?在讲WSGI协议之前,我把处理动态页面的功能拆分到另一个模块文件中。
创建一个处理业务的框架模块,复制刚刚处理好的代码返回浏览器:
导入时间
定义应用程序(客户端套接字):
#请求动态资源
Print(这是对动态资源的请求!)
#设置返回的头信息头
response _ headers= http/1.1 200 ok \ r \ n # 200表示已经找到该资源。
Response_headers=\r\n #正文中有一个空行
#设置返回给浏览器的正文内容。
response_body= h1你好,我是xxx.py /h1 br
response_body=time.ctime()
response=response_headers响应_正文
#向浏览器返回数据
client _ socket . send(response . encode( UTF-8 ))然后在原来的webserver.py模块中,只需导入模块文件,使用application()方法处理刚才的业务即可。
web.py模块的操作如下
好了,完成这个解耦操作后,让我们运行测试:
从上面的通话结果来看,确实通话成功。理解如下:
但是可以看出,如果webserver要调用框架处理业务,就得这样写,如下:
框架。application (client _ socket)方法是可行的,但在业界并不常见。也就是说,这个调用方法是扔给别人写的框架,是不兼容的。
举个例子,假设我后面用Django和Flask框架处理业务,这绝对不是沟通调用的方式。
那么以什么方式呢?
是否可以修改服务器和架构代码,保证多种架构下服务器和web服务器的通信?
答案是python web服务器网关接口(简称WSGI,读作“wizgy”)。
WSGI,我来了。
WSGI协议的引入WSGI允许开发者将web框架的选择与web服务器分开。您可以混合搭配web服务器和web框架,并选择合适的配对。例如,您可以在Gunicorn或Nginx/uWSGI或waste上运行Django、Flask或Pyramid。真正的混合匹配是由于WSGI对服务器和架构的支持:
web服务器必须有WSGI接口,所有现代的Python Web框架都已经有了WSGI接口,可以让你在不修改代码的情况下,让服务器和特色Web框架协同工作。
WSGI由web server支持,web framework允许你选择适合自己的一对,但也为服务器和框架开发者提供了便利,让他们专注于自己喜欢的领域和专长,而不是互相牵制。其他语言也有类似的接口:java有Servlet API,Ruby有Rack。
好像很厉害!
定义WSGI接口WSGI接口定义非常简单。它只需要Web开发人员实现一个函数来响应HTTP请求。
我们来看看最简单的网页版《Hello World!
定义应用程序(环境,启动响应):
start_response(200 OK ,[(Content-Type , text/html)])
返回“你好,世界!”上面的application()函数是一个符合WSGI标准的HTTP处理函数。它接收两个参数:
Environ:包含所有HTTP请求信息的dict对象;Start_response:发送HTTP响应的函数。整个application()函数本身并不涉及任何HTTP解析部分,也就是说,底层web服务器的解析部分与应用逻辑部分是分离的,这样开发者就可以专心于一个领域。
但是等等,这个application()函数怎么调用呢?如果我们自己调用,就不能提供environ和start_response两个参数,返回的str也不能发送到浏览器。
因此,application()函数必须由WSGI服务器调用。有许多服务器符合WSGI规范。此时我们的web服务器项目的目的是成为一个可以分析静态和动态web页面的WSGI服务器。
说了这么多,敢不敢秀一波代码操作?
写框架支持WSGI协议,实现hello worldframwork.py的浏览器显示:
直接复制协议规范代码。
然后在webserver.py的部分,需要接受application返回的信息。
首先,start_response在framwork中设置http请求头信息。而return是返回http请求体信息。
所以知道了这两点之后,接下来该怎么做。就是想办法接受这个应用程序的头和主体信息。
那么如何应对呢?
webserver.py
要比较这两个文件的代码,请使用pycharm同时打开两个视图窗口。
好了,我们继续看。
让我们创建这两个参数:
Environ:包含所有HTTP请求信息的dict对象;Start_response:发送HTTP响应的函数。
写一个空白处,填入WSGI规范要求的参数。
response_body可以由return的返回值接收。
那么response_header应该怎么处理呢?
从代码中可以看出,start_response是在从webserver.py传递到framwork.py的应用程序中调用的
在应用程序中,报头信息被直接设置到start_response的参数中。那我是不是可以在webserver.py直接拿出来拼接成头信息?
写入start_response以接收报头信息。
然后先写一个类变量来保存信息,然后测试并打印出来。
运行它并查看一下:
那么只要保存在self.application_header中,我就可以在类方法中的任何地方进行拆分或拼接,形成所需的http头返回值。
写的如下:
运行测试看看。
好了,你已经得到了完整的标题内容。然后拼接正文内容,返回浏览器显示。
按如下方式运行测试:
哦,这么说成功了?
不,我确定。如果我请求返回一个页面,我如何返回一个hello world?
下一步肯定是能够正常返回到index.py这样的正常页面。但是这一章也很长,
下一章继续。
本次开发的完整代码如下:webserver.py
#编码=utf-8
从套接字导入*
进口re
导入多重处理
导入时间
导入框架
类web服务器:
def __init__(self):
#创建套接字
self . server _ socket=socket(AF _ INET,SOCK_STREAM)
#先设置服务器关闭后立即释放资源,即服务器波动4次,保证下次运行程序时能立即绑定7788端口。
self . server _ SOCKET . setsockopt(SOL _ SOCKET,SO_REUSEADDR,1)
#设置服务器提供的服务的端口号。
self.server_socket.bind(( ,7788))
#使用socket创建的socket的默认属性是active,使用listen监听连接将其更改为passive。
self . server _ socket . listen(128)#最多可以侦听128个连接
def start_http_service(self):
#打开while循环以处理访问请求。
虽然正确:
#如果有一个新的客户端链接到服务器,那么将创建一个新的套接字来服务这个客户端。
# client_socket用于服务此客户端。
# self.server_socket可以被保存以等待其他新的客户端连接,而True:
client_socket,client addr=self . server _ socket . accept()
# handle_client(客户端套接字)
#设置子流程
new_process=多重处理。进程(target=self.handle_client,args=(client_socket,))
New_process.start() #启动子进程
#因为子进程已经复制了父进程的套接字和其他资源,所以父进程不会通过调用close来关闭它们对应的链接。
client _套接字. close()
定义handle_client(self,client_socket):
“为客户服务”
#接收对方发送的数据
recv _ data=client _ socket . recv(1024)。Decode (UTF-8) # 1024表示这次接收的最大字节数
#打印从客户端发送的数据内容
#print(客户端接收:,接收数据)
request _ header _ lines=recv _ data . split lines()
对于请求标题行中的行:
打印(行)
#返回浏览器数据
#设置内容正文
#使用常规匹配来匹配文件路径。
打印(-,request_header_lines[0])
打印(文件路径-,。/html/ re.match(r[^/]/([^\s]*),request_header_lines[0]).组(1))
/([^\s]*),request_header_lines[0 re.match(r[^/])
如果ret:
文件路径=。/html/ ret.group(1)
if file_path==。/html/:
文件路径=。/html/index.html
打印(文件路径*******,文件路径)
#判断file_path是否是py文件的后缀,如果是,请求动态资源,否则,请求静态资源。
if file_path.endswith(。py’):
# framework.application(客户端套接字)
#支持WGSI协议的呼叫模式
environ={}
response _ body=framework . application(environ,self.start_response)
#设置返回的头信息头
# 1.拼接第一行HTTP/1.1 200没问题换行符内容
response _ headers= HTTP/1.1 self。application _ header[0] \ r \ n
# 2.循环拼接第二行或者多行元组内容:内容类型:文本/html
对于self.application_header[1]中的var:
response _ headers=var[0]: var[1] \ r \ n
# 3.空一行与身体隔开
response_headers=\r\n
# 4.打印看看页眉的内容信息
打印( response_header=)
打印(响应标题)
# 设置返回的浏览器的内容
响应=响应标题响应_正文
客户端_套接字。发送(响应。编码( utf-8 ))
否则:
# 请求静态资源
尝试:
# 设置返回的头信息页眉
response _ headers= HTTP/1.1 200 OK \ r \ n # 200表示找到这个资源
response_headers=\r\n #空一行与身体隔开
# 读取超文本标记语言文件内容
文件名=文件路径#设置读取的文件路径
f=打开(文件名, rb) #以二进制读取文件内容
response_body=f.read()
f.close()
# 返回数据给浏览器
客户端_套接字。发送(response _ headers。encode( utf-8 )#转码utf-8并派遣数据到浏览器
客户端套接字发送(响应正文)#转码utf-8并派遣数据到浏览器
除了:
# 如果没有找到文件,那么就打印404未找到
# 设置返回的头信息页眉
response_headers=HTTP/1.1 404找不到\r\n # 200表示找到这个资源
response_headers=\r\n #空一行与身体隔开
response_body= h1对不起,找不到文件/h1
响应=响应标题响应_正文
客户端_套接字。发送(响应。编码( utf-8 ))
定义开始响应(自身、状态、表头):
自我。application _ header=[状态,标题]
print(application_header=,self.application_header)
def main():
webserver=WebServer()
webserver.start_http_service()
if __name__==__main__ :
main()framework.py
# 支撑WGSI协议
定义应用程序(环境,启动响应):
start_response(200 OK ,[(Content-Type , text/html)])
返回"你好,世界!"
关注微信公众号,回复【资料】、Python、PHP、JAVA、web、则可获得Python、PHP、JAVA、前端等视频资料。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。