Python_Web_App_Day_1:編寫Web App骨架

用到知識點:logging、asyncio、aiohttp模塊(後續補充知識點)html

一、logging(日誌)模塊nginx

功能:web

  提供了標準的日誌接口,記錄程序日誌,包含正常的程序訪問日誌、錯誤、警告等信息輸出編程

  能夠存爲各類格式的日誌數據瀏覽器

  日誌分爲:debug()、info()、warning()、error()、critical()5個級別(級別:DEBUG < INFO < WARNING < ERROR < CRITICAL)服務器

import logging

print(logging.DEBUG)     # 10
print(logging.INFO)      # 20
print(logging.WARNING)   # 30
print(logging.ERROR)     # 40
print(logging.CRITICAL)  # 50

  默認輸出只顯示大於等於WARNING的日誌網絡

import logging

logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

"""
輸出:
WARNING:root:warning message
ERROR:root:error message
CRITICAL:root:critical message
"""

  經過logging.basicConfig()函數靈活配置日誌級別,日誌格式,輸出位置併發

  在logging.basicConfig()函數中可經過具體參數來更改logging模塊默認行爲,可用參數有:app

  filename:用指定的文件名建立FiledHandler(後邊會具體講解handler的概念),這樣日誌會被存儲在指定的文件中。框架

  filemode:文件打開方式,在指定了filename時使用這個參數,默認值爲「a」還可指定爲「w」。

  format:指定handler使用的日誌顯示格式。

  datefmt:指定日期時間格式。

  level:設置rootlogger(後邊會講解具體概念)的日誌級別

  stream:用指定的stream建立StreamHandler。能夠指定輸出到sys.stderr,sys.stdout或者文件,默認爲sys.stderr。若同時列出了filename和stream兩個參數,則stream參數會被忽略。

  format參數中可能用到的格式化串:
  %(name)s Logger的名字
  %(levelno)s 數字形式的日誌級別
  %(levelname)s 文本形式的日誌級別
  %(pathname)s 調用日誌輸出函數的模塊的完整路徑名,可能沒有
  %(filename)s 調用日誌輸出函數的模塊的文件名
  %(module)s 調用日誌輸出函數的模塊名
  %(funcName)s 調用日誌輸出函數的函數名
  %(lineno)d 調用日誌輸出函數的語句所在的代碼行
  %(created)f 當前時間,用UNIX標準的表示時間的浮 點數表示
  %(relativeCreated)d 輸出日誌信息時的,自Logger建立以 來的毫秒數
  %(asctime)s 字符串形式的當前時間。默認格式是 「2003-07-08 16:49:45,896」。逗號後面的是毫秒
  %(thread)d 線程ID。可能沒有
  %(threadName)s 線程名。可能沒有
  %(process)d 進程ID。可能沒有
  %(message)s用戶輸出的消息

import logging
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='test.log',
                    filemode='w')
"""
level=logging.DEBUG:修改默認輸出級別爲Debug
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s':指定handler使用的日誌顯示格式。
    %(asctime)s:字符串形式的當前時間
    %(filename)s:調用日誌輸出函數的模塊的文件名
    [line:%(lineno)d]:調用日誌輸出函數的語句所在的代碼行
    %(levelname)s:文本形式的日誌級別
    %(message)s:用戶輸出的消息
datefmt='%a, %d %b %Y %H:%M:%S':設置日期格式
    %a:星期
    %d:日期
    %b:月份
    %Y:年
    %H:%M:%S:時:分:秒
filename='test.log':設置日誌輸出文件
filemode='w':設置日誌輸出文件打開方式
"""

logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')

