徒手撸出一个类Flask微框架 理解HTTP请求 request和response
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了徒手撸出一个类Flask微框架 理解HTTP请求 request和response相关的知识,希望对你有一定的参考价值。
environ 环境参数
考虑到后期的事宜,引入第三方库 webob
https://docs.pylonsproject.org/projects/webob/en/stable/index.html
一、webob.request对象
将环境参数解析并封装成request对象
使用方法:
GET | 发送的数据是URL的查询字符串,在request头部信息中 就是一个字典MultiDict,封装着查询字符串 |
POST | 提交,数据放在请求的body中,但是也可以同时使用QERY_STRING |
只需要做一件事情:将其封装,都通过对象访问即可
这样的方式比字典访问好太多了
如果我们单用environ的方式进行定义的话会很麻烦,必须写明下面信息
def app(environ:dict,start_response):
html = '<h1>hi</h1>'
start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
return [html.encode()]
如果使用webob的方式,直接return它的方法就可以了
涉及模块功能:
· Request
· Response
需要用的功能
· webob.dec -- WSGIfy decorator
· webob.exc -- WebOb Exceptions
· webob.static -- Serving static files
安装webob
pip install webob
分别打印以下信息查看结果:
from wsgiref.simple_server import make_server, demo_app
from urllib.parse import parse_qs,parse_qsl #获取传递的查询信息
from webob import Request,Response
def app(environ:dict, start_response):
qstr = environ.get('QUERY_STRING')
print('~~~',qstr)
request = Request(environ)
print(request.method) # 请求的方法
print(request.path) # 请求路径
print(request.GET) # 请求方法GET是否有数据
print(request.POST) # 请求方法POST是否有数据
print(request.params) # MultiDict
html = '<ha>hello request</h1>'
start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
return [html.encode()]
if __name__ == "__main__":
ip = '127.0.0.1'
port = 9999
server = make_server(ip,port,app)
server.serve_forever()
server.server_close()
返回信息如下:
qstr:
method: GET
path: /
GET: GET([])
POST: <NoVars: Not a form request>
params: NestedMultiDict([])
127.0.0.1 - - [26/Dec/2017 19:43:59] "GET / HTTP/1.1" 200 22
访问字符串查询
http://127.0.0.1:9999/index.html?id=777&name=wang&name=chao
返回如下:
127.0.0.1 - - [26/Dec/2017 19:46:16] "GET /index.html?id=777&name=wang&name=chao HTTP/1.1" 200 22
qstr: id=777&name=wang&name=chao
method: GET
path: /index.html
GET: GET([('id', '777'), ('name', 'wang'), ('name', 'chao')])
POST: <NoVars: Not a form request>
params: NestedMultiDict([('id', '777'), ('name', 'wang'), ('name', 'chao')])
MuliDict
允许一个key存多个值,下面红色部分就是结果
再访问一个
qstr: name=tom&arg=19&age=18
method: GET
path: /index.html
GET: GET([('name', 'tom'), ('arg', '19'), ('age', '18')])
POST: <NoVars: Not a form request>
params: NestedMultiDict([('name', 'tom'), ('arg', '19'), ('age', '18')])
127.0.0.1 - - [26/Dec/2017 19:53:34] "GET /index.html?name=tom&arg=19&age=18 HTTP/1.1" 200 22
为何这么设计:
在网页开发中,在表单中都是有name的属性,name有可能重名,一旦重名返回的就是name=xxx&name=xxx&name=xxxnu
这样就需要用某种数据结构去接应它
导入模块MultiDict
from webob.multidict import MultiDict
创建一个实例:
from webob.multidict import MultiDict
md = MultiDict()
md[1] = 2
md.add(1,'b')
md.add(1,'com')
print(md.get(1))
print(md.getall(1))
返回如下:
com # 如果是get方法则返回最新加入的结果
[2, 'b', 'com'] # 如果getall则以列表方式返回所有add过的value
POST
POST没有值,借助postman工具传递
提交同样的value,
返回如下:
127.0.0.1 - - [26/Dec/2017 20:10:47] "POST / HTTP/1.1" 200 22
qstr:
method: POST
path: /
GET: GET([])
POST: MultiDict([('name', 'wang'), ('name', 'chao')])
params: NestedMultiDict([('name', 'wang'), ('name', 'chao')])
如果不关心什么方法提交,只关心数据直接打印params即可
web.Response对象
from webob import Response
res = Response()
print('status: ',res.status)
print('content_type: ',res.content_type)
print('charset: ',res.charset)
print('status_code: ',res.status_code)
from webob import Response
res = Response()
print('status: ',res.status)
print('content_type: ',res.content_type)
print('charset: ',res.charset)
print('status_code: ',res.status_code)
返回如下:
status: 200 OK
content_type: text/html
charset: UTF-8
status_code: 200
打印response头部信息
print(res.headerlist)
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
通过这样的可以构造状态和header,主要的目的是将start_response替换掉
查看Response()源码
res = Response() 《--查看源码
依次点开下面的方法查看过程
print('status: ',res.status)
print('content_type: ',res.content_type)
print('charset: ',res.charset)
print('status_code: ',res.status_code)
res.status_code = 404 ##
status_code 代码解读:
def _status_code__set(self, code):
try:
self._status = '%d %s' % (code, status_reasons[code])
except KeyError:
self._status = '%d %s' % (code, status_generic_reasons[code // 100])
#找到以下信息,发现已经帮我们定义好了,不需要关心
status_code = status_int = property(_status_code__get, _status_code__set,
doc=_status_code__get.__doc__)
这里面的http虽然server做了很多事情,但是对我们来讲还是不方便所以需要借助第三方库
查看Response源码如下:
如果是一个App实例可否用__call__方法来实现
查看源码:发现是一个__call__ 方法,说明可以直接调用
def __call__(self, environ, start_response):
"""
WSGI application interface
"""
if self.conditional_response:
return self.conditional_response_app(environ, start_response)
headerlist = self._abs_headerlist(environ) #response需要用到,因为请求头获取后要做响应头
start_response(self.status, headerlist)
if environ['REQUEST_METHOD'] == 'HEAD':
# Special case here...
return EmptyResponse(self._app_iter)
return self._app_iter
_safe_methods = ('GET', 'HEAD')
代码解读:
def __call__(self, environ, start_response):
传递了两个参数,
environ : 包含了所有的请求信息的字典,
start_response : 返回的头部信息
将头部信息通过_abs_headerlist 方法进行封装
headerlist = self._abs_headerlist(environ)
如果head头部信息,则返回一个空相应,如果不是则返回一个可迭代_app_iter
不过是将所谓的正文写在一个可迭代对象里面进行封装
start_response(self.status, headerlist)
if environ['REQUEST_METHOD'] == 'HEAD': #如果传进来的是一个HEAD是一个‘REQUEST_METHOD’ 则返回一个空,否则封装传出一个可迭代列表
# Special case here...
return EmptyResponse(self._app_iter)
return self._app_iter
由此可以得到下面的代码:
from webob import Response,Request
def app(environ,start_resonse): #environ 获取所有信息
request = Request(environ)
print(request.method)
print(request.path)
print(request.query_string)
print(request.GET)
print(request.POST)
print(request.params)
#构建Response
#响应处理,如果调用这个实例有一个默认实现
res = Response()
res.status_code = 200
print(res.content_type)
html = 'hhh'.encode()
res.body = html #将响应的body写在这里就可以了
return res(environ, start_response) #通过__call__ 方法实现return
if __name__ == "__main__":
ip = '127.0.0.1'
port = 9999
server = make_server(ip,port,app)
server.serve_forever()
server.server_close()
既然__call__有意义,那么是否可以直接调用?
当业务逻辑完成之后则进行response
res = response() 默认是可行的
如果什么都不想改则进行res.body = xxx 直接返回
如果改成这样的语句:
return res(environ, start_response)
传递environ, start_response 这两个参数与
start_response(res,status,res.headerlist)
return [html.encode()]
有什么关联?
return res(environ, start_response)
实际是在调用response __call__方法,实际是一个实例的
这个__call__方法实际完成中实际是存有值的,就是body
因为在调用call方法 值已经赋给了res.body属性,状态码如果没有被赋值,则自动调用下面代码
start_response(self.status, headerlist)
将状态吗和headerlist传递进来
headerlist就是传过去的类型html ,实际都是默认的属性
__call__方法不过是实现了一个类似自定义的接口
传递这个接口之前是执行了一个实例的方法,这个实例是通过res = Response()创建出来的
调__call__方法就相当于将environ, start_response 传递,之后执行返回响应头和可迭代对象
只不过这个可迭代对象封装在body里, 而这个body由被封装在app.iter中
最后改进后的代码如下:
from webob import Request,Response
from wsgiref.simple_server import make_server, demo_app
def application(environ:dict,start_response):
request = Request(environ)
print('method: ',request.method)
print('path: ',request.path)
print('GET: ',request.GET)
print('POST: ',request.POST)
print('parms: ', request.params)
print('query_str: ',request.query_string)
#构建response
res = Response('<h1>hahah</h1>')
return res(environ,start_response)
ip = '127.0.0.1'
port = 9999
server = make_server(ip,port,application)
server.serve_forever()
server.server_close()
webob.dec 装饰器
https://docs.pylonsproject.org/projects/webob/en/latest/api/dec.html?highlight=wsgify
wsgify装饰器将一个普通函数转变成WSGI应用程序
导入模块
from webob.dec import wsgify
官方说明依然需要实例化一个response
例:
@wsgify
def myfunc(req:Request):
return Response('hey here')
用装饰器,并传递参数,这样就是被包裹过的request,
当返回这个值,则就被当作它的response
这个response必须用webob的response方法
@wsgify
def myfunc(request:Request):
return Response('hey here')
if __name__ == "__main__":
ip = '127.0.0.1'
port = 9999
try:
server = make_server(ip,port,myfunc)
except:
server.serve_forever()
server.server_close()
装饰的函数应该具有一个参数,这个参数就是被包装后的request类型,是对字典environ的对象化后的实例,但是返回值必须是一个webob.Resopnse类型,所以需要在函数中return webob.Resopnse类型
return Response('hey here')
以上是关于徒手撸出一个类Flask微框架 理解HTTP请求 request和response的主要内容,如果未能解决你的问题,请参考以下文章