[Python WEB開發] 使用WSGI開發類Flask框架 (二)

 

 

 

WSGI     Web服務器網關接口html

WSGI主要規定了Web服務器如何與Web應用程序進行通訊,以及如何將Web應用程序連接在一塊兒來處理一個請求。python

 

 

 

 

wsgiref  Python中的WSGI參考模塊git

 

1、WSGI 應用程序端:github

一、 根據WSGI定義,應用程序應該是可調用對象web

二、該可調用對象必須有兩個固定參數:environ、start_responseapi

一個是含有服務器環境變量的字典,另外一個是可調用對象,該對象使用HTTP狀態碼和會返回給客戶端的HTTP頭來初始化響應瀏覽器

environ 變量包含一些熟悉的環境變量,如HTTP_HOST,HTTP_USER_AGENT,REMOTE_ADDR,REQUEST_METHOD,SERVER_PORT,部分以下:緩存

Hello world!

GATEWAY_INTERFACE = 'CGI/1.1'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip, deflate, br'
HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9,en;q=0.8'
HTTP_CONNECTION = 'keep-alive'
HTTP_HOST = '127.0.0.1:9999'
HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
QUERY_STRING = ''
REMOTE_ADDR = '127.0.0.1'
REQUEST_METHOD = 'GET'
SERVER_PORT = '9999'
SERVER_PROTOCOL = 'HTTP/1.1'
SERVER_SOFTWARE = 'WSGIServer/0.2'

 

 

三、這個可調用對象必須返回一個可迭代對象用於組成響應服務器

res_str = b'github.com\n'

# 函數實現
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):
		retur [res_str]

  

 

 

 

wsgiref參考庫中有如下幾個子模塊:cookie

* util -- 一些有用的功能和包裝

* headers -- 管理響應頭

* handlers -- 爲server/gateway實現如何處理的基類

* simple_server -- 實現一個簡單的WSGI HTTP服務器

* validate -- 位於應用程序和server之間檢測錯誤的校驗包裝

 

 

2、WSGI HTTP Server端的使用

1. 啓動一個簡單的WSGI HTTP Server:

# 簡單web 1
from wsgiref.simple_server import make_server


def demo_app(environ, start_response): #copy自simple_server模塊
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k, v in h:
        print(k, '=', repr(v), file=stdout)
    start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]


ip = '127.0.0.1'
port = 9999
server = make_server(ip, port, demo_app)
server.serve_forever()

server.server_close()
 
 
wsgiref.simple_server.make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler)
經過這個函數能夠啓動一個用於簡單訪問的WSGI參考服務器,必須傳入host, port, app三個參數。

 

在運行這段程序以後,就已經實現了一個監聽在9999端口的webServer,下面是服務端運行狀態和瀏覽器中訪問結果:

 

訪問 http://127.0.0.1:9999/

#server端運行狀態:
127.0.0.1 - - [26/Dec/2017 15:01:13] "GET / HTTP/1.1" 200 2128
127.0.0.1 - - [26/Dec/2017 15:01:13] "GET /favicon.ico HTTP/1.1" 200 2096


#瀏覽器訪問結果:
Hello world!

Apple_PubSub_Socket_Render = '/private/tmp/com.apple.launchd.Gx10g4snot/Render'
CLICOLOR = '1'
CONTENT_LENGTH = ''
CONTENT_TYPE = 'text/plain'
GATEWAY_INTERFACE = 'CGI/1.1'
GREP_OPTIONS = '--color=auto'
HOME = '/Users/ihoney'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip, deflate, br'
HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9,en;q=0.8'
HTTP_CONNECTION = 'keep-alive'
HTTP_HOST = '127.0.0.1:9999'
HTTP_UPGRADE_INSECURE_REQUESTS = '1'
HTTP_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
LC_CTYPE = 'zh_CN.UTF-8'
......

  

2. 自定義響應的網頁內容:

# 簡單web 2
from wsgiref.simple_server import make_server

def application(environ:dict,start_response):
    # print(type(environ),environ)
    html = "<h1>北京歡迎你</h1>"
    # start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) #文本格式
    start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')]) #html格式

    return [html.encode()]


ip = '127.0.0.1'
port =9999
server = make_server(ip,port,application)
server.serve_forever()

server.server_close()


