Tornado HTTP

HTTP協(xié)議是建立在TCP基礎(chǔ)之上的應(yīng)用層協(xié)議饮焦,Tornado中TCP層由TCPServerIOStream負(fù)責(zé),對HTTP報文的讀取與解析則是由http1connection負(fù)責(zé)的。當(dāng)HTTP報文被解析完畢后再交由某個delegate代理類實例負(fù)責(zé)進(jìn)行后續(xù)處理。

HTTP服務(wù)器工作流程

HTTP服務(wù)器工作流程

Web服務(wù)器工作的三部曲是什么呢蚜锨?

  1. 創(chuàng)建:服務(wù)器創(chuàng)建Socket并在指定地址的端口上進(jìn)行監(jiān)聽,等待客戶端請求的到來慢蜓。
  2. 監(jiān)聽:監(jiān)聽的Socket接收客戶端的請求亚再,當(dāng)?shù)玫娇蛻舳说腟ocket時,通過客戶端Socket與客戶端保持通信晨抡。
  3. 處理:處理客戶端的請求氛悬,首先客戶端的Socket中讀取HTTP請求的協(xié)議頭,如果是POST則可能需要讀取客戶端上傳的數(shù)據(jù)耘柱,讓然后處理請求如捅。最后準(zhǔn)備好客戶端所需的數(shù)據(jù)后通過客戶端Socket回寫給客戶端。

例如:使用Python編寫HTTP服務(wù)器

$ vim server.py
#!/usr/bin/python3
# -*- coding:utf-8 -*-

import socket

def handle_request(client):
    buf = client.recv(1024)
    print(buf)
    client.send(bytes("HTTP/1.1 200 OK\r\n\r\n", "utf-8"))
    client.send(bytes("hello world", "utf-8"))

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("127.0.0.1", 8000))
    sock.listen(3)
    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()

if __name__ == "__main__":
    main()
$ python3 server.py

調(diào)試運行

$ curl http://127.0.0.1:8000
hello world

Tornado的HTTP服務(wù)器工作流程

Tornado Web程序編程思路

  1. 創(chuàng)建Web應(yīng)用程序?qū)嵗龑ο?/li>
  2. 定義實現(xiàn)路由映射表中的請求多處理器類
  3. 創(chuàng)建服務(wù)器實例并綁定端口
  4. 啟動當(dāng)前線程的事件循環(huán)

根據(jù)上述分析會將Web框架劃分為兩大部分

  1. 待請求階段
    創(chuàng)建服務(wù)器Socket并監(jiān)聽端口
  2. 處理請求階段
    當(dāng)有客戶端連接時接收請求并根據(jù)請求的不同做出相應(yīng)的響應(yīng)

當(dāng)Tornado程序啟動以及接收到客戶端請求的過程可分為兩部分

  1. 啟動程序階段/待請求階段
  • 第一步:獲取配置文件然后生成URL映射调煎,即一個URL對應(yīng)一個RequestHandler請求處理器镜遣,從而使請求處理器來處理制定URL發(fā)送的請求。
  • 第二步:創(chuàng)建服務(wù)器Socket對象并添加到Epoll中
  • 第三步:創(chuàng)建無限循環(huán)去監(jiān)聽Epoll
  1. 接收并處理請求階段
  • 第一步:接收客戶端Socket發(fā)送的請求socket.accept
  • 第二步:從請求中獲取請求頭信息士袄,在根據(jù)請求頭中的請求URL去匹配對應(yīng)請求處理器RequestHandler悲关。
  • 第三步:成功匹配請求處理器
  • 第四步:將處理后的請求發(fā)送給客戶端
  • 第五步:關(guān)閉客戶端Socket

例如:使用Tornado實現(xiàn)單進(jìn)程的HTTP服務(wù)器

$ vim server.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.options import define, options
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

# 自定義配置項
define("port", type=int, default=8000)
define("debug", type=bool, default=True)

# 請求處理器
class IndexHandler(RequestHandler):
    def get(self):
        print("index handler get method")
        self.write("hello world")

# 創(chuàng)建應(yīng)用
# 定義路由表
urls = [
    (r"/index", IndexHandler)
]
# 定義應(yīng)用配置
configs = dict(debug = options.debug)
# 指定路由和配置創(chuàng)建應(yīng)用程序?qū)ο?app = Application(urls, configs)

# 程序主入口
if __name__ == "__main__":
    # 解析命令行參數(shù)
    options.parse_command_line()
    # 為應(yīng)用創(chuàng)建HTTP服務(wù)器
    server = HTTPServer(app)
    print("http server start")
    # 為HTTP服務(wù)器設(shè)置監(jiān)聽端口
    server.listen(options.port)
    print("http server listen port %s" % options.port)
    # 開啟事件循環(huán)接收客戶端連接
    IOLoop.current().start()
