一、請求
http://www.baidu.com:8080/path1/path2/file.html?a=1&b=2#abc
客戶端請求流程:查詢本地hosts文件,如果沒有主機名www.baidu.com對應的ip,從cdn服務器查義主機名對應的ip,找到,則訪問到服務器。再根據路徑和文件名,訪問到/path1/path2/file.html文件;查詢參數為:a=1&b=2,錨為abc。瀏覽器接收到此文件以后,顯示在瀏覽上。
tornado.web.RequestHandler的作用:
1.利用HTTP協議向服務器傳遞參數。
其傳遞參數的方式有:
- 提取uri的特定部分
-
- 示例:網址如http://127.0.0.1/liuyf/aaa/bb/c,http://127.0.0.1/liuyf為固定部分,路徑/aaa/bb/c為可變部分。要實現此功能,需要提取uri部分作為參數。
# 路由1: (r'/liuyf/(\w+)/(\w+)/(\w+)', index.LiuyfHandler) # 路由2: (r'/liuyf/(?P<p1>\w+)/(?P<p3>\w+)/(?P<p2>\w+)', index.LiuyfHandler), # 兩種路由方式都可匹配下面的視圖函數 # handler: class LiuyfHandler(RequestHandler):
# 接收uri參數, 在get、post等請求函數的參數中接收。 def get(self, h1, h2, h3, *args, **kwargs): print h1,h2,h3 self.write('這是liuyf')
- 示例:網址如http://127.0.0.1/liuyf/aaa/bb/c,http://127.0.0.1/liuyf為固定部分,路徑/aaa/bb/c為可變部分。要實現此功能,需要提取uri部分作為參數。
-
- 查詢字符串,get方式傳遞參數
- 網址:http://127.0.0.1/zhangmy?a=1&b=2&c=3
# 路由一樣: (r'/zhangmy', index.ZhangmyHandler) #Handler: class ZhangmyHandler(RequestHandler): def get(self,*args, **kwargs): # get_argument方法的原型:self.get_argument(name, default=ARG_DEFAULT, strip=True) # name:從get請求參數字符串中返回指定參數的值。如果出現多個同名參數,這個方法會返回最后一個值。 # default:設置未傳的name參數的默認值。如果name未傳,default也未設置,會拋出tornado.web.MissingArgument異常 # strip:表示是否過濾掉參數值的左右空白字符,默認為True。通常情況下不需要空格字符,但是在搜索等情況下需要空格。 a = self.get_query_argument("a") b = self.get_query_argument("b") c = self.get_query_argument("c") print "*"+a+"*","*"+b+"*","*"+c+"*" self.write('這是zhangmy')
- 網址:http://127.0.0.1/zhangmy?a=1&a=2&a=3
class ZhangmyHandler(RequestHandler): def get(self,*args, **kwargs): # 如果有多個同名參數,使用get_argument會獲取到最后一個值。 # 如果想要多個值都獲取,要使用get_arguments,返回列表。 # get_arguments方法的原型:self.get_argument(name, strip=True) a = self.get_query_arguments("a") print "*"+a+"*" self.write('這是zhangmy')
- 網址:http://127.0.0.1/zhangmy?a=1&b=2&c=3
- 請求體攜帶數據,post方式傳遞參數
- 示例:
- 1.創建路由
(r'/mylike', index.MyLikeHandler)
- 2.創建一個提交數據的頁面
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>select my like</title> </head> <body> <form action="/mylike" method="post"> 姓名:<input type="text" name="name"/> <hr/> 密碼:<input type="password" name="passwd"/> <hr/> 愛好: <input type="checkbox" value="power" name="mylike">權利 <input type="checkbox" value="money" name="mylike">金錢 <input type="checkbox" value="book" name="mylike">書 <input type="submit" value="登陸"> </form> </body> </html>
在post請求,提交數據之前:自動使用get方法返回上面的頁面。頁面渲染使用self.render(模板)。
#Handler: class MyLikeHandler(RequestHandler): def get(self, *args, **kwargs): # 返回模板 self.render("postfile.html")
- 處理post請求參數:
class MyLikeHandler(RequestHandler): def get(self, *args, **kwargs): # 返回模板 self.render("postfile.html") def post(self, *args, **kwargs): # 接收post參數 # 1.使用get_body_argument,原型為: # self.get_body_argument(name, default=ARG_ARGUMENT, strip=True) # 2.如果存在多個同名參數,想取所有值,使用get_body_arguments,返回列表,原型為: # self.get_body_arguments(name, strip=True) name = self.get_body_argument("name") passwd = self.get_body_argument("passwd") likeList = self.get_body_arguments("like") print name, passwd, likeList self.write("提交成功!")
- 既可以獲取get請求,也可以獲取post請求的參數
- get_argument(name, default=ARG_DEFAULT, strip=True)和get_arguments(name, default=ARG_DEFAULT, strip=True)既可以獲取get請求參數,也可以獲取post請求參數。以上面的示例為例:
class MyLikeHandler(RequestHandler): def get(self, *args, **kwargs): # 返回模板 self.render("postfile.html") def post(self, *args, **kwargs): # 同時接收get和post參數 # 1.使用get_argument,原型為: # self.getargument(name, default=ARG_ARGUMENT, strip=True) # 2.如果存在多個同名參數,想取所有值,使用get_arguments,返回列表,原型為: # self.get_arguments(name, strip=True) a_list = self.get_arguments("a") b = self.get_argument("b") c = self.get_argument("c") name = self.get_argument("name") passwd = self.get_argument("passwd") likeList = self.get_arguments("like") print name, passwd, likeList self.write("提交成功!")
以上的缺點,不能區分是get還是post的參數;優點,可以同時獲取get和post的參數。
- get_argument(name, default=ARG_DEFAULT, strip=True)和get_arguments(name, default=ARG_DEFAULT, strip=True)既可以獲取get請求參數,也可以獲取post請求參數。以上面的示例為例:
- 在http報文的頭中增加自定義的字段:略
2.request對象--請求對象
作用:存儲了關於請求的相關信息。
它擁有的屬性:
method:是HTTP請求的方式
host:被請求的服務器名稱
uri:請求的完整資源地址,包括路徑和get查詢參數部分:/path1/path2/file.html?a=1&b=2
path:請求的路徑部分:/path1/path2
query:請求參數部分:a=1&b=2
version:使用的HTTP版本
headers:請求的協議頭,是一個字典類型
body:請求體的數據,post請求才有請求體。
remote_ip:客戶端的ip
files:用戶上傳的文件,是一字典類型
#路由 ('r/haha', index.HahaHandler), # 視圖處理Handler class HahaHandler(RequestHandler): def get(self, *args, **kwargs): print self.request.method print self.request.uri print self.request.path print self.request.query print self.request.version print self.request.headers print self.request.body print self.request.remote_ip print self.request.files
3.tornador.httputil.HTTPFile對象:是接收到的文件對象,每上傳一個文件,生成一個文件對象。
當上傳文件的時侯,才有此文件對象。
此文件對象的屬性:
- filename:文件的實際名字
- body屬性:文件的數據實體,即數據內容
- content_type:文件的類型
上傳文件的過程:
1).get展示頁面;
2).post請求上傳文件;
3).在服務器上創建一個同名同類型文件;
4).將tornador.httputil.HTTPFile對象的body內容寫入服務器上的同名文件。
創建一個上傳文件的頁面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>上傳文件</title> </head> <body> <form method="post" action="/linqx" enctype="multipart/form-data"> <input type="file" name="file"> <input type="file" name="file"> <input type="file" name="img"> <input type="submit" value="上傳"> </form> </body> </html>
創建上傳文件的路由:
(r'/linqx', index.UpfileHandler)
視圖函數Handler:
class UpFileHandler(RequestHandler): def get(self, *args, **kwargs): # 展示上傳文件的頁面 self.render("upfile.html") def post(self, *args, **kwargs): filesDict = self.request.files # files 就是tornador.httputil.HTTPFile對象 print filesDict
# 最外層字典的key是html文件中的input標簽的name的值,可以為任意值。 """{"file": [{ "filename": "a.txt", "body": b"this is content.........", "content_type": "text/plain" }, { "filename": "b.txt", "body": b"this is b's content.........", "content_type": "text/plain" },], "img":[{ "filename": "圖片.png", "body": b"weradfs342r.........", "content_type":"image/png" },], }""" # 將文件寫入到服務器上 import os import config for inputname in filesDict: fileArr = filesDict[inputname] for fileObj in fileArr: filePath = os.path.join(config.BASE_DIRS, 'upfile/' + inputname) with open(filePath, 'wb') as f: f.write(fileObj.body) self.write("上傳文件成功!")
二、響應
響應中的方法,也在RequestHandler對象中。
帶有self的方法,是RequestHandler已實現的方法,是實例方法,直接使用。
未帶self的方法,是RequestHandler未實現的方法,需要我們重寫的方法,如果有需要。
1.self.wirte:
原型self.wirte(chunk)。
作用:將chunk數據寫到輸出緩沖區,對應的響應socket將緩沖區的數據返回給瀏覽器。
緩沖區刷新條件:1.程序結束;2.手動刷新;3.緩沖區滿了;遇到/n
2.self.finish(chunck=None),默認chunk為None
作用:刷新緩沖區,並關閉當次請求通道。
因此,self.finish()后面的self.write('asdf')內容雖然會寫入到緩沖區,但是請求通道已關閉,不會返回給用戶,沒有意義了。
3.利用self.wirte('.....')寫json,返回給ajax,是動態的刷新給的。如,瀏覽器向下拖動的數據,是后來加載的。
返回json數據給瀏覽器方式一,手動轉換數據為json字符串:
# json ('r/json1', index.Json1Handler)
class Json1Handler(RequestHandler): def get(self, *args, **kwargs): per = { "name": "haha", "age": 23, "heigh": 175, "weight":70 } import json jsonStr = json.dumps(per) self.write(jsonStr)
返回json數據給瀏覽器方式二:
self.writer(),默認會轉換為json字符串返回。
class Json1Handler(RequestHandler): def get(self, *args, **kwargs): per = { "name": "haha", "age": 23, "heigh": 175, "weight":70 } self.write(per)
區別:方式一手動序列化時,返回的headers中的content_type為application/html;
方式二使用write自動序列化時,返回的headers中的content_type為application/json;
因此,希望返回json數據時,使用第二種方式self.write自動序列化。
4.self.set_header設置響應頭(Response Headers)
作用:如,手動設置一個名為username,值為xxxx的響應頭字段
參數:name,字段名稱;value字段值
示例:將3中的第一種方式的content_type改為application/json;charset=UTF-8
class Json1Handler(RequestHandler): def get(self, *args, **kwargs): per = { "name": "haha", "age": 23, "heigh": 175, "weight":70 } import json jsonStr = json.dumps(per) self.set_header("Content-Type", "application/json;charset=UTF-8") self.set_header("heihei", "heihei's value") self.write(jsonStr)
5.set_default_headers()設置響應頭
通常,要修改響應頭,重寫此方法,而不是在隨處任寫;在此方法中,調用self.set_header()去設置響應頭。
作用:此方法,在進入HTTP響應處理之前調用,可以重寫該 方法來預先設置響應頭。
注意:如果在多處,寫了self.set_header()方法;后調用的方法會覆蓋先調用的方法。
class HeaderHandler(RequestHandler): def set_default_headers(self): self.set_header("Content-Type", "application/json;charset=UTF-8") self.set_header("heihei", "1") def get(self, *args, **kwargs): self.set_header("heihei", "2") self.write("test set_header")
6.self.set_status(status_code, reason=None)設置響應狀態碼
作用:為響應設置狀態碼
參數:status_code,狀態碼值,為int整型;reason,描述狀態碼的詞組,string類型。
如果reason的值為None,則狀態碼必須為系統定義的值。
1).使用已有的(系統定義的)狀態碼:
class StatusHandler(RequestHandler): def get(self, *args, **kwargs): self.set_status(404) self.write('sdaf.........')
2).使用自定義的狀態碼:
此時,reason不能為None,否則將拋出異常。
class StatusHandler(RequestHandler): def get(self, *args, **kwargs): self.set_status(999, "who?") self.write('sdaf.........')
7.self.redirect(url)重定向
作用:重定向到url網址
# 重定向 (r'/redirect', index.RedirectHandler), class RedirectHandler(RequestHandler): def get(self, *args, **kwargs): self.redirect("/")
8.self.send_error(status_code=500, **kwargs): 用以拋出錯誤狀態碼
作用:拋出HTTP錯誤狀態碼,默認為500。拋出錯誤后tornado會調用writer_error()方法進行處理,並返回給瀏覽器錯誤界面。
9.write_error(status_code, **kwargs):用來處理拋出的錯誤
作用:用來處理send_error拋出的錯誤信息,並並返回給瀏覽器錯誤界面。同上面的方法一起使用。
# 路由 # 錯誤處理 #iserror?flag=2,假設flag為0為錯誤 (r'/iserror', index.ErrorHandler) # Handler class ErrorHandler(RequestHandler): def write_error(self, status_code, **kwargs): if status_code == 500: self.set_status(500) # 或者返回自定義的頁面 self.write("服務器內部錯誤") elif status_code == 404: self.set_status(404) self.write("資源不存在") else: self.set_status(999, "what") self.write("未知錯誤") def get(self, *args, **kwargs): flag = self.get_query_argument("flag") if flag == '0': self.send_error(500) self.write("your are right!")
三、請求與響應流程
紅色邊框:tornado-IOLoop
黑色邊框:linux服務器范圍:
- 當tornado httpserver服務器創建成功以后,linux服務器為http服務器創建socket,創建后將socket交給linux服務器管理,負責監聽客戶端的請求。
- tornado-IOLoop死循環,與linux-epoll交互,詢問,是否有新的請求到來。
- 當有客戶端請求到來時,linux服務器為每個客戶端創建一個獨立的socket,如socket1,socket2。
- 並交由tornado處理
藍色箭頭:請求流程
每個客戶端請求,使用獨立的套接字,如socket1。沿着藍色箭頭,經過socket1,經路由Application處理,交給視圖處理函數Handler處理。同步。
黃色箭頭:響應流程
Handler收到請求並處理后,沿着黃色箭頭,異步返回給客戶端響應套接字socket1緩沖區,socket1刷新緩沖區數據返回給瀏覽器。