Python web框架开发 - WSGI协议

目录

浏览器请求动态页面过程

多进程web服务端代码 - 面向过程

封装对象分析

增加识别动态资源请求的功能

为什么需要 WSGI协议

WSGI协议的介绍

定义WSGI接口

编写framwork支持WSGI协议,实现浏览器显示 hello world

本次开发的完整代码如下:


浏览器进行http请求的时候,不单单会请求静态资源,还可能需要请求动态页面。

那么什么是静态资源,什么是动态页面呢?

静态资源 : 例如html文件、图片文件、css、js文件等,都可以算是静态资源
动态页面:当请求例如登陆页面、查询页面、注册页面等可能会变化的页面,则是动态页面。

浏览器请求动态页面过程

通过下图来了解一下页面HTTP请求的过程,如下:

 可以看到web服务器是用wsgi协议调用应用程序框架的,这里我们先不讲什么是wsgi协议,先看看我之前写的静态web服务端。

多进程web服务端代码 - 面向过程

#coding=utf-8
from socket import *
import re
import multiprocessingdef handle_client(client_socket):"""为一个客户端服务"""# 接收对方发送的数据recv_data = client_socket.recv(1024).decode("utf-8") #  1024表示本次接收的最大字节数# 打印从客户端发送过来的数据内容#print("client_recv:",recv_data)request_header_lines = recv_data.splitlines()for line in request_header_lines:print(line)# 返回浏览器数据# 设置内容body# 使用正则匹配出文件路径print("------>",request_header_lines[0])print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])if ret:file_path = "./html/" + ret.group(1)if file_path == "./html/":file_path = "./html/index.html"print("file_path *******",file_path)try:# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开# 读取html文件内容file_name = file_path # 设置读取的文件路径f = open(file_name,"rb") # 以二进制读取文件内容response_body = f.read()f.close()   # 返回数据给浏览器client_socket.send(response_headers.encode("utf-8"))   #转码utf-8并send数据到浏览器client_socket.send(response_body)   #转码utf-8并send数据到浏览器except:# 如果没有找到文件,那么就打印404 not found# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开response_body = "<h1>sorry,file not found</h1>"response = response_headers + response_bodyclient_socket.send(response.encode("utf-8"))#client_socket.close()def main():# 创建套接字server_socket = socket(AF_INET, SOCK_STREAM)# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)# 设置服务端提供服务的端口号server_socket.bind(('', 7788))# 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接server_socket.listen(128) #最多可以监听128个连接# 开启while循环处理访问过来的请求 while True:# 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务# client_socket用来为这个客户端服务# server_socket就可以省下来专门等待其他新的客户端连接while True:client_socket, clientAddr = server_socket.accept()# handle_client(client_socket)# 设置子进程new_process = multiprocessing.Process(target=handle_client,args=(client_socket,))new_process.start() # 开启子进程# 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的client_socket.close()if __name__ == "__main__":main()

先来回顾一下运行的情况:

 好了,看到运行也是正常的,那么下面就要来分析一下,如何将代码封装为对象。

封装对象分析

首先我需要定义一个webServer类,然后将访问静态资源的功能都封装进去。

#coding=utf-8
from socket import *
import re
import multiprocessingclass WebServer:def __init__(self):# 创建套接字self.server_socket = socket(AF_INET, SOCK_STREAM)# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)# 设置服务端提供服务的端口号self.server_socket.bind(('', 7788))# 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接self.server_socket.listen(128) #最多可以监听128个连接def start_http_service(self):# 开启while循环处理访问过来的请求while True:# 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务# client_socket用来为这个客户端服务# self.server_socket就可以省下来专门等待其他新的客户端连接while True:client_socket, clientAddr = self.server_socket.accept()# handle_client(client_socket)# 设置子进程new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,))new_process.start() # 开启子进程# 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的client_socket.close()def handle_client(self,client_socket):"""为一个客户端服务"""# 接收对方发送的数据recv_data = client_socket.recv(1024).decode("utf-8") #  1024表示本次接收的最大字节数# 打印从客户端发送过来的数据内容#print("client_recv:",recv_data)request_header_lines = recv_data.splitlines()for line in request_header_lines:print(line)# 返回浏览器数据# 设置内容body# 使用正则匹配出文件路径print("------>",request_header_lines[0])print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])if ret:file_path = "./html/" + ret.group(1)if file_path == "./html/":file_path = "./html/index.html"print("file_path *******",file_path)try:# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开# 读取html文件内容file_name = file_path # 设置读取的文件路径f = open(file_name,"rb") # 以二进制读取文件内容response_body = f.read()f.close()# 返回数据给浏览器client_socket.send(response_headers.encode("utf-8"))   #转码utf-8并send数据到浏览器client_socket.send(response_body)   #转码utf-8并send数据到浏览器except:# 如果没有找到文件,那么就打印404 not found# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开response_body = "<h1>sorry,file not found</h1>"response = response_headers + response_bodyclient_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("文件后缀")的方法来判断处理。