$ python server.py

查看進(jìn)程

$ ps -ef | grep server.py
jc        5047  3946  1 00:50 pts/0    00:00:00 python server.py
jc        5051  4169  0 00:50 pts/1    00:00:00 grep --color=auto server.py

訪問測試

$ curl http://127.0.0.1:8000/index
hello world

注意

  • listen()方法只能在單進(jìn)程模式中使用

例如:使用Tornado實現(xiàn)多進(jìn)程的HTTP服務(wù)器

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.options import define, options
from tornado.web import Application, RequestHandler
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

# 自定義配置項
# 定義端口號
define("port", type=int, default=8000)
# 定義調(diào)試模式
define("debug", type=bool, default=True)
# 定義子進(jìn)程個數(shù)
define("process", type=int, default=2)

# 請求處理器
class IndexHandler(RequestHandler):
    def get(self):
        print("index handler get method")
        self.write("hello world")


# 創(chuàng)建應(yīng)用
# 定義路由表
urls = [
    (r"/index", IndexHandler)
]
# 定義應(yīng)用配置
configs = dict(debug = options.debug)
# 指定路由和配置創(chuàng)建應(yīng)用程序?qū)ο?app = Application(urls, configs)

# 程序主入口
if __name__ == "__main__":
    # 解析命令行參數(shù)
    options.parse_command_line()
    # 為應(yīng)用創(chuàng)建HTTP服務(wù)器
    server = HTTPServer(app)
    print("http server start")
    # 為HTTP服務(wù)器綁定監(jiān)聽端口
    server.bind(options.port)
    # 為HTTP服務(wù)器開啟監(jiān)聽,并設(shè)置開啟進(jìn)程的數(shù)量num_process。
    # 多進(jìn)程數(shù)量默認(rèn)為1
    # 多進(jìn)程數(shù)量若小于等于0或為None則啟動會根據(jù)及其硬件的CPU核心數(shù)量創(chuàng)建等同數(shù)量的子進(jìn)程
    # 多進(jìn)程數(shù)量大于0則表示實際創(chuàng)建子進(jìn)程的數(shù)量
    server.start(options.process)
    print("http server listen port %s" % options.port)
    # 開啟事件循環(huán)接收客戶端連接
    IOLoop.current().start()

開啟服務(wù)并查看進(jìn)程數(shù)量

$ python server.py
$ ps -ef | grep server.py
jc        5079  3946  1 00:59 pts/0    00:00:00 python server.py
jc        5080  5079  0 00:59 pts/0    00:00:00 python server.py
jc        5081  5079  0 00:59 pts/0    00:00:00 python server.py
jc        5084  4169  0 00:59 pts/1    00:00:00 grep --color=auto server.py

注意

  • 每個子進(jìn)程都會從父進(jìn)程中復(fù)制一份IOLoop事件循環(huán)實例娄柳,若在創(chuàng)建子進(jìn)程前修改了IOLoop實例則會影響到每個子進(jìn)程寓辱。
  • 所有進(jìn)程是由一條命令一次性開啟的,所以是無法做到在不停服的情況下更新代碼赤拒。
  • 所有進(jìn)程共享同一個端口

當(dāng)使用Tornado作為服務(wù)端時的工作流程是什么樣的呢秫筏?

  1. Tornado在連接建立完畢后會將連接封裝成為一個IOStream對象,這個對象可以異步的從連接中讀寫數(shù)據(jù)需了。
  2. Tornado中實現(xiàn)了HTTP1ServerConnectionHTTP1Connection兩個類,它們依賴于底層的IOStream并從Socket中讀寫般甲,并共同何做完成了HTTP 1.X協(xié)議肋乍。
  3. HTTP1Connection實際上主要用來處理HTTP事務(wù),也就是由一條請求以及對應(yīng)該請求的響應(yīng)所組成的事務(wù)敷存。HTTP1Connection實現(xiàn)了HTTP事務(wù)前半部分也就是對報文起始行墓造、首行堪伍、主體進(jìn)行讀取并解析,而HTTP事務(wù)的后半部分則需要配合HTTPMessageDeletegate進(jìn)行處理觅闽。
  4. HTTPMessageDelegate對經(jīng)過HTTP1Connection解析后的報文會進(jìn)行分配帝雇,然后由其他類如RequestHandler來執(zhí)行具體的業(yè)務(wù)邏輯。其他類會生成響應(yīng)并將響應(yīng)發(fā)送到IOStream中蛉拙,此時才表示這條HTTP事務(wù)已經(jīng)完成尸闸。最后開發(fā)人員可以根據(jù)實際情況判斷是否關(guān)閉連接。
  5. HTTP1ServerConnection則會不斷地生成HTTP1Connection實例孕锄,也就是不斷的處理HTTP事務(wù)吮廉,直至連接關(guān)閉為止。

