开发者学堂课程【Python Web 开发基础:类 Flask 框架请求封装】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/554/detail/7638
类 Flask 框架请求封装
WSGI
WSGI 是一套协议它主要规定了 WSGI Server 与 WSGI App 之间是如何调用的,它规范了这个调用接口,那我们刚才已经有所了解了,所以说我们再回到说浏览器,发起的请求当然很多,浏览器不止一个,这边其实有很多 HTTP 请求,也就是说这边可能有很多 tcp 连接。
那不管怎么说,对我们来说,从应用层角度来讲的话,它全部都是 HTTP 协议的这样一个请求,这个请求被 WSGI 获取到之后,就会对这个请求进行解析,解析之后将这些信息重新封装到一个字典中,这个字典就是 environment,这个字典把它所请求的这些数据装在一个环境数据的字典中,提供给大家使用。这个字典我们有的时候其实习惯了称为 request,也就是它的请求,请求数据,那这个信息我们拿到之后我,这个请求就得转发给你的这个 app,由 app 对这个请求进行处理,那往往会生成一些数据,这些数据最终要返回到浏览器端,但是必须按照 WSGI 的要求来做。
WSGI 在调用你的 app 的时候,会传入两个参数,第一个参数 environment,第二个参数 start response。这两样东西第一个是个字典,第二个呢是一个函数,这个函数拿到之后,在那个函数在 return 或者在向外提交你那个数据的时候,请你给他返回正文之前,请你调用他传进来这个 stata race boss ,这个函数里面一般要传3个信息,但是一般情况下你传2个就够了。
第一个 stater ,也就是说传入的状态码和他的描述第二个是 race boss ,也就是你要响应的头,这往往又是个字典或跟字典相当的,这个二元组组成集合往往都是这样子的。
有的时候也记不清了因为我们慢慢就不用太注重,所以我们想办法把它封装起来。
那这时,我们要处理东西,不管怎么样先调它,调用完之后把你组织好的正文内容放到一个可迭代对象里面去,然后 return 出去,别人要拿到你这个可迭代对象之后,要迭代的,迭代之后要把你这些每一个元素都通过 stp 封装,然后返回回去的,但是你返回的每一个这个封装你你这每一个元素我都要替你封装。
这些就是 WSGI 为我们提供的一套协议,那我们一般来讲遵守这套协议就可以开发了,遵守这套协议是 Python 为我们提供了一个参考的库,真要用的话一般来讲要使用 flask 这样的框架,那我们在学习的时候肯定会有很多障碍,所以我们必须先学 WSGI,以及我们看看他究竟是怎样处理数据的,因为他们代码量太大了,你一时半会都了解不了他到底是怎么处理数据的,很多层次数据在一个流程中不断的跑,跑到这个调用了,又被那个调用了,所以你看不清楚,所以我们就必须来自己写一个。
WSGI 主要规定了服务器端和应用程序间的接口。
WSGI 服务器——wsgiref
wsgiref 这个一个 WSGI 参考实现库
wsgiref.simple_server 模块实现一个简单的 WSGI HTTP 服务器.
wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer,
handler_class=WSGIRequestHandler)启动一个 WSGI 服务器
wsgiref.simple_server.demo_app(environ, start_response)]一个函数,小巧完整的 WSGI 的应用程序的实现
#返回文本例子
from wsgiref.simple_server import make_server, demo_app
ip ='127.0.0.1'
port = 9999
server = make_server(ip, port, demo_app)# demo_app 应用程序,可调用
server.serve_forever()# server.handle_request()执行—次
WSGI 服务器作用
监听 HTTP 服务端口(TCPServer,默认端口80)
接收浏览器端的 HTTP 请求并解析封装成 environ 环境数据
负责调用应用程序,将 environ 和 start_response 方法传入
将应用程序响应的正文封装成HTTP响应报文返回浏览器端
WSGI APP 应用程序端
1、应用程序应该是一个可调用对象
Python 中应该是函数、类、实现了_call_方法的类的实例
2、这个可调用对象应该接收两个参数
# 1 函数实现
def application(environ, start_response): pass
# 2 类实现
class Application: def _init_(self, environ, start_response): pass
# 3 类实现
class Application: def _call_(self, environ, start_response): pass
3、以上的可调用对象实现,都必须返回一个可迭代对象
#函数实现
def application(environ, start_response):
return [res_str]
#类实现
class Application:
def _init_(self, environ, start_response):
pass
def _iter_(self):#实现此方法,对象即可迭代
yield res_str
#类实现
class Application:
def _call_(self, environ, start_response):
return[res_str]
environ和start_response 这两个参数名可以是任何合法名,但是一般默认都是这2个名字。应用程序端还有些其他的规定,暂不用关心
environ
environ 是包含 Http 请求信息的dict对象
名称 |
含义 |
REQUEST_METHOD |
请求方法,GET、POST等 |
PATH_INFO |
URL中的路径部分 |
QUERY_STRING |
查询字符串 |
SERVER_NAME, SERVER_PORT |
服务器名、端口 |
HTTP_HOST |
地址和端口 |
SERVER_PROTOCOL |
协议 |
HTTP_USER_AGENT |
UserAgent信息 |
start_response
它是一个可调用对象。有3个参数,定义如下:
start_response(status, response_headers, exc_info=None) status 是状态码,例如 200 0K
response_headers 是一个元素为二元组的列表,例如[(Content-Type,'text/plain;charset=utf-8')]
exc_info 在错误处理的时候使用
start_response 应该在返回可迭代对象之前调用,因为它返回的是 Response Header。返回的可迭代对象是 Response Body.
服务器端
服务器程序需要调用符合上述定义的可调用对象 APP,传入 environ、start_response ,APP 处理后,返回响应头和可迭代对象的正文,由服务器封装返回浏览器端。
#返回网页的例子
from wsgiref.simple_server import make_server
def application(environ, start_response):
status ='200 OK'
headers =[('Content-Type",'text/html;charset=utf-8')]
start_response(status, headers)
#返回可迭代对象
html ='
马哥教育欢迎你
".encode("utf-8")
return [html]
ip ='127.0.0.1'
port = 9999
server = make_server(ip, port, application)
server.serve_forever()# server.handle_request()一次
simple_server 只是参考用,不能用于生产。
测试用命令
$ curl -I
http://192.168.142.1:9999/xxx?id=5|
$ecurl -x POST
http://192.168.142.1:9999/yyy
-d'{"x":2}'
使用 HEAD 方法
指定方法,-d 传输数据
到这里就完成了一个简单的 WEB 程序开发。
WEB 服务器
•本质上就是一个 TCP 服务器,监听在特定端口上
•支持 HTTP 协议,能够将 HTTP 请求报文进行解析,能够把响应数据进行 HTTP协议的报文封装并返回浏览器 端.
•实现了 WSGI 协议,该协议约定了和应用程序之间接口(参看 PEP333,https://www.python.org/dev/peps/p ep-0333/)
APP应用程序
•遵从 WSG 协议
•本身是一个可调用对象
•调用 start_response,返回响应头部
•返回包含正文的可迭代对象
为了更好的理解 WSGI 框架的工作原理,现在开始动手自己写一个 WEB 框架。
类 Flask 框架实现
从现在开始,我们将一步步完成一个WSGI的WEB框架,从而了解WEB框架的内部机制。
WSGI 请求 environ 处理
WSGI 服务器程序会帮我们处理 HTTP 请求报文,但是提供的 environ 还是一个用起来不方便的字典
http://127.6.0.1:9999/python/index.htm1?id=1234&name=tom
('SERVER_PROTOCOL','HTTP/1.1')
('wsgi.url_scheme",'http')
('HTTP_HOST','127.0.0.1:9999')
('SERVER_PORT','9999')
('REMOTE_ADDR','127.0.0.1')
('REQUEST_METHOD','GET')
('CONTENT_TYPE','text/plain') ('PATH_INFO",'/python/index.html')
('QUERY_STRING','id=1234&name=tom') ('HTTP_USER_AGENT','Mozilla/5.θ(Windows NT 6.1) ApplewebKit/537.36 (KHTML, like Gecko) Maxthon/5.0 Chrome/55.0.2883.75 Safari/537.36')
QUERY_STRING 查询字符串的解析
WSGI 服务器程序处理过 HTTP 报文后,返回一个字典,可以得到查询字符串('QUERY_STRING", 'id=12348name=tom')。这个键值对用起来不方便。
1、编程序解析
# id=5&name=wayne
qstr = environ.get('QUERY_STRING')
print(qstr)
if qstr:
for pair in qstr.split('&'):
k,_,v= pair.partition('=')
print("k={}, v={}".format(k,v))
# id=5&name=wayne
querystr = environ.get('QUERY_STRING')
if querystr:
querydict ={k:v for k,_,v in map(lambda item: item.partition('='), querystr.split('&'))} print(querydict)
2、使用 cgi 模块
# id=5&name=wayne
qstr= environ.get('QUERY_STRING')
print(qstr)
print(parse_qs(qstr))
#{'name":['wayne"],'id":['5']}
可以看到使用这个库,可以解析查询字符串,请注意value是列表,为什么? 这是因为同一个key可以有多个值。
cgi模块过期了,建议使用urllib
3、使用 urllib 库
#http://127.0.0.1:9999/?id=5&name=wayne&age=&commek=1,a,c&age=19&age=28
qstr = environ.get('QUERY_STRING')
print(qstr)
print(parse.parse_qs(qstr))#字典
print(parse.parse_qs1(qstr))#二元组列表
#运行结果
id=5&name=wayne&age=&comment=1,a,c&age=19&age=20
{'name":['wayne'],'age":['19','26'],'id':['5'],'comment':['1,a,c']}
[('id','5'),('name','wayne'),('comment",‘1,a,c'),('age','19'),('age','20')]
parse_qs函数,将同一个名称的多值,保存在字典中,使用了列表保存。
comment=1,a,c 这不是多值,这是一个值。
age 是多值。
environ 的解析——webob库
环境数据有很多,都是存在字典中的,字典的存取方式没有对象的属性访问方便。
使用第三方库 webob,可以把环境数据的解析、封装成对象。
webob 简介
Python 下,可以对 WSGI 请求进行解析,并提供对响应进行高级封装的库。
$ pip install webob
官网文档 docs.webod.org
webob.Request 对象
将环境参数解析并封装成 request 对象
GET 方法,发送的数据是 URL 中 Query string,在 Request Header 中。 request.GET 就是一个字典 MultiDict,里面就封装着查询字符串。
POST 方法,“提交”的数据是放在 Request Body 里面,但是也可以同时使用 Query String. request.POST 可以获取 Request Body 中的数据,也是个字典 MultiDict。
不关心什么方法提交,只关心数据,可以使用 request.params,它里面是所有提交数据的封装,。
request = webob.Request(environ)
print(request.headers)#类字典容器
print(request.method)
print(request.path)
print(request.query_string)#查询字符串
print(request.GET)# GET 方法的所有数据
print(request.POST)# POST 方法的所有数据
print('params ={}'.format(request.params))#所有数据,参数
MultiDict
MultiDict 允许一个 key 存了好几个值。
from webob.multidict import MultiDict
md = MultiDict()
md.add(1,'magedu')
md.add(1,'.com'】
md.add('a', 1)
md.add('a', 2)
md.add('b','3')
md['b']='4 for pair in
md.items(): print(pair) print(md.getall(1)】
#print(md.getone('a'))#只能有一个值
print(md.get('a'))#返回一个值
print(md.get('c'))#不会抛异常 KeyError,返回 None