Python web框架开发-WSGI协议!

前情介绍

前面我利用TCP协议,返回HTTP数据的方法,实现了web静态页面返回的服务端功能。

但是这样并不能满足大部分的功能需求。

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

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

静态资源 : 例如html文件、图片文件、css、js文件等,都可以算是静态资源

动态页面:当请求例如登陆页面、查询页面、注册页面等可能会变化的页面,则是动态页面。

哦,好像很厉害

浏览器请求动态页面过程

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

前面我开发的web静态服务器就是只做了中间部分,只用来返回静态资源。那么后面的应用程序框架则是处理动态请求的页面。

例如:浏览器发送一个 http://172.16.5.81:7788/login.py 的请求,则返回浏览器一个关于登陆的页面,其中包含了服务端的当前时间等。

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

可以通过以下访问前面开发的web静态服务器:

Python 开发web服务器,多进程优化

Python 开发web服务器,多线程

那么,我先来取这两个代码中的一个来进行优化开发,就采用多进程的版本吧。

下一步的优化目的,首先就是要将原来面向过程的代码,修改为面向对象,做好封装。

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

 1#coding=utf-8 2from socket import * 3import re 4import multiprocessing 5 6def handle_client(client_socket): 7 """为一个客户端服务""" 8 # 接收对方发送的数据 9 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数10 # 打印从客户端发送过来的数据内容11 #print("client_recv:",recv_data)12 request_header_lines = recv_data.splitlines()13 for line in request_header_lines:14 print(line)1516 # 返回浏览器数据17 # 设置内容body18 # 使用正则匹配出文件路径19 print("------>",request_header_lines[0])20 print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))21 ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])22 if ret:23 file_path = "./html/" + ret.group(1)24 if file_path == "./html/":25 file_path = "./html/index.html"26 print("file_path *******",file_path)2728 try:29 # 设置返回的头信息 header30 response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源31 response_headers += "\r\n" # 空一行与body隔开32 # 读取html文件内容33 file_name = file_path # 设置读取的文件路径34 f = open(file_name,"rb") # 以二进制读取文件内容35 response_body = f.read()36 f.close() 37 # 返回数据给浏览器38 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器39 client_socket.send(response_body) #转码utf-8并send数据到浏览器40 except:41 # 如果没有找到文件,那么就打印404 not found42 # 设置返回的头信息 header43 response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源44 response_headers += "\r\n" # 空一行与body隔开45 response_body = "<h1>sorry,file not found</h1>"46 response = response_headers + response_body47 client_socket.send(response.encode("utf-8"))4849 #client_socket.close()5051def main():52 # 创建套接字53 server_socket = socket(AF_INET, SOCK_STREAM)54 # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口55 server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)56 # 设置服务端提供服务的端口号57 server_socket.bind(('', 7788))58 # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接59 server_socket.listen(128) #最多可以监听128个连接60 # 开启while循环处理访问过来的请求 61 while True:62 # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务63 # client_socket用来为这个客户端服务64 # server_socket就可以省下来专门等待其他新的客户端连接while True:65 client_socket, clientAddr = server_socket.accept()66 # handle_client(client_socket)67 # 设置子进程68 new_process = multiprocessing.Process(target=handle_client,args=(client_socket,))69 new_process.start() # 开启子进程7071 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的72 client_socket.close()737475if __name__ == "__main__":76 main()

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

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