Tornado服務(wù)器工作流程

首先按照socket->bind->listen的順序創(chuàng)建listen socket監(jiān)聽客戶端畸肆,并將每個listen socket的文件描述符fd注冊到IOLoop的單例實例中宦芦,當(dāng)listen socket可讀寫時回調(diào)_handle_events處理客戶端請求,在與客戶端通信的過程中使用IOStream封裝了讀和寫緩沖區(qū)轴脐,實現(xiàn)與客戶端的異步讀寫调卑。

Tornado服務(wù)器工作流程

Tornado服務(wù)器核心模塊

  1. IOLoop

Tornado為了實現(xiàn)高并發(fā)和高性能使用了一個IOLoop來處理Socket的讀寫事件,IOLoop是基于Linux的Epoll大咱,可以高效地響應(yīng)網(wǎng)絡(luò)事件恬涧,這也是Tornado高效的保證。

  1. IOStream

為了在處理請求時實現(xiàn)對Socket的異步讀寫徽级,Tornado實現(xiàn)了IOStream類用來處理Socket的異步讀寫操作气破。

  1. HTTPConnect

HTTPConnect類是用來處理HTTP請求,包括HTTP請求頭餐抢、讀取POST數(shù)據(jù)现使,調(diào)用用戶自定義的處理方法,以及把響應(yīng)數(shù)據(jù)寫給客戶端的Socket旷痕。

tornado.httpserver

HTTPServer繼承于TCPServer碳锈,其__init__初始化函數(shù)中記錄了連接到來時的回調(diào)函數(shù)即application對象的__call__方法被觸發(fā),然后是父類的初始化欺抗。父類TCPServer初始化主要是監(jiān)聽在特定地址的端口售碳,當(dāng)客戶端發(fā)起連接時服務(wù)器接收并調(diào)用handle_stream方法。由于HTTPServer覆蓋了父類TCPServerhandle_stream方法绞呈,并在該方法中生成HTTPConnection對象贸人。HTTPConnection對象被構(gòu)造時就立即開始了HTTP協(xié)議的處理。由于handle_stream被調(diào)用時是新連接的到來佃声,此時緩沖區(qū)中都會有是有數(shù)據(jù)可讀的艺智。

class HTTPServer(
  TCPServer, 
  Configurable, 
  httputil.HTTPServerConnectionDeletegate
):
  • HTTPServerTCPServer的子類,關(guān)于Socket連接部分由TCPServer類實現(xiàn)圾亏。
  • HTTPServer只用于實現(xiàn):處理連接connection和生成請求request

為了向后兼容十拣,HTTPServerHTTPServerConnectionDelegate的子類封拧,有一個將HTTPServerRequest作為參數(shù)的回調(diào)函數(shù)。這個委托delegate一般是tornado.web.Application夭问。

如果客戶端的請求頭中指定了Connection:keep-alive的話泽西,HTTPServer支持活動的連接。而且HTTPServer是默認(rèn)支持活動連接的缰趋。

如果xheaders選擇設(shè)置成了True捧杉,HTTPServer同樣支持x-Real-Ip/X-Forwarded-For以及X-Scheme/X-Forwarded-proto請求頭,這樣對所有的請求都會覆蓋遠(yuǎn)程IP和 URL 協(xié)議埠胖。當(dāng)在運行反向代理或負(fù)載均衡時糠溜,這些頭文件會很有用。

  1. 處理連接handle_stream

HTTPServer默認(rèn)需要重載父類TCPServerhandle_stream方法用來處理連接和生成請求

def handle_stream(self, stream, address):
  # 生成請求上下文
  context = _HTTPRequestContext(stream, addres, self.protocol)
  # 生成請求
  conn = HTTPServerConnection(stream, self.conn_params, context)
  # 加入到連接池直撤,請求結(jié)束時需移除
  self._connections.add(conn)
  # 該連接開啟服務(wù)
  conn.start_service(self)
  1. 生成請求 start_request
# 開始處理請求
def start_request(self, server_conn, request_conn):
  # 交由_ServerRequestAdapter生成Request請求
  return _ServerRequestAdapter(self, server_conn, request_conn)

HTTPServer的構(gòu)造函數(shù)__init__中最重要的是request_callback非竿,也就是說,當(dāng)一個請求過來時應(yīng)該如何處理谋竖。

Tornado的httpserver模塊中具有HTTPServer红柱、HTTPConnectionHTTPRequest三個類蓖乘。

#!/usr/bin/python3
# -*- coding:utf-8 -*-

from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

def handle_request(request):
    message = "method:{0}\nuri:{1}\nversion={2}".format(request.method, request.uri, request.version)
    for k,v in request.headers.items():
        message += k + ":" + v + "\n"
    request.connection.write(message)
    request.connection.finish()