#運行結果:
127.0.0.1 - - [26/Dec/2017 15:38:55] "GET / HTTP/1.1" 200 24
127.0.0.1 - - [26/Dec/2017 15:38:55] "GET /favicon.ico HTTP/1.1" 200 24

  瀏覽器訪問結果:

  simple_server 只是參考,不可用於生產環境。

 

 

3、QUERY_STRING 查詢字符串的解析

1. 使用cgi模塊:

# 簡單web 3 使用cgi模塊解析query_string
import cgi
from wsgiref.simple_server import make_server

def application(environ:dict,start_response):
    qstr = environ.get("QUERY_STRING")
    print(qstr)
    # ?id=5&name=ihoney&age=18,19
    print(cgi.parse_qs(qstr)) #字典,value爲列表類型
    print(cgi.parse_qsl(qstr)) #二元組列表

    html = "<h1>北京歡迎你</h1>"
    start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
    return [html.encode()]


ip = '127.0.0.1'
port =9999
server = make_server(ip,port,application)
server.serve_forever()

server.server_close()

#瀏覽器訪問http://127.0.0.1:9999/?id=5&name=ihoney&age=18,19
#運行結果:
127.0.0.1 - - [26/Dec/2017 15:51:17] "GET /?id=5&name=ihoney&age=18,19 HTTP/1.1" 200 24
id=5&name=ihoney&age=18,19
{'age': ['18,19'], 'name': ['ihoney'], 'id': ['5']}
[('id', '5'), ('name', 'ihoney'), ('age', '18,19')]

  在寫的時候IDE工具就會提示CGI模塊已通過期了,建議使用urllib庫。

2. 使用urllib庫

# 簡單web 4 使用urllib模塊解析query_string
from urllib import parse
from wsgiref.simple_server import make_server

def application(environ:dict,start_response):
    qstr = environ.get("QUERY_STRING")
    print(qstr)
    # ?id=5&name=ihoney&age=18,19
    print(parse.parse_qs(qstr)) #字典,value爲列表類型
    print(parse.parse_qsl(qstr)) #二元組列表

    html = "<h1>北京歡迎你</h1>"
    start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
    return [html.encode()]


ip = '127.0.0.1'
port =9999
server = make_server(ip,port,application)
server.serve_forever()

server.server_close()

#瀏覽器訪問:http://127.0.0.1:9999/?id=5&name=ihoney&age=18,19
#運行結果:
id=5&name=ihoney&age=18,19
{'id': ['5'], 'age': ['18,19'], 'name': ['ihoney']}
[('id', '5'), ('name', 'ihoney'), ('age', '18,19')]
127.0.0.1 - - [26/Dec/2017 15:58:40] "GET /?id=5&name=ihoney&age=18,19 HTTP/1.1" 200 24

  

3. 使用第三方庫webob

pip3 install webob

第三方庫webob能夠把環境數據的解析封裝成對象,使用時直接調用。

webob.request module:

req.method: 請求方法
req.GET: 返回一個類字典對象,GET請求方式提交的查詢字符串的二元組格式。
req.POST: 也返回一個類字典對象,POST請求正文的查詢字符串。通常是表單提交
req.params: 一個類字典對象,包括GET和POST的全部查詢字符串。
req.body: POST提交的請求正文的內容
req.cookies: 字典格式的全部cookie
req.headers: 包含全部請求頭的字典,不區分大小寫

web.response module:

response.status: 響應碼加描述信息,如"200 OK"
response.status_code: 響應碼,只有 "200"
response.headerlist: 全部響應頭的列表,如"[('Content-Type', 'text/html')]"
response.app_iter: 一個可迭代對象(如列表和生成器),用於產生響應的內容
response.content_type: 響應內容的類型,如"text/html","text/plain"
response.charset: 字符集編碼類型
response.set_cookie(name=None, value='', max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None, overwrite=False): 爲客戶端設置一個cookie,max_age控制cookie的有效時長,以秒爲單位
response.delete_cookie(key, path='/', domain=None): 從客戶端刪除一個cookie
response.cache_expires(seconds=0): 設置這個響應的緩存時間,單位爲秒,若是seconds爲0表示這個響應不緩存
response(environ, start_response): 返回對象是一個WSGI應用程序的響應

 

 

3.1 webob.Request

#簡單web 5,使用第三方庫webob解析
from wsgiref.simple_server import make_server
from webob import Request, Response