"""
test.log文件內容:
Wed, 21 Jun 2017 16:54:30 test.py[line:12] DEBUG debug message
Wed, 21 Jun 2017 16:54:30 test.py[line:13] INFO info message
Wed, 21 Jun 2017 16:54:30 test.py[line:14] WARNING warning message
Wed, 21 Jun 2017 16:54:30 test.py[line:15] ERROR error message
Wed, 21 Jun 2017 16:54:30 test.py[line:16] CRITICAL critical message

"""

 二、asyncio(異步IO)模塊

  用協程實現生產消息者模型

  其實next()和send()在必定意義上做用是類似的,區別是send()能夠傳遞yield表達式的值進去,而next()只能傳遞None進去。所以c.next() 和 c.send(None) 做用是同樣的。

  第一次調用時,請使用next()語句或是send(None),不能使用send發送一個非None的值,不然會出錯的,由於沒有Python yield語句來接收這個值。

def consumer():
    r = ''
    while True:
        n = yield r  # 跳出生成器,n沒有定義
        if not n:
            return
        print('[CONSUMER] Consuming %s...' %n)
        r = '200 OK'
def produce(c):
    c.send(None)  # 啓動生成器,從生成器函數的第一行代碼開始執行
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCE] Producing %s....' %n)
        r = c.send(n) #獲取yield返回值
        print('[PRODUCE] Consumer return %s....' %r)
    c.close()  # 關閉consumer,整個過程結束
c = consumer()  # 建立生成器對象
produce(c)


"""
輸出:
[PRODUCE] Producing 1....
[CONSUMER] Consuming 1...
[PRODUCE] Consumer return 200 OK....
[PRODUCE] Producing 2....
[CONSUMER] Consuming 2...
[PRODUCE] Consumer return 200 OK....
[PRODUCE] Producing 3....
[CONSUMER] Consuming 3...
[PRODUCE] Consumer return 200 OK....
[PRODUCE] Producing 4....
[CONSUMER] Consuming 4...
[PRODUCE] Consumer return 200 OK....
[PRODUCE] Producing 5....
[CONSUMER] Consuming 5...
[PRODUCE] Consumer return 200 OK....

"""

   asyncio的編程模型就是一個消息循環,經過async關鍵字定義一個協程(coroutine),協程也是一種對象。

  協程不能直接運行,須要把協程加入到事件循環(loop),由後者在適當的時候調用協程。

  asyncio.get_event_loop方法能夠建立一個事件循環,而後使用run_until_complete將協程註冊到事件循環,並啓動事件循環。

import asyncio

async def hello():  # async定義一個協程
    print('hello world!')

    # await用於掛起阻塞的異步調用接口。
    # 異步調用,把asyncio.sleep(1)當作是一個耗時1秒的IO操做
    r = await asyncio.sleep(1)

    print('hello again!')

# 建立一個事件循環
loop = asyncio.get_event_loop()

# 將協程註冊到事件循環,並啓動事件循環
loop.run_until_complete(hello())

# 關閉事件循環
loop.close()

用task封裝兩個coroutine

import asyncio
import threading

async def hello():  # async定義一個協程
    print('hello world! (%s)' %threading.current_thread())

    # await用於掛起阻塞的異步調用接口。
    # 異步調用,把asyncio.sleep(1)當作是一個耗時1秒的IO操做
    r = await asyncio.sleep(1)

    print('hello again! (%s)' %threading.current_thread())

# 建立一個事件循環
loop = asyncio.get_event_loop()

tasks = [hello(), hello()]
# 將協程註冊到事件循環,並啓動事件循環
loop.run_until_complete(asyncio.wait(tasks))

# 關閉事件循環
loop.close()

"""
輸出:
hello world! (<_MainThread(MainThread, started 13160)>)
hello world! (<_MainThread(MainThread, started 13160)>)
(間隔約1秒後再輸出下面)
hello again! (<_MainThread(MainThread, started 13160)>)
hello again! (<_MainThread(MainThread, started 13160)>)
"""

咱們用asyncio的異步網絡鏈接來獲取sina、sohu和163的網站首頁

import asyncio


async def wget(host):  # async定義一個協程
    print('wget %s...' %host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = await connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))

    # 刷新底層傳輸的寫緩衝區。也就是把須要發送出去的數據,從緩衝區發送出去
    await writer.drain()
    while True:
        line = await reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
    writer.close()