def main():
    server = HTTPServer(handle_request, False)
    server.listen(8000)
    IOLoop.instance().start()

if __name__=="__main__":
    main()
  • HTTPServer是一個簡單的HTTP服務(wù)器锤悄,主要作用是創(chuàng)建監(jiān)聽Socket,設(shè)置監(jiān)聽Socket的讀事件處理器嘉抒,最后調(diào)用HTTPConnection類處理整個連接零聚。
  • HTTPConnection用于讀取請求并解析,調(diào)用HTTPServer設(shè)置的回調(diào)函數(shù)(一般是Application)處理請求些侍,并提供發(fā)送響應(yīng)的接口隶症。

httpserver模塊是Tornado的HTTP服務(wù)器實現(xiàn)

引入tornado.httpserver模塊

from tornado.httpserver import HTTPServer

創(chuàng)建HTTP服務(wù)器實例

urls = [
  (r"/", MainHandler)
]
app = tornado.web.Application(urls, debug=True)
httpServer = tornado.httpserver.HTTPServer(app)

監(jiān)聽端口,將服務(wù)器綁定到指定端口岗宣。

listen()方法只能在單進(jìn)程模式中使用蚂会,若在多進(jìn)程中可使用bind()方法和start()方法相結(jié)合。

單進(jìn)程監(jiān)聽端口

httpServer.listen(8000)

多進(jìn)程綁定并監(jiān)聽端口

# 將服務(wù)器綁定到指定端口
httpServer.bind(port)

# 指定開啟進(jìn)程的數(shù)量耗式,num_processes表示進(jìn)程的數(shù)量胁住,默認(rèn)為1.
# 若num_processes小于0或為None則會自動根據(jù)機(jī)器硬件的CPU核心數(shù)量創(chuàng)建等數(shù)量子進(jìn)程,若num_processes大于0則會創(chuàng)建與num_processes相同數(shù)量的子進(jìn)程刊咳。
httpServer.start(num_processes = 1)

HTTPConnection

HTTPConnection是HTTP協(xié)議在服務(wù)器的真正實現(xiàn)者彪见,對于請求的讀取和解析基本上是依賴HTTPHeaders完成的,對于響應(yīng)方面的處理包括響應(yīng)頭娱挨、響應(yīng)體的中間處理等在是在RequestHandlerflush方法和finish方法中完成的余指。

tornado.web

Tornado的Web框架tornado.web是在web.py文件中實現(xiàn)的,主要包含RequestHandler類和Application類让蕾。

  • RequestHandler類是對HTTP請求處理的封裝
  • Application類是一些列請求的集合浪规,構(gòu)成一個Web應(yīng)用程序。

A collection of request handleres that make up a web application

from tornado.web import RequestHandler, Application

RequestHandler

RequestHandler提供了一個針對HTTP請求處理的基類封裝探孝。主要功能包括:

  • 提供GET/POST/HEAD/DELETE/ PATCH/PUT/OPTIONS等方法的功能接口笋婿,開發(fā)時RequestHandler的子類重寫這些方法以支持不同需求的請求處理。
  • 提供對HTTP請求的處理方法顿颅,包括對headers缸濒、頁面元素、cookie的處理粱腻。
  • 提供對請求響應(yīng)的方法庇配,包括redirect頁面沖重定向、write將數(shù)據(jù)寫入IO緩沖區(qū)绍些、render渲染模板等捞慌。
  • 提供輔助功能,如結(jié)束請求和響應(yīng)柬批、刷新輸出緩沖區(qū)啸澡、對用戶授權(quán)等處理。

RequestHandler封裝了對應(yīng)請求的所有信息和方法氮帐,利用HTTP向服務(wù)器傳參的方式分為

  • 通過GET方法的查詢字符串querystring嗅虏,形式為key1=value1&key2=value2&key3=value3...
  • 通過POST方法的請求體body中發(fā)送的數(shù)據(jù)上沐,比如表單數(shù)據(jù)皮服、jsonxml...
  • 通過GET方法提取URI中的特定部分参咙,比如PATHINFO模式中/blogs/2019/07/09龄广,通過服務(wù)器路由中的正則表達(dá)式進(jìn)行提取。
  • 通過HTTP報文的頭header中增加自定義字段昂勒,如X-XSRFToken=xxxx蜀细。

使用Tornado獲取請求參數(shù)的方式

  1. 獲取GET請求中查詢字符串querystring參數(shù)
  • get_query_argument獲取查詢字符串querystring中的指定參數(shù)
val = get_query_argument(name, default=_ARG_DEFAULT, strip=True)

get_query_argument會從請求查詢字符串中獲取指定參數(shù)名name的值,如果出現(xiàn)多個同名參數(shù)則返回最后一個值戈盈。

參數(shù)列表