def application(environ: dict, start_response):
    request = Request(environ)
    print(request.method)
    print(request.path)
    print(request.GET)
    print(request.POST)
    print(request.params)
    print(request.query_string)

    html = "<h1>北京歡迎你</h1>"
    start_response("200 OK", [('Content-Type', 'text/html; charset=utf-8')])
    return [html.encode()]


ip = '127.0.0.1'
port = 9999
server = make_server(ip, port, application)
server.serve_forever()

server.server_close()

#瀏覽器訪問:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
#運行結果:
GET
/index.html
GET([('id', '5'), ('name', 'tom,jerry'), ('age', '17'), ('age', '18,19')])
<NoVars: Not a form request>
NestedMultiDict([('id', '5'), ('name', 'tom,jerry'), ('age', '17'), ('age', '18,19')])
id=5&name=tom,jerry&age=17&age=18,19
127.0.0.1 - - [26/Dec/2017 16:51:41] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 24

  

3.2 webob.Resphone

#
from wsgiref.simple_server import make_server
from webob import Request, Response


def application(environ: dict, start_response):
    res = Response("<h1>北京歡迎你</h1>")
    return res(environ,start_response)   #__call__


ip = '127.0.0.1'
port = 9999
server = make_server(ip, port, application)
server.serve_forever()

server.server_close()

#瀏覽器訪問:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
#運行結果:
127.0.0.1 - - [26/Dec/2017 18:08:03] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 24

  

3.3 MultiDict

MultiDict容許一個key存好幾個值。

Request.GET、Request.POST 都是MultiDict字典

# multidict
from webob.multidict import MultiDict

md = MultiDict()
md[1] = 'b'
md.add(1,'a')

print(md.get(1)) #只返回一個值
print(md.getall(1))
# print(md.getone(1)) #要求key的value只能有一個,不然拋KeyError異常
print(md.get('c')) #不存在返回默認值None
#運行結果:
a
['b', 'a']
None

 

3.4 webob.dec.wsgify 裝飾器

官方文檔:https://docs.pylonsproject.org/projects/webob/en/stable/api/dec.html

功能:將一個函數變成一個WSGI應用程序

使用舉例:

from webob.dec import wsgify

@wsgify
    def myfunc(req):
        return webob.Response('hey there')

  wsgi裝飾器裝飾的函數應該具備一個參數,這個參數是webob.Request類型,是對字典environ的對象化後的實例。返回值必須是一個webob.Respnose類型,因此在函數中應該建立一個webob.Response類型的實例。

# wsgify
from wsgiref.simple_server import make_server
from webob import Request, Response,dec


def application(environ: dict, start_response):
    res = Response("<h1>北京歡迎你</h1>")
    #200 OK
	#[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
    return res(environ,start_response)   #__call__

@dec.wsgify
def app(request:Request) -> Response:
	return Response("<h1>Welcome to BeiJing</h1>")


ip = '127.0.0.1'
port = 9999
# server = make_server(ip, port, application)
server = make_server(ip, port, app)
server.serve_forever()

server.server_close()

#瀏覽器訪問:http://127.0.0.1:9999/index.html?id=5&name=tom,jerry&age=17&age=18,19
#運行結果:
127.0.0.1 - - [26/Dec/2017 20:25:14] "GET /index.html?id=5&name=tom,jerry&age=17&age=18,19 HTTP/1.1" 200 27
127.0.0.1 - - [26/Dec/2017 20:25:14] "GET /favicon.ico HTTP/1.1" 200 27

  

 改進:

from wsgiref.simple_server import make_server
from webob import Request, Response,dec


@dec.wsgify
def app(request:Request) -> Response:
	return Response("<h1>Welcome to BeiJing.</h1>")

if __name__ == "__main__":
	ip = '127.0.0.1'
	port = 9999
	server = make_server(ip, port, app)
	try:
		server.serve_forever()
	except KeyBoardInterrupt:
		pass
	finally:
		server.server_close()

  

3.5  webob.Response SourceCode

    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)

        start_response(self.status, headerlist)
        if environ['REQUEST_METHOD'] == 'HEAD':
            # Special case here...
            return EmptyResponse(self._app_iter)
        return self._app_iter

  

Chrome插件Postman POST 提交:

 

總結:

本文簡單介紹了WSGI、WSGI HTTP Server、查詢字符串的處理、第三方庫webob的一些用法。

相關文章
相關標籤/搜索