# 建立一個事件循環
loop = asyncio.get_event_loop()

tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
# 將協程註冊到事件循環,並啓動事件循環
loop.run_until_complete(asyncio.wait(tasks))

# 關閉事件循環
loop.close()

"""
輸出:
wget www.sohu.com...
wget www.sina.com.cn...
wget www.163.com...
www.163.com header > HTTP/1.0 302 Moved Temporarily
www.163.com header > Server: Cdn Cache Server V2.0
www.163.com header > Date: Wed, 21 Jun 2017 11:13:32 GMT
www.163.com header > Content-Length: 0
www.163.com header > Location: http://www.163.com/special/0077jt/error_isp.html
www.163.com header > Connection: close
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Server: nginx
www.sina.com.cn header > Date: Wed, 21 Jun 2017 11:12:45 GMT
www.sina.com.cn header > Content-Type: text/html
www.sina.com.cn header > Last-Modified: Wed, 21 Jun 2017 11:11:09 GMT
www.sina.com.cn header > Vary: Accept-Encoding
www.sina.com.cn header > Expires: Wed, 21 Jun 2017 11:13:45 GMT
www.sina.com.cn header > Cache-Control: max-age=60
www.sina.com.cn header > X-Powered-By: shci_v1.03
www.sina.com.cn header > Age: 47
www.sina.com.cn header > Content-Length: 597663
www.sina.com.cn header > X-Cache: HIT from cnc.tianjin.sinacache.91.nb.sinaedge.com
www.sina.com.cn header > Connection: close
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html;charset=UTF-8
www.sohu.com header > Connection: close
www.sohu.com header > Server: nginx
www.sohu.com header > Date: Wed, 21 Jun 2017 11:13:27 GMT
www.sohu.com header > Cache-Control: max-age=60
www.sohu.com header > X-From-Sohu: X-SRC-Cached
www.sohu.com header > Content-Encoding: gzip
www.sohu.com header > FSS-Cache: HIT from 8212655.14766265.9212435
www.sohu.com header > FSS-Proxy: Powered by 2969695.4280425.3969395

"""

 三、asyncio能夠實現單線程併發IO操做。

  asyncio用在服務器端,例如Web服務器,因爲HTTP鏈接就是IO操做,所以能夠用單線程+coroutine實現多用戶的高併發支持。

  asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架。

編寫一個HTTP服務器,分別處理如下URL:

  • / - 首頁返回b'<h1>Index</h1>'

  • /hello/{name} - 根據URL參數返回文本hello, %s!

import asyncio
from aiohttp import web

async def index(request):
    await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>', content_type='text/html')

async def hello(request):
    await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' %request.match_info['name']
    return web.Response(body=text.encode('utf-8'), content_type='text/html')

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
    app.router.add_route('GET', '/hello/{name}', hello)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8500)
    print('Server started at http://127.0.0.1:8500...')
    return srv

loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

一、輸入瀏覽器:

  http://127.0.0.1:8500/hello/char  

瀏覽器輸出:

  hello, char!

二、輸入瀏覽器:

  http://127.0.0.1:8500

瀏覽器輸出:

  Index!

"""
app.py編寫Web App骨架
"""

import logging; logging.basicConfig(level=logging.INFO)
import asyncio
from aiohttp import web

async def index(request):
    """響應函數"""
    return web.Response(body=b'<h1>Awesome</h1>', content_type='text/html')

async def init(loop):
    """Web App服務器初始化"""
    # 製做響應合集
    app = web.Application(loop=loop)

    # 把響應函數添加到響應函數集合
    app.router.add_route(method='GET', path='/', handler=index)

    # 建立服務器(鏈接網址、端口,綁定handler)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 9000)

    logging.info('server start at http://127.0.0.1:9000')

    return srv

# 建立事件
loop = asyncio.get_event_loop()

# 運行
loop.run_until_complete(init(loop))

# 服務器不關閉
loop.run_forever()
相關文章
相關標籤/搜索