徒手撸出一个类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

·         Exceptions

 

需要用的功能

·         webob.dec -- WSGIfy decorator

·         webob.exc -- WebOb Exceptions

·         webob.request -- Request

·         webob.response -- Response

·         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必须用webobresponse方法

 

@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的主要内容,如果未能解决你的问题,请参考以下文章

徒手撸出一个类Flask微框架根据业务进行路由分组

Flask框架基础功能

徒手打造你的MVC框架

响应微博小秘书倡议 连夜加急撸出头像变灰小工具之开发历程

徒手撸了一个RPC框架,理解更透彻了,代码已上传github,自取~

Python微框架Flask源码剖析