參數(shù)1:name表示查詢字符串中指定的參數(shù)名
參數(shù)2:default表示當(dāng)未傳入name參數(shù)時返回的默認(rèn)值奠衔,若default默認(rèn)值未設(shè)置則會拋出tornado.web.MissingArgumentError異常。
參數(shù)3:strip表示是否過濾左右兩邊的空白字符塘娶,默認(rèn)過濾strip=True归斤。

例如

$ vim server.php
#! /usr/bin/python3
# encoding=utf-8

from tornado.options import define, options
from tornado.web import Application, RequestHandler, url
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

define("port", type=int, default=8000, help="run server on the given port")

class IndexHandler(RequestHandler):
    def get(self):
        self.write("hello world")
    def post(self):
        self.write("post")

class UserHandler(RequestHandler):
    def get(self):
        uid = self.get_query_argument("uid", strip=True)
        print("uid = " + str(uid))

        username = self.get_query_argument("username", strip=True)
        print("username = %s" %username.encode("utf-8"))

        index_url = self.reverse_url("index_url")
        print(index_url)

        self.write("success")
        # self.write("uid=%s username=%s index_url=%s" %uid %username.encode("utf-8") %index_url)

urls = [
    url(r"/", IndexHandler, name="index_url"),
    (r"/user", UserHandler)
]

settings = dict(debug=True)

if __name__ == "__main__":
    options.parse_command_line()
    app = Application(urls, settings)
    server = HTTPServer(app)
    server.listen(options.port)
    IOLoop.current().start()

運行服務(wù)器

$ python server.php

客戶端訪問,注意Linux下&取地址符表示進(jìn)程后臺運行刁岸,所以在URL中必須進(jìn)行轉(zhuǎn)義才能獲取到參數(shù)脏里。

$ curl http://127.0.0.1:8000/user?uid=100\&username=中文
  • get_query_arguments獲取查詢字符串querystring中參數(shù)
list = get_query_arguments(name, strip=True)

get_query_arguments用于從請求的查詢字符串中返回指定參數(shù)名name的值,返回的是list列表虹曙,若未找到name參數(shù)則返回空列表[]迫横。

  1. 獲取POST請求體body參數(shù)
  • get_body_argument獲取請求體參數(shù)

對于HTTP請求體body中的數(shù)據(jù)番舆,必須要求格式為字符串且表單編碼格式與URL中的請求字符串格式一致,即key1=value1&key2=value2&key3=value3...的形式矾踱。

HTTP報文頭Header中的Content-Type內(nèi)容類型必須為application/x-www-form-urlencodedmultipart/form-data恨狈。對于請求體數(shù)據(jù)為JSONXML的則無法獲取。

val = get_body_argument(name, default=_ARG_DEFAULT, strip=True)

get_body_argument會從請求體body中獲取指定參數(shù)名name的值呛讲,若出現(xiàn)多個同名參數(shù)則返回最后一個的值禾怠。

  • get_body_arguments 獲取請求體參數(shù)
list = get_body_arguments(name, strip=True)

get_body_arguments表示從請求體中返回指定參數(shù)名name的值,返回list列表贝搁,若未找到name參數(shù)則返回空列表[]吗氏。

  1. 獲取請求參數(shù)
  • get_argument
val = get_argument(name, default=_ARG_DEFAULT, strip=True)

get_argument表示從POST請求體bodyGET查詢字符串querystring中返回指定參數(shù)名name的值,如果出現(xiàn)多個同名參數(shù)則返回最后一個的值雷逆。

  • get_arguments
list = get_arguments(name, strip=True)

get_arguments表示從POST請求體bodyGET查詢字符串querystring中返回指定參數(shù)名name的值弦讽,返回值類型為list列表,若為找到name參數(shù)則返回空列表[]膀哲。

  1. 獲取請求信息

RequestHandler.request對象存儲了請求的相關(guān)信息坦袍,具體屬性包括:

  • RequestHandler.request.method 表示HTTP的請求方式,如GETPOST等太。
  • RequestHandler.request.host 表示HTTP中被請求的主機(jī)名
  • RequestHandler.request.uri 表示HTTP請求的完整資源標(biāo)識捂齐,包括路徑和查詢字符串。
  • RequestHandler.request.path 表示HTTP請求中URL的路徑部分
  • RequestHandler.request.query 表示HTTP請求中URL的查詢字符串部分
  • RequestHandler.request.version 表示HTTP請求所使用的HTTP版本號
  • RequestHandler.request.headers 表示HTTP請求的協(xié)議頭缩抡,是類字典型的對象奠宜,支持關(guān)鍵字索引的方式獲取特定協(xié)議頭字段,如request.headers["Content-Type"]瞻想。
  • RequestHandler.request.body 表示HTTP請求體數(shù)據(jù)
  • RequestHandler.request.remote_ip表示HTTP請求中客戶端的IP地址
  • RequesteHandler.request.files 表示HTTP請求中用戶上傳的文件压真,字典類型。