In [1]: file_name = "time.py"# 匹配后缀为 .html ,直接报False
In [3]: file_name.endswith(".html")
Out[3]: False# 匹配后缀为 .py ,则报True
In [4]: file_name.endswith(".py")
Out[4]: True

那么下面就可以来写写这里判断的处理分支了。

 测试执行一下:

首先请求HTML等静态资源页面

 请求动态资源页面

 先简单地写一串HTML+当前服务器时间的内容吧。

       if file_path.endswith(".py"):# 请求动态资源print("这个是请求动态资源的!")# 设置返回的头信息 headresponse_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开# 设置返回浏览器的body内容response_body = "<h1>hello this is xxx.py</h1><br>"response_body += time.ctime()response = response_headers + response_body# 返回数据给浏览器client_socket.send(response.encode("utf-8"))

 从这里已经可以正常返回动态页面的内容的了。

思考 :如果将动态处理页面的代码在web服务端不断地写,代码就会很庞大。是否可以拆分出来,服务端只接受浏览器的消息,判断静态还是动态以及其他业务功能放到另一个模块进行编写呢?

这里就涉及到 web服务端 与 业务处理服务端 之间的一个协议了,这个业界内通用的协议就是 WSGI协议。

为什么需要 WSGI协议

在讲WSGI协议之前,我先把处理动态页面的功能拆分到另一个模块文件中。

import timedef application(client_socket):# 请求动态资源print("这个是请求动态资源的!")# 设置返回的头信息 headresponse_headers = "HTTP/1.1 200 OK\r\n"  # 200 表示找到这个资源response_headers += "\r\n"  # 空一行与body隔开# 设置返回浏览器的body内容response_body = "<h1>hello this is xxx.py</h1><br>"response_body += time.ctime()response = response_headers + response_body# 返回数据给浏览器client_socket.send(response.encode("utf-8"))

那么在原来的webserver.py模块只要import该模块文件,使用application()方法就可以处理刚才的业务了。

 

 好了,做了这个解耦的操作之后,下面来运行测试一下:

 从上面的调用结果来看,的确是调用成功啦,理解大概如下图:

 可以看出来,webserver想要调用 framework处理业务的话,就要这样去写,如下:

framework.application(client_socket)

这种方式虽然可行,但是在业界中是不通用的。也就是说这种调用方法扔给别人写的框架,就无法兼容了。

例如:假设我后面改用Django、Flask框架来处理业务,此时一定就不是用这种方式来通讯调用的。

 那么该用什么方式呢?

是否可以修改服务器和架构代码而确保可以在多个架构下,保证与web服务器之间的通讯调用呢?

答案就是  Web Server Gateway Interface (或简称 WSGI,读作“wizgy”)。

WSGI协议的介绍

WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。
比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构。

 web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。

WSGI由web服务器支持,而web框架允许你选择适合自己的配对,但它同样对于服务器和框架开发者提供便利使他们可以专注于自己偏爱的领域和专长而不至于相互牵制。其他语言也有类似接口:java有Servlet API,Ruby 有 Rack。

定义WSGI接口

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。

我们来看一个最简单的Web版本的“Hello World!”

def application(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])return 'Hello World!'

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,把底层web服务器解析部分和应用程序逻辑部分进行了分离,这样开发者就可以专心做一个领域了。

不过,这个application()函数怎么调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的str也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器。而我们此时的web服务器项目的目的就是做一个既能解析静态网页还可以解析动态网页的WSGI服务器。

编写framwork支持WSGI协议,实现浏览器显示 hello world

直接协议规范代码复制进去。

那么在webserver.py的部分,就需要接受application返回的信息。
首先,start_response 就是在framwork设置http请求header信息的。而return 就是返回http请求body信息的。

那么知道了这两点之后,下一步要做的。就是想办法来接受这个application的设置header以及body信息。

那么怎么处理呢?

为了方便对比查看这两个文件的代码,使用pycharm同时打开两个视图窗口来查看。

 好了,下面来继续看看。