封装对象分析

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

 1#coding=utf-8 2from socket import * 3import re 4import multiprocessing 5 6class WebServer: 7 8 def __init__(self): 9 # 创建套接字10 self.server_socket = socket(AF_INET, SOCK_STREAM)11 # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口12 self.server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)13 # 设置服务端提供服务的端口号14 self.server_socket.bind(('', 7788))15 # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接16 self.server_socket.listen(128) #最多可以监听128个连接1718 def start_http_service(self):19 # 开启while循环处理访问过来的请求20 while True:21 # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务22 # client_socket用来为这个客户端服务23 # self.server_socket就可以省下来专门等待其他新的客户端连接while True:24 client_socket, clientAddr = self.server_socket.accept()25 # handle_client(client_socket)26 # 设置子进程27 new_process = multiprocessing.Process(target=self.handle_client,args=(client_socket,))28 new_process.start() # 开启子进程29 # 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的30 client_socket.close()3132 def handle_client(self,client_socket):33 """为一个客户端服务"""34 # 接收对方发送的数据35 recv_data = client_socket.recv(1024).decode("utf-8") # 1024表示本次接收的最大字节数36 # 打印从客户端发送过来的数据内容37 #print("client_recv:",recv_data)38 request_header_lines = recv_data.splitlines()39 for line in request_header_lines:40 print(line)4142 # 返回浏览器数据43 # 设置内容body44 # 使用正则匹配出文件路径45 print("------>",request_header_lines[0])46 print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))47 ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])48 if ret:49 file_path = "./html/" + ret.group(1)50 if file_path == "./html/":51 file_path = "./html/index.html"52 print("file_path *******",file_path)5354 try:55 # 设置返回的头信息 header56 response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源57 response_headers += "\r\n" # 空一行与body隔开58 # 读取html文件内容59 file_name = file_path # 设置读取的文件路径60 f = open(file_name,"rb") # 以二进制读取文件内容61 response_body = f.read()62 f.close()63 # 返回数据给浏览器64 client_socket.send(response_headers.encode("utf-8")) #转码utf-8并send数据到浏览器65 client_socket.send(response_body) #转码utf-8并send数据到浏览器66 except:67 # 如果没有找到文件,那么就打印404 not found68 # 设置返回的头信息 header69 response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源70 response_headers += "\r\n" # 空一行与body隔开71 response_body = "<h1>sorry,file not found</h1>"72 response = response_headers + response_body73 client_socket.send(response.encode("utf-8"))747576def main():77 webserver = WebServer()78 webserver.start_http_service()7980if __name__ == "__main__":81 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")

测试使用如下:

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

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

测试执行一下:

  • 首先请求HTML等静态资源页面
  • .进群:960410445 即可获取数十套PDF!
  • 请求动态资源页面

那么下面编写一下当接受到动态资源请求时候,返回浏览器的数据

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

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

运行一下测试看看:

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

思考 :那么如果将动态处理页面的代码在web服务端不断地写,代码就会很庞大。是否可以拆分出来,放到另一个模块进行编写呢?

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

为什么需要 WSGI协议

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

创建一个处理业务的框架模块,并讲刚才处理返回浏览器的代码复制进去:

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

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

webserver.py 模块操作如下

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

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

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

1framework.application(client_socket)

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

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

那么该用什么方式呢?

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

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

WSGI我来啦

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!:

1def application(environ, start_response):2 start_response('200 OK', [('Content-Type', 'text/html')])3 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

framwork.py:

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

那么在webserver.py的部分,就需要接受application返回的信息。

首先,start_response 就是在framwork设置http请求header信息的。而return 就是返回http请求body信息的。

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

那么怎么处理呢?

webserver.py

为了方便对比查看这两个文件的代码,使用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 信息

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

热门产品

php编程基础教程.pptx|php编程培训,php,编程,基础,教程,pptx
php编程基础教程.pptx

历史上的今天:04月30日

热门专题

国家开放大学|国家开放大学报名,国家开放大学报考,国家开放大学,什么是国家开放大学,国家开放大学学历,国家开放大学学费,国家开放大学报名条件,国家开放大学报名时间,国家开放大学学历,国家开放大学专业
国家开放大学
安徽中源管业有限公司|安徽中源管业有限公司,安徽中源管业有限公司介绍,安徽中源管业有限公司电话,安徽中源管业有限公司地址,安徽中源管业有限公司厂家,安徽中源管业有限公司电力管,安徽中源管业有限公司管材
安徽中源管业有限公司
云南开放大学|云南开放大学报名,云南开放大学报考,云南开放大学,什么是云南开放大学,云南开放大学学历,云南开放大学学费,云南开放大学报名条件,云南开放大学报名时间,云南开放大学学历,云南开放大学专业
云南开放大学
综合高中|云南综合高中,昆明综合高中,综合高中能考本一吗,综合高中和普通高中的区别,综合高中是什么意思,综合高中能参加全国统一高考吗,综合高中可以考哪些大学,综合高中的学籍是什么
综合高中
安徽开放大学|安徽开放大学报名,安徽开放大学报考,安徽开放大学,什么是安徽开放大学,安徽开放大学学历,安徽开放大学学费,安徽开放大学报名条件,安徽开放大学报名时间,安徽开放大学学历,安徽开放大学专业
安徽开放大学
大理科技管理学校|大理科技管理中等职业技术学校,大理市科技管理中等职业技术学校
大理科技管理学校
一年制中专|中专学历,中专是什么学历,中专是什么,中专有什么专业,中专升大专,一年制中专
一年制中专
自考本科|自考本科有用吗,自考文凭,自考本科文凭,自考文凭有用吗,自考本科文凭有用吗,自考文凭承认吗
自考本科

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部