# form_filename1字典中的鍵名表示的是表單對應(yīng)項的名字
request.files["form_filename1"][0]["body"]

文件上傳

文件上傳時可使用tornado.httputil.HTTPFile接收文件蘑险,HTTPFile是接收到的文件對象滴肿,它包含三個屬性。

  • 屬性1:HTTPFile.filename 表示文件的實際名字
  • 屬性2:HTTPFile.body 表示文件的數(shù)據(jù)實體
  • 屬性3:HTTPFile.content_type 表示文件的類型

HTTPFile中的三個對象屬性可以像字典一樣支持關(guān)鍵字索引佃迄。


輸出數(shù)據(jù)流

write(chunk)

writechunk數(shù)據(jù)寫入到緩沖區(qū)泼差,如果多次使用write方法則會不斷追加響應(yīng)內(nèi)容,最終會將所有寫入到緩沖區(qū)的內(nèi)容一起作為本次請求的響應(yīng)一起輸出呵俏。

如果傳入的chunkdict字典類型堆缘,Tornado會自動將其識別為JSON,并設(shè)置Header報頭的Content-Type字段為application/json普碎。

如果傳入的chunklist列表類型吼肥,考慮到安全問題list列表將不會被轉(zhuǎn)換為JSON格式。

finish(chunk)

finish表示完成響應(yīng)并結(jié)束本次請求,通常情況下請求會在return時自動調(diào)用finish方法缀皱,只有在使用異步裝飾器@asynchronous或其他將._auto_finish設(shè)置為False時才需要手動調(diào)用finish方法斗这。

#! /usr/bin/python3
# encoding=utf-8

from tornado.options import define, options
from tornado.web import Application, RequestHandler, url
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

define("port", type=int, default=8000, help="run server on the given port")

class IndexHandler(RequestHandler):
    def get(self):
        self.write("hello world")
    def post(self):
        self.write("post")

class UserHandler(RequestHandler):
    def get(self):
        uid = self.get_query_argument("uid", strip=True)
        print("uid = " + str(uid))

        username = self.get_query_argument("username", strip=True)
        print("username = %s" %username)

        index_url = self.reverse_url("index_url")
        print(index_url)

        jsonstr= {"uid":uid, "username":username,"url":index_url}
        self.finish(jsonstr)

urls = [
    url(r"/", IndexHandler, name="index_url"),
    (r"/user", UserHandler)
]

settings = dict(debug=True)

if __name__ == "__main__":
    options.parse_command_line()
    app = Application(urls, settings)
    server = HTTPServer(app)
    server.listen(options.port)
    IOLoop.current().start()

flush()

flush方法會將緩沖區(qū)的數(shù)據(jù)寫入到socket中,如果設(shè)置了回調(diào)函數(shù)callback啤斗,會在完成數(shù)據(jù)寫入后回調(diào)涝影。需要在注意的是同一時間只能有一個等待的flush callback,如果上一次的flush callback還沒有執(zhí)行争占,又來了新的flush則上一次的flush callback會被忽略掉。

Application

A collection of request handle that mak up a web application. Instances of this class are callbable and can be passed directly to HTTPServer to server the application.

Application類初始化

Application是Tornado Web框架的核心應(yīng)用類序目,是與服務(wù)器對接的接口臂痕。保存了路由信息,其初始化的第一個 參數(shù)是一個路由映射元組的列表猿涨,可使用listen方法創(chuàng)建一個HTTP服務(wù)器實例并綁定指定的端口握童。

from tornado.web import Application
app = Application(urls, settings)

Application類初始化的第一個參數(shù)是接收一個(regexp, requste_class)形式的列表,參數(shù)指定了針對不同URL請求所采取的處理方法叛赚,包括對靜態(tài)文件請求的處理web.StaticFileHandler澡绩。

Application類中實現(xiàn)了__call__調(diào)用函數(shù),這樣該類就成為了可調(diào)用的對象俺附,并由HTTPServer來進(jìn)行調(diào)用肥卡。__call__函數(shù)會遍歷Application類中的handlers列表,匹配到相應(yīng)的URL后通過handler._execute進(jìn)行相應(yīng)處理事镣。如果沒有匹配到URL則會調(diào)用ErrorHandler步鉴。

參數(shù)1:路由映射表urls

在構(gòu)建路由映射表時使用的是二元元組

urls = [(r"/", IndexHandler),]

對于路由映射表中的路由可傳入多個路由規(guī)則

from tornado.web import Application, RequestHandler,  url

urls = [
  (r"/", IndexHandler),
  (r"/create", CreateHandler, {"pid": 100}),
  url(r"/update", UpdateHandler, {"id":100}, name="update_url")
]

路由中的字典{k:v}會傳入到對應(yīng)的RequestHandlerinitialize()方法中