下面来创建这两个形参:

  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数。

先随便写个空的,来填入WSGI规范所需要的参数。
其中response_body通过return的返回值就可以接受到了。
那么response_header该怎么处理呢?

可以从代码中看出start_response 在webserver.py 传入到 framwork.py 的application中调用。
其中在application中就直接设置header信息到start_response的参数中。然后我在webserver.py能否直接将其取出来,拼接成header信息呢?

编写start_response 接收 header 信息

那么首先编写一个类变量来保存信息,然后测试打印一下看看。

 运行测试一下看看:

 

 那么只要将其保存到self.application_header中,我就可以在类方法的任意一个地方进行拆分或者拼接成所需要的http header返回值了。

编写如下:

 运行测试看看。

这样就得到了完成的header内容啦,那么下面将其拼接body内容,然后返回浏览器中显示。

 运行测试如下:

本次开发的完整代码如下:

webserver.py

#coding=utf-8
from socket import *
import re
import multiprocessing
import time
import frameworkclass WebServer:def __init__(self):# 创建套接字self.server_socket = socket(AF_INET, SOCK_STREAM)# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)# 设置服务端提供服务的端口号self.server_socket.bind(('', 7788))# 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接self.server_socket.listen(128) #最多可以监听128个连接def start_http_service(self):# 开启while循环处理访问过来的请求while True:# 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务# client_socket用来为这个客户端服务# self.server_socket就可以省下来专门等待其他新的客户端连接while True:client_socket, clientAddr = self.server_socket.accept()# handle_client(client_socket)# 设置子进程new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,))new_process.start() # 开启子进程# 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的client_socket.close()def handle_client(self,client_socket):"""为一个客户端服务"""# 接收对方发送的数据recv_data = client_socket.recv(1024).decode("utf-8") #  1024表示本次接收的最大字节数# 打印从客户端发送过来的数据内容#print("client_recv:",recv_data)request_header_lines = recv_data.splitlines()for line in request_header_lines:print(line)# 返回浏览器数据# 设置内容body# 使用正则匹配出文件路径print("------>",request_header_lines[0])print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])if ret:file_path = "./html/" + ret.group(1)if file_path == "./html/":file_path = "./html/index.html"print("file_path *******",file_path)# 判断file_path是否py文件后缀,如果是则请求动态资源,否则请求静态资源if file_path.endswith(".py"):# framework.application(client_socket)# 支撑WGSI协议的调用方式environ = {}response_body = framework.application(environ, self.start_response)# 设置返回的头信息header# 1.拼接第一行HTTP/1.1 200 OK + 换行符内容response_headers = "HTTP/1.1 " + self.application_header[0] + "\r\n"# 2.循环拼接第二行或者多行元组内容:Content-Type:text/htmlfor var in self.application_header[1]:response_headers += var[0]+":"+var[1] + "\r\n"# 3.空一行与body隔开response_headers += "\r\n"# 4.打印看看header的内容信息print("response_header=")print(response_headers)# 设置返回的浏览器的内容response = response_headers + response_bodyclient_socket.send(response.encode("utf-8"))else:# 请求静态资源try:# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开# 读取html文件内容file_name = file_path # 设置读取的文件路径f = open(file_name,"rb") # 以二进制读取文件内容response_body = f.read()f.close()# 返回数据给浏览器client_socket.send(response_headers.encode("utf-8"))   #转码utf-8并send数据到浏览器client_socket.send(response_body)   #转码utf-8并send数据到浏览器except:# 如果没有找到文件,那么就打印404 not found# 设置返回的头信息 headerresponse_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源response_headers += "\r\n" # 空一行与body隔开response_body = "<h1>sorry,file not found</h1>"response = response_headers + response_bodyclient_socket.send(response.encode("utf-8"))def start_response(self,status,header):self.application_header = [status,header]print("application_header=",self.application_header)def main():webserver = WebServer()webserver.start_http_service()if __name__ == "__main__":main()
framework.py# 支撑WGSI协议
def application(environ, start_response):start_response('200 OK', [('Content-Type', 'text/html')])return 'Hello World!'

-事必有法,然后有成- 最后祝大家早日达到测试的天花板!



 以下是我收集到的比较好的学习教程资源,虽然不是什么很值钱的东西,如果你刚好需要,可以评论区,留言【777】直接拿走就好了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/3777.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

