徒手擼出一個類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')

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章