from tornado.web import RequestHandler, url

class TestHandler(RequestHandler):
  def initialize(self, k):
    self.k = k
  def get(self):
    self.write(self.k)

urls = [
  (r"/", TestHandler, {k:v})
]
app = tornado.web.Application(urls)

路由中的name字段不能再使用元組而應(yīng)使用tornado.web.url來構(gòu)建,name是給路由起一個別名璃哟,可通過調(diào)用RequestHandler.reverse_url(name)來獲取名字對應(yīng)的URL氛琢。

$ vim server.py
#! /usr/bin/python
# encoding=utf-8

from tornado.options import define, options
from tornado.web import Application, RequestHandler, url
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

define("port", type=int, default=8000, help="run server on the given port")

class IndexHandler(RequestHandler):
    def initialize(self, id):
        self.id = id
    def get(self):
        id = self.get_query_argument("id", strip=True)
        # self.write(id)

        index_url = self.reverse_url("index_url")
        self.write("url=%s" %index_url)
    def post(self):
        self.write("post")


urls = [
    url(r"/", IndexHandler, {"id":100}, name="index_url")       
]

settings = dict(debug=True)

if __name__ == "__main__":
    options.parse_command_line()
    app = Application(urls, settings)
    server = HTTPServer(app)
    server.listen(options.port)
    IOLoop.current().start()
$ python server.py

參數(shù)2:應(yīng)用配置參數(shù)settings

Application類傳遞給構(gòu)造器的附加關(guān)鍵字參數(shù)保存在settings字典中,settings被用于自定義Tornado應(yīng)用的多個方面随闪。

settins = dict(debug=True)

debug用于設(shè)置Tornado是否工作在調(diào)試模式阳似,默認(rèn)為False即工作在生產(chǎn)模式,當(dāng)設(shè)置debug=True后Tornado會工作在開發(fā)調(diào)試模式铐伴,在此種調(diào)試模式下Tornado提供了幾種特性:

  1. 自動重啟

Tornado應(yīng)用會監(jiān)控源代碼文件撮奏,當(dāng)有改動保存后會自動重啟程序,減少手工重啟程序的次數(shù)当宴。需要注意的時挽荡,一旦保存的更改有錯誤,自動重啟會導(dǎo)致程序報錯而退出即供,從而需要保存修正錯誤后手動啟動程序定拟。此特性可以通過autoreload=True設(shè)置。

settings = dict(autoreload=True)
  1. 取消緩存編譯的模板
settings = dict(compiled_template_cache=False)
  1. 取消緩存靜態(tài)文件hash
settings = dict(static_hash_cache=False)
  1. 提供追蹤信息

當(dāng)RequestHandler或其子類拋出一個異常而未捕獲后,會生成一個包含追蹤信息的頁面青自。

settings = dict(serve_traceback=True)

設(shè)置應(yīng)用配置

import tornado.web

tornado.web.Application(urls, settings)
from tornado.web import Application, RequestHandler, url

urls = [
  url(r"/", IndexHandler)
]

settings = dict(debug=True)

app = Application(urls, settings)

tornado.ioloop

Tonado的核心IO循環(huán)模塊是tornado.io株依,它封裝了Linux的Epoll和BSD的kqueue,是Tornado高性能的基礎(chǔ)延窜。

  • tornado.ioloop是全局Tornado的IO事件循環(huán)恋腕,是服務(wù)器的引擎核心。
  • tornado.ioloop是核心IO循環(huán)模塊逆瑞,封裝了Linux的epoll和BSD的kqueue荠藤,是Tornado高性能處理的核心。
  • tornado.ioloop.IOLoop.current()返回當(dāng)前線程的IOLoop實例對象
  • tornado.ioloop.IOLoop.current().start() 用于啟動IOLoop實例對象的IO循環(huán)并開啟監(jiān)聽
ioloop工作原理
# 加載Tornado核心IO事件循環(huán)模塊
import tornado.ioloop

# 默認(rèn)Tornado的ioloop實例
tornado.ioloop.IOLoop.current()

tornado.options

from tornado.options import define, options

options表示全局的options對象获高,可用于全局參數(shù)的定義哈肖、存儲、轉(zhuǎn)換念秧,所有定義的選項變量都會作為options對象的屬性淤井。

tornado.options.optins

define方法用來定義options選項變量,定義的變量可以在全局tornado.options.options中獲取使用摊趾。