社区活动 | OpenVINO™ DevCon 中国系列工作坊第二期 | 使用 OpenVINO™ 加速生成式 AI...

生成式 AI 领域一直在快速发展&#xff0c;许多潜在应用随之而来&#xff0c;这些应用可以从根本上改变人机交互与协作的未来。这一最新进展的一个例子是 GPT 模型的发布&#xff0c;它具有解决复杂问题的能力&#xff0c;比如通过医学和法律考试这种类似于人类的能力。然而&am…

MySql脚本 asc 排序字段空值条目靠后的写法

场景&#xff1a; mysql中如果使用正序 asc 排序&#xff0c;那么默认是把排序字段值为空的条目数据&#xff0c;优先排到前面&#xff0c;这明显不符合需求&#xff0c;解决如下 一、重现问题 -- 按排序号-正序 select shop_id,sort_num,update_time from t_shop_trend_conte…

SpringMvc中文件上传

文章目录 1.导入文件上传所需要的jar包 2. 配置文件解析器 3.写一个前端页面 4.写后台程序 1.导入文件上传所需要的jar包 <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.…

华为FIT痩AP旁挂式隧道组网实验(一)

拓扑图 实验设备型号ACAC6005S1S5700S2S3700APAP2050DNAP4AP2050DNAR1AR200 没有配置好之前,是没有这个AP范围圈的 配置流程 接入交换机创建VLAN,配置对应端口的链路类型,放行vlan,开启端口隔离 # 与AP连接的接口(0/0/2) [S2]vlan batch 100 101 [S2]int e0/0/2 [S2-Ethern…

finalshell使用方法,前端vue更新服务器项目

首先我们看看finalshell的整体 上面是xshell一样&#xff0c;可以输命令 上面是WinSCP一样&#xff0c;可以直接拖文件&#xff0c;下载&#xff0c;上传&#xff0c;可视化视图 1.下载服务器文件 服务器文件通过Jenkins打包上去的&#xff0c;首先我们把文件下载到本地 点击…

ranger配置hive出錯:Unable to connect repository with given config for hive

ranger配置hive出錯&#xff1a;Unable to connect repository with given config for hive 我一開始我以為是我重啟了ranger-admin導致ranger有點問題&#xff0c;後面排查之後發現是我之前把hiveserver2關閉了&#xff0c;所以只需要重新開啟hiveserver2即可

Mysql主从同步失败排查思路及解决办法

1、查看同步信息 登录进从数据库后查询同步状态 show slave status \G 2、查看同步失败出现的日志 Coordinator stopped because there were error(s) in the worker(s). The most r ecent failure being: Worker 1 failed executing transaction 55b49392-fdcd-11ec-83b2-…

已烧写过的镜像重新烧镜像教程

本教程是已经烧录过镜像的SD卡&#xff0c;无法被电脑识别盘符导致无法重新烧录镜像的教程。一般是win7系统无法识别烧录过的Ubuntu系统盘符。win10可以使用SDformat软件格式化。 1.确定读卡器是否识别到SD卡。 点击计算机右键选择“管理”&#xff0c;选择磁盘管理&#xff0…

【AI底层逻辑】——篇章4:大数据处理与挖掘

目录 引入 一、大数据概述 二、数据处理的流程&方法 1、数据收集——“从无到有” 2、数据加工——“从有到能用” 3、数据分析 三、大数据改变了什么 往期精彩&#xff1a; 引入 AI的表现依赖大数据。曾经一段时间&#xff0c;对于图像识别的准确率只能达到60%~70…

接口测试和功能测试的区别

目录 前言&#xff1a; 一、测试目的不同 二、测试内容不同 三、测试重点不同 总结 前言&#xff1a; 接口测试和功能测试都是软件测试中非常重要的测试类型&#xff0c;它们都是验证软件产品的正确性、完备性、正确性和可靠性。但这两者之间有着一些区别。 一、测试目的…

python爬虫-逆向实例小记-3

注意&#xff01;&#xff01;&#xff01;&#xff01;某数据网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01;&#xff01; 案例分析 第一步&#xff1a;分析页面。查看响应内容&#xff0c;内容加密 第二步&am…

chatgpt赋能python:用Python来制作动画

用Python来制作动画 Python是一种高级编程语言&#xff0c;可以用于许多任务&#xff0c;包括数据分析、网络编程&#xff0c;甚至是制作动画。在这篇文章中&#xff0c;我们将讨论如何使用Python来制作动画。 Python中的动画库 Python中有许多用于制作动画的库。其中最流行…