tornado.options.define(name, default, type, multiple, help)
  • name 表示選項的變量名稱币狠,必須保證全局唯一,否則會報Option xxx already defined in...錯誤砾层。
  • default表示選項變量的默認(rèn)值漩绵,若不傳入則默認(rèn)為None
  • type表示選項變量的類型肛炮,從命令行或配置文件導(dǎo)入?yún)?shù)時Tornado會根據(jù)此類型轉(zhuǎn)換輸入的值渐行,若轉(zhuǎn)換 失敗則 報錯,轉(zhuǎn)換的類型支持str铸董、float祟印、intdatetime粟害、timedate蕴忆。若未設(shè)置則會根據(jù)default的值自動進(jìn)行推斷,若default默認(rèn)值也沒有設(shè)置則不會再進(jìn)行轉(zhuǎn)換悲幅,可以通過利用設(shè)置type類型字段來過濾不正確的輸入套鹅。
  • multiple選項變量的值是否可為多個,布爾類型汰具,默認(rèn)為False卓鹿,如果 multipleTrue則設(shè)置選項變量時值與值之間使用英文逗號分隔,此時選項變量是一個list列表留荔。若默認(rèn)值和輸入均未設(shè)置則為空列表[]吟孙。
  • help選項變量的幫助提示信息,在命令行啟動Tornado時會加入命令行參數(shù)--help,可以查看所有選項變量的信息杰妓。

define用于定義端口用于指定HTTP服務(wù)監(jiān)聽的端口藻治,如果命令行中帶有port同名參數(shù)則會稱為全局tornado.options的屬性,若沒有則使用define定義巷挥。

tornado.options.define('port', default=8000, type=int, help="this is the port >for application")

parse_command_line轉(zhuǎn)換命令行參數(shù)桩卵,并將轉(zhuǎn)換后的值對應(yīng)的設(shè)置到全局options對象相關(guān)屬性上。

tornado.options.parse_command_line()

命令行追加參數(shù)的方式是使用--myoption=myvalue

$ python server.py --port=8000

parse_config_file根據(jù)配置文件配置文件獲取參數(shù)

tornado.options.parse_config_file(filepath)

從配置文件導(dǎo)入option時配置文件中的格式為

tornado.options.parse_config_file("./config")

使用parse_config_file()函數(shù)時配置文件的編寫格式需按照Python的語法要求倍宾,其優(yōu)勢是可以直接將配置文件的參數(shù)文件轉(zhuǎn)換設(shè)置到全局對象tornado.options.options上雏节。

$ vim config.py
port = 8000
debug =  True
ip = ["192.168.56.100", "192.168.56.101", "192.168.56.102"]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市高职,隨后出現(xiàn)的幾起案子钩乍,更是在濱河造成了極大的恐慌,老刑警劉巖初厚,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件依疼,死亡現(xiàn)場離奇詭異斩披,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)乡革,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門牵啦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亚情,“玉大人,你說我怎么就攤上這事哈雏±慵” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵裳瘪,是天一觀的道長土浸。 經(jīng)常有香客問我,道長彭羹,這世上最難降的妖魔是什么黄伊? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮派殷,結(jié)果婚禮上还最,老公的妹妹穿的比我還像新娘。我一直安慰自己毡惜,他們只是感情好拓轻,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著经伙,像睡著了一般扶叉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天辜梳,我揣著相機(jī)與錄音粱甫,去河邊找鬼。 笑死作瞄,一個胖子當(dāng)著我的面吹牛茶宵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宗挥,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼乌庶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了契耿?” 一聲冷哼從身側(cè)響起瞒大,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搪桂,沒想到半個月后透敌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡踢械,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年酗电,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片内列。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡撵术,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出话瞧,到底是詐尸還是另有隱情嫩与,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布交排,位于F島的核電站划滋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏埃篓。R本人自食惡果不足惜古毛,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望都许。 院中可真熱鬧稻薇,春花似錦、人聲如沸胶征。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睛低。三九已至案狠,卻和暖如春服傍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骂铁。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工吹零, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拉庵。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓灿椅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钞支。 傳聞我的和親對象是個殘疾皇子茫蛹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 參考資料 http://c.biancheng.net/view/2125.html TCP TCP協(xié)議是一個面向...
    JunChow520閱讀 1,453評論 0 5
  • 1.tornado是一個異步的http框架 2.常用模塊 importtornado.httpserver imp...
    冬gua閱讀 465評論 0 0
  • 網(wǎng)絡(luò) 理論模型,分為七層物理層數(shù)據(jù)鏈路層傳輸層會話層表示層應(yīng)用層 實際應(yīng)用,分為四層鏈路層網(wǎng)絡(luò)層傳輸層應(yīng)用層 IP...
    FlyingLittlePG閱讀 778評論 0 0
  • 1.tornado是一個異步的http框架 2.常用模塊 importtornado.httpserverimpo...
    a荷包蛋閱讀 836評論 0 0
  • 參考教程 為什么用Tornado? 異步編程原理 服務(wù)器同時要對許多客戶端提供服務(wù),他的性能至關(guān)重要烁挟。而服務(wù)器端的...
    內(nèi)心強(qiáng)大的Jim閱讀 6,220評論 1 8