3.3 輸出
1. write(chunk)
將chunk數據寫到輸出緩衝區。如我們在之前的示例代碼中寫的:
class IndexHandler(RequestHandler):
def get(self):
self.write("hello itcast!")
想一想,可不可以在同一個處理方法中多次使用write方法?
下面的代碼會出現什麼效果?
class IndexHandler(RequestHandler):
def get(self):
self.write("hello itcast 1!")
self.write("hello itcast 2!")
self.write("hello itcast 3!")
write方法是寫到緩衝區的,我們可以像寫文件一樣多次使用write方法不斷追加響應內容,最終所有寫到緩衝區的內容一起作爲本次請求的響應輸出。
想一想,如何利用write方法寫json數據?
import json
class IndexHandler(RequestHandler):
def get(self):
stu = {
"name":"zhangsan",
"age":24,
"gender":1,
}
stu_json = json.dumps(stu)
self.write(stu_json)
實際上,我們可以不用自己手動去做json序列化,當write方法檢測到我們傳入的chunk參數是字典類型後,會自動幫我們轉換爲json字符串。
class IndexHandler(RequestHandler):
def get(self):
stu = {
"name":"zhangsan",
"age":24,
"gender":1,
}
self.write(stu)
兩種方式有什麼差異?
對比一下兩種方式的響應頭header中Content-Type
字段,自己手動序列化時爲Content-Type:text/html; charset=UTF-8
,而採用write方法時爲Content-Type:application/json; charset=UTF-8
。
write方法除了幫我們將字典轉換爲json字符串之外,還幫我們將Content-Type
設置爲application/json; charset=UTF-8
。
2. set_header(name, value)
利用set_header(name, value)方法,可以手動設置一個名爲name、值爲value的響應頭header字段。
用set_header方法來完成上面write所做的工作。
import json
class IndexHandler(RequestHandler):
def get(self):
stu = {
"name":"zhangsan",
"age":24,
"gender":1,
}
stu_json = json.dumps(stu)
self.write(stu_json)
self.set_header("Content-Type", "application/json; charset=UTF-8")
3. set_default_headers()
該方法會在進入HTTP處理方法前先被調用,可以重寫此方法來預先設置默認的headers。注意:在HTTP處理方法中使用set_header()方法會覆蓋掉在set_default_headers()方法中設置的同名header。
class IndexHandler(RequestHandler):
def set_default_headers(self):
print "執行了set_default_headers()"
# 設置get與post方式的默認響應體格式爲json
self.set_header("Content-Type", "application/json; charset=UTF-8")
# 設置一個名爲itcast、值爲python的header
self.set_header("itcast", "python")
def get(self):
print "執行了get()"
stu = {
"name":"zhangsan",
"age":24,
"gender":1,
}
stu_json = json.dumps(stu)
self.write(stu_json)
self.set_header("itcast", "i love python") # 注意此處重寫了header中的itcast字段
def post(self):
print "執行了post()"
stu = {
"name":"zhangsan",
"age":24,
"gender":1,
}
stu_json = json.dumps(stu)
self.write(stu_json)
終端中打印出的執行順序:
get請求方式的響應header:
post請求方式的響應header:
4. set_status(status_code, reason=None)
爲響應設置狀態碼。
參數說明:
- status_code int類型,狀態碼,若reason爲None,則狀態碼必須爲下表中的。
- reason string類型,描述狀態碼的詞組,若爲None,則會被自動填充爲下表中的內容。
Code | Enum Name | Details |
---|---|---|
100 | CONTINUE | HTTP/1.1 RFC 7231, Section 6.2.1 |
101 | SWITCHING_PROTOCOLS | HTTP/1.1 RFC 7231, Section 6.2.2 |
102 | PROCESSING | WebDAV RFC 2518, Section 10.1 |
200 | OK | HTTP/1.1 RFC 7231, Section 6.3.1 |
201 | CREATED | HTTP/1.1 RFC 7231, Section 6.3.2 |
202 | ACCEPTED | HTTP/1.1 RFC 7231, Section 6.3.3 |
203 | NON_AUTHORITATIVE_INFORMATION | HTTP/1.1 RFC 7231, Section 6.3.4 |
204 | NO_CONTENT | HTTP/1.1 RFC 7231, Section 6.3.5 |
205 | RESET_CONTENT | HTTP/1.1 RFC 7231, Section 6.3.6 |
206 | PARTIAL_CONTENT | HTTP/1.1 RFC 7233, Section 4.1 |
207 | MULTI_STATUS | WebDAV RFC 4918, Section 11.1 |
208 | ALREADY_REPORTED | WebDAV Binding Extensions RFC 5842, Section 7.1 (Experimental) |
226 | IM_USED | Delta Encoding in HTTP RFC 3229, Section 10.4.1 |
300 | MULTIPLE_CHOICES | HTTP/1.1 RFC 7231, Section 6.4.1 |
301 | MOVED_PERMANENTLY | HTTP/1.1 RFC 7231, Section 6.4.2 |
302 | FOUND | HTTP/1.1 RFC 7231, Section 6.4.3 |
303 | SEE_OTHER | HTTP/1.1 RFC 7231, Section 6.4.4 |
304 | NOT_MODIFIED | HTTP/1.1 RFC 7232, Section 4.1 |
305 | USE_PROXY | HTTP/1.1 RFC 7231, Section 6.4.5 |
307 | TEMPORARY_REDIRECT | HTTP/1.1 RFC 7231, Section 6.4.7 |
308 | PERMANENT_REDIRECT | Permanent Redirect RFC 7238, Section 3 (Experimental) |
400 | BAD_REQUEST | HTTP/1.1 RFC 7231, Section 6.5.1 |
401 | UNAUTHORIZED | HTTP/1.1 Authentication RFC 7235, Section 3.1 |
402 | PAYMENT_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.2 |
403 | FORBIDDEN | HTTP/1.1 RFC 7231, Section 6.5.3 |
404 | NOT_FOUND | HTTP/1.1 RFC 7231, Section 6.5.4 |
405 | METHOD_NOT_ALLOWED | HTTP/1.1 RFC 7231, Section 6.5.5 |
406 | NOT_ACCEPTABLE | HTTP/1.1 RFC 7231, Section 6.5.6 |
407 | PROXY_AUTHENTICATION_REQUIRED | HTTP/1.1 Authentication RFC 7235, Section 3.2 |
408 | REQUEST_TIMEOUT | HTTP/1.1 RFC 7231, Section 6.5.7 |
409 | CONFLICT | HTTP/1.1 RFC 7231, Section 6.5.8 |
410 | GONE | HTTP/1.1 RFC 7231, Section 6.5.9 |
411 | LENGTH_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.10 |
412 | PRECONDITION_FAILED | HTTP/1.1 RFC 7232, Section 4.2 |
413 | REQUEST_ENTITY_TOO_LARGE | HTTP/1.1 RFC 7231, Section 6.5.11 |
414 | REQUEST_URI_TOO_LONG | HTTP/1.1 RFC 7231, Section 6.5.12 |
415 | UNSUPPORTED_MEDIA_TYPE | HTTP/1.1 RFC 7231, Section 6.5.13 |
416 | REQUEST_RANGE_NOT_SATISFIABLE | HTTP/1.1 Range Requests RFC 7233, Section 4.4 |
417 | EXPECTATION_FAILED | HTTP/1.1 RFC 7231, Section 6.5.14 |
422 | UNPROCESSABLE_ENTITY | WebDAV RFC 4918, Section 11.2 |
423 | LOCKED | WebDAV RFC 4918, Section 11.3 |
424 | FAILED_DEPENDENCY | WebDAV RFC 4918, Section 11.4 |
426 | UPGRADE_REQUIRED | HTTP/1.1 RFC 7231, Section 6.5.15 |
428 | PRECONDITION_REQUIRED | Additional HTTP Status Codes RFC 6585 |
429 | TOO_MANY_REQUESTS | Additional HTTP Status Codes RFC 6585 |
431 | REQUEST_HEADER_FIELDS_TOO_LARGE Additional | HTTP Status Codes RFC 6585 |
500 | INTERNAL_SERVER_ERROR | HTTP/1.1 RFC 7231, Section 6.6.1 |
501 | NOT_IMPLEMENTED | HTTP/1.1 RFC 7231, Section 6.6.2 |
502 | BAD_GATEWAY | HTTP/1.1 RFC 7231, Section 6.6.3 |
503 | SERVICE_UNAVAILABLE | HTTP/1.1 RFC 7231, Section 6.6.4 |
504 | GATEWAY_TIMEOUT | HTTP/1.1 RFC 7231, Section 6.6.5 |
505 | HTTP_VERSION_NOT_SUPPORTED | HTTP/1.1 RFC 7231, Section 6.6.6 |
506 | VARIANT_ALSO_NEGOTIATES | Transparent Content Negotiation in HTTP RFC 2295, Section 8.1 (Experimental) |
507 | INSUFFICIENT_STORAGE | WebDAV RFC 4918, Section 11.5 |
508 | LOOP_DETECTED | WebDAV Binding Extensions RFC 5842, Section 7.2 (Experimental) |
510 | NOT_EXTENDED | An HTTP Extension Framework RFC 2774, Section 7 (Experimental) |
511 | NETWORK_AUTHENTICATION_REQUIRED | Additional HTTP Status Codes RFC 6585, Section 6 |
class Err404Handler(RequestHandler):
"""對應/err/404"""
def get(self):
self.write("hello itcast")
self.set_status(404) # 標準狀態碼,不用設置reason
class Err210Handler(RequestHandler):
"""對應/err/210"""
def get(self):
self.write("hello itcast")
self.set_status(210, "itcast error") # 非標準狀態碼,設置了reason
class Err211Handler(RequestHandler):
"""對應/err/211"""
def get(self):
self.write("hello itcast")
self.set_status(211) # 非標準狀態碼,未設置reason,錯誤
5. redirect(url)
告知瀏覽器跳轉到url。
class IndexHandler(RequestHandler):
"""對應/"""
def get(self):
self.write("主頁")
class LoginHandler(RequestHandler):
"""對應/login"""
def get(self):
self.write('<form method="post"><input type="submit" value="登陸"></form>')
def post(self):
self.redirect("/")
6. send_error(status_code=500, **kwargs)
拋出HTTP錯誤狀態碼status_code,默認爲500,kwargs爲可變命名參數。使用send_error拋出錯誤後tornado會調用write_error()方法進行處理,並返回給瀏覽器處理後的錯誤頁面。
class IndexHandler(RequestHandler):
def get(self):
self.write("主頁")
self.send_error(404, content="出現404錯誤")
注意:默認的write\_error()
方法不會處理send\_error
拋出的kwargs參數,即上面的代碼中content="出現404錯誤"
是沒有意義的。
嘗試下面的代碼會出現什麼問題?
class IndexHandler(RequestHandler):
def get(self):
self.write("主頁")
self.send_error(404, content="出現404錯誤")
self.write("結束") # 我們在send_error再次向輸出緩衝區寫內容
注意:使用send_error()方法後就不要再向輸出緩衝區寫內容了!
7. write_error(status_code, **kwargs)
用來處理send_error拋出的錯誤信息並返回給瀏覽器錯誤信息頁面。可以重寫此方法來定製自己的錯誤顯示頁面。
class IndexHandler(RequestHandler):
def get(self):
err_code = self.get_argument("code", None) # 注意返回的是unicode字符串,下同
err_title = self.get_argument("title", "")
err_content = self.get_argument("content", "")
if err_code:
self.send_error(err_code, title=err_title, content=err_content)
else:
self.write("主頁")
def write_error(self, status_code, **kwargs):
self.write(u"<h1>出錯了,程序員GG正在趕過來!</h1>")
self.write(u"<p>錯誤名:%s</p>" % kwargs["title"])
self.write(u"<p>錯誤詳情:%s</p>" % kwargs["content"])
3.4 接口與調用順序
下面的接口方法是由tornado框架進行調用的,我們可以選擇性的重寫這些方法。
1. initialize()
對應每個請求的處理類Handler在構造一個實例後首先執行initialize()方法。在講輸入時提到,路由映射中的第三個字典型參數會作爲該方法的命名參數傳遞,如:
class ProfileHandler(RequestHandler):
def initialize(self, database):
self.database = database
def get(self):
...
app = Application([
(r'/user/(.*)', ProfileHandler, dict(database=database)),
])
此方法通常用來初始化參數(對象屬性),很少使用。
2. prepare()
預處理,即在執行對應請求方式的HTTP方法(如get、post等)前先執行,注意:不論以何種HTTP方式請求,都會執行prepare()方法。
以預處理請求體中的json數據爲例:
import json
class IndexHandler(RequestHandler):
def prepare(self):
if self.request.headers.get("Content-Type").startswith("application/json"):
self.json_dict = json.loads(self.request.body)
else:
self.json_dict = None
def post(self):
if self.json_dict:
for key, value in self.json_dict.items():
self.write("<h3>%s</h3><p>%s</p>" % (key, value))
def put(self):
if self.json_dict:
for key, value in self.json_dict.items():
self.write("<h3>%s</h3><p>%s</p>" % (key, value))
用post方式發送json數據時:
用put方式發送json數據時:
3. HTTP方法
方法 | 描述 |
---|---|
get | 請求指定的頁面信息,並返回實體主體。 |
head | 類似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭 |
post | 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的修改。 |
delete | 請求服務器刪除指定的內容。 |
patch | 請求修改局部數據。 |
put | 從客戶端向服務器傳送的數據取代指定的文檔的內容。 |
options | 返回給定URL支持的所有HTTP方法。 |
4. on_finish()
在請求處理結束後調用,即在調用HTTP方法後調用。通常該方法用來進行資源清理釋放或處理日誌等。注意:請儘量不要在此方法中進行響應輸出。
5. set_default_headers()
6. write_error()
7. 調用順序
我們通過一段程序來看上面這些接口的調用順序。
class IndexHandler(RequestHandler):
def initialize(self):
print "調用了initialize()"
def prepare(self):
print "調用了prepare()"
def set_default_headers(self):
print "調用了set_default_headers()"
def write_error(self, status_code, **kwargs):
print "調用了write_error()"
def get(self):
print "調用了get()"
def post(self):
print "調用了post()"
self.send_error(200) # 注意此出拋出了錯誤
def on_finish(self):
print "調用了on_finish()"
在正常情況未拋出錯誤時,調用順序爲:
- set_defautl_headers()
- initialize()
- prepare()
- HTTP方法
- on_finish()
在有錯誤拋出時,調用順序爲:
- set_default_headers()
- initialize()
- prepare()
- HTTP方法
- set_default_headers()
- write_error()
- on_finish()
3.5 練習
-
將Application的設置參數(目前只學習了debug)抽離爲一個字典類型變量settings,並在構造Application對象時使用settings。
-
熟練使用RequestHandler的各種輸入輸出方法。
-
嘗試抽象出BaseHandler基類,繼承自RequestHandler,並在此基類中實現prepare(解析json數據)、write_error兩個接口。
4 模板
知識點
- 靜態文件配置
- static_path
- StaticFileHandler
- 模板使用
- 變量與表達式
- 控制語句
- 函數
- 塊
4.1 靜態文件
現在有一個預先寫好的靜態頁面文件 (下載靜態文件資源), 我們來看下如何用tornado提供靜態文件。
static_path
我們可以通過向web.Application類的構造函數傳遞一個名爲static_path的參數來告訴Tornado從文件系統的一個特定位置提供靜態文件,如:
app = tornado.web.Application(
[(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "statics"),
)
在這裏,我們設置了一個當前應用目錄下名爲statics的子目錄作爲static_path的參數。現在應用將以讀取statics目錄下的filename.ext來響應諸如/static/filename.ext的請求,並在響應的主體中返回。
對於靜態文件目錄的命名,爲了便於部署,建議使用static
對於我們提供的靜態文件資源,可以通過http://127.0.0.1/static/html/index.html
來訪問。而且在index.html中引用的靜態資源文件,我們給定的路徑也符合/static/...的格式,故頁面可以正常瀏覽。
<link href="/static/plugins/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/plugins/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/css/reset.css" rel="stylesheet">
<link href="/static/css/main.css" rel="stylesheet">
<link href="/static/css/index.css" rel="stylesheet">
<script src="/static/js/jquery.min.js"></script>
<script src="/static/plugins/bootstrap/js/bootstrap.min.js"></script>
<script src="/static/js/index.js"></script>
StaticFileHandler
我們再看剛剛訪問頁面時使用的路徑http://127.0.0.1/static/html/index.html
,這中url顯然對用戶是不友好的,訪問很不方便。我們可以通過tornado.web.StaticFileHandler來自由映射靜態文件與其訪問路徑url。
tornado.web.StaticFileHandler是tornado預置的用來提供靜態資源文件的handler。
import os
current_path = os.path.dirname(__file__)
app = tornado.web.Application(
[
(r'^/()$', StaticFileHandler, {"path":os.path.join(current_path, "statics/html"), "default_filename":"index.html"}),
(r'^/view/(.*)$', StaticFileHandler, {"path":os.path.join(current_path, "statics/html")}),
],
static_path=os.path.join(current_path, "statics"),
)
- path 用來指明提供靜態文件的根路徑,並在此目錄中尋找在路由中用正則表達式提取的文件名。
- default_filename 用來指定訪問路由中未指明文件名時,默認提供的文件。
現在,對於靜態文件statics/html/index.html,可以通過三種方式進行訪問:
4.2 使用模板
1. 路徑與渲染
使用模板,需要仿照靜態文件路徑設置一樣,向web.Application類的構造函數傳遞一個名爲template_path的參數來告訴Tornado從文件系統的一個特定位置提供模板文件,如:
app = tornado.web.Application(
[(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
在這裏,我們設置了一個當前應用目錄下名爲templates的子目錄作爲template_path的參數。在handler中使用的模板將在此目錄中尋找。
現在我們將靜態文件目錄statics/html中的index.html複製一份到templates目錄中,此時文件目錄結構爲:
.
├── statics
│ ├── css
│ │ ├── index.css
│ │ ├── main.css
│ │ └── reset.css
│ ├── html
│ │ └── index.html
│ ├── images
│ │ ├── home01.jpg
│ │ ├── home02.jpg
│ │ ├── home03.jpg
│ │ └── landlord01.jpg
│ ├── js
│ │ ├── index.js
│ │ └── jquery.min.js
│ └── plugins
│ ├── bootstrap
│ │ └─...
│ └── font-awesome
│ └─...
├── templates
│ └── index.html
└── test.py
在handler中使用render()方法來渲染模板並返回給客戶端。
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html") # 渲染主頁模板,並返回給客戶端。
current_path = os.path.dirname(__file__)
app = tornado.web.Application(
[
(r'^/$', IndexHandler),
(r'^/view/(.*)$', StaticFileHandler, {"path":os.path.join(current_path, "statics/html")}),
],
static_path=os.path.join(current_path, "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
2. 模板語法
2-1 變量與表達式
在tornado的模板中使用{{}}作爲變量或表達式的佔位符,使用render渲染後佔位符{{}}會被替換爲相應的結果值。
我們將index.html中的一條房源信息記錄
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>398</span>/晚</div>
<div class="house-intro">
<span class="house-title">寬窄巷子+160平大空間+文化保護區雙地鐵</span>
<em>整套出租 - 5分/6點評 - 北京市豐臺區六裏橋地鐵</em>
</div>
</div>
</li>
改爲模板:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{price}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title}}</span>
<em>整套出租 - {{score}}分/{{comments}}點評 - {{position}}</em>
</div>
</div>
</li>
渲染方式如下:
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}
self.render("index.html", **house_info)
{{}}不僅可以包含變量,還可以是表達式,如:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{p1 + p2}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{"+".join(titles)}}</span>
<em>整套出租 - {{score}}分/{{comments}}點評 - {{position}}</em>
</div>
</div>
</li>
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"p1": 198,
"p2": 200,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}
self.render("index.html", **house_info)
2-2 控制語句
可以在Tornado模板中使用Python條件和循環語句。控制語句以{\%和\%}包圍,並以類似下面的形式被使用:
{% if page is None %}
或
{% if len(entries) == 3 %}
控制語句的大部分就像對應的Python語句一樣工作,支持if、for、while,注意end:
{% if ... %} ... {% elif ... %} ... {% else ... %} ... {% end %}
{% for ... in ... %} ... {% end %}
{% while ... %} ... {% end %}
再次修改index.html:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{house["title"]}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
python中渲染語句爲:
class IndexHandler(RequestHandler):
def get(self):
houses = [
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}]
self.render("index.html", houses=houses)
2-3 函數
static_url()
Tornado模板模塊提供了一個叫作static_url的函數來生成靜態文件目錄下文件的URL。如下面的示例代碼:
<link rel="stylesheet" href="{{ static_url("style.css") }}">
這個對static_url的調用生成了URL的值,並渲染輸出類似下面的代碼:
<link rel="stylesheet" href="/static/style.css?v=ab12">
優點:
- static_url函數創建了一個基於文件內容的hash值,並將其添加到URL末尾(查詢字符串的參數v)。這個hash值確保瀏覽器總是加載一個文件的最新版而不是之前的緩存版本。無論是在你應用的開發階段,還是在部署到生產環境使用時,都非常有用,因爲你的用戶不必再爲了看到你的靜態內容而清除瀏覽器緩存了。
- 另一個好處是你可以改變你應用URL的結構,而不需要改變模板中的代碼。例如,可以通過設置static_url_prefix來更改Tornado的默認靜態路徑前綴/static。如果使用static_url而不是硬編碼的話,代碼不需要改變。
轉義
我們新建一個表單頁面new.html
<!DOCTYPE html>
<html>
<head>
<title>新建房源</title>
</head>
<body>
<form method="post">
<textarea name="text"></textarea>
<input type="submit" value="提交">
</form>
{{text}}
</body>
</html>
對應的handler爲:
class NewHandler(RequestHandler):
def get(self):
self.render("new.html", text="")
def post(self):
text = self.get_argument("text", "")
print text
self.render("new.html", text=text)
當我們在表單中填入如下內容時:
<script>alert("hello!");</script>
寫入的js程序並沒有運行,而是顯示出來了:
我們查看頁面源代碼,發現<、>、"等被轉換爲對應的html字符。
<script>alert("hello!");</script>
這是因爲tornado中默認開啓了模板自動轉義功能,防止網站受到惡意攻擊。
我們可以通過raw語句來輸出不被轉義的原始格式,如:
{% raw text %}
注意:在Firefox瀏覽器中會直接彈出alert窗口,而在Chrome瀏覽器中,需要set_header("X-XSS-Protection", 0)
若要關閉自動轉義,一種方法是在Application構造函數中傳遞autoescape=None,另一種方法是在每頁模板中修改自動轉義行爲,添加如下語句:
{% autoescape None %}
escape()
關閉自動轉義後,可以使用escape()函數來對特定變量進行轉義,如:
{{ escape(text) }}
自定義函數
在模板中還可以使用一個自己編寫的函數,只需要將函數名作爲模板的參數傳遞即可,就像其他變量一樣。
我們修改後端如下:
def house_title_join(titles):
return "+".join(titles)
class IndexHandler(RequestHandler):
def get(self):
house_list = [
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}]
self.render("index.html", houses=house_list, title_join = house_title_join)
前段模板我們修改爲:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
2-4 塊
我們可以使用塊來複用模板,塊語法如下:
{% block block_name %} {% end %}
現在,我們對模板index.html進行抽象,抽離出父模板base.html如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
{% block page_title %}{% end %}
<link href="{{static_url('plugins/bootstrap/css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{static_url('plugins/font-awesome/css/font-awesome.min.css')}}" rel="stylesheet">
<link href="{{static_url('css/reset.css')}}" rel="stylesheet">
<link href="{{static_url('css/main.css')}}" rel="stylesheet">
{% block css_files %}{% end %}
</head>
<body>
<div class="container">
<div class="top-bar">
{% block header %}{% end %}
</div>
{% block body %}{% end %}
<div class="footer">
{% block footer %}{% end %}
</div>
</div>
<script src="{{static_url('js/jquery.min.js')}}"></script>
<script src="{{static_url('plugins/bootstrap/js/bootstrap.min.js')}}"></script>
{% block js_files %}{% end %}
</body>
</html>
而子模板index.html使用extends來使用父模板base.html,如下:
{% extends "base.html" %}
{% block page_title %}
<title>愛家-房源</title>
{% end %}
{% block css_files %}
<link href="{{static_url('css/index.css')}}" rel="stylesheet">
{% end %}
{% block js_files %}
<script src="{{static_url('js/index.js')}}"></script>
{% end %}
{% block header %}
<div class="nav-bar">
<h3 class="page-title">房 源</h3>
</div>
{% end %}
{% block body %}
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
{% end %}
{% block footer %}
<p><span><i class="fa fa-copyright"></i></span>愛家租房 享受家的溫馨</p>
{% end %}
4.3 練習
-
對比Django模板與Tornado模板的異同。
-
練習使用Tornado模板的語法。
4.2 使用模板
1. 路徑與渲染
使用模板,需要仿照靜態文件路徑設置一樣,向web.Application類的構造函數傳遞一個名爲template_path的參數來告訴Tornado從文件系統的一個特定位置提供模板文件,如:
app = tornado.web.Application(
[(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
在這裏,我們設置了一個當前應用目錄下名爲templates的子目錄作爲template_path的參數。在handler中使用的模板將在此目錄中尋找。
現在我們將靜態文件目錄statics/html中的index.html複製一份到templates目錄中,此時文件目錄結構爲:
.
├── statics
│ ├── css
│ │ ├── index.css
│ │ ├── main.css
│ │ └── reset.css
│ ├── html
│ │ └── index.html
│ ├── images
│ │ ├── home01.jpg
│ │ ├── home02.jpg
│ │ ├── home03.jpg
│ │ └── landlord01.jpg
│ ├── js
│ │ ├── index.js
│ │ └── jquery.min.js
│ └── plugins
│ ├── bootstrap
│ │ └─...
│ └── font-awesome
│ └─...
├── templates
│ └── index.html
└── test.py
在handler中使用render()方法來渲染模板並返回給客戶端。
class IndexHandler(RequestHandler):
def get(self):
self.render("index.html") # 渲染主頁模板,並返回給客戶端。
current_path = os.path.dirname(__file__)
app = tornado.web.Application(
[
(r'^/$', IndexHandler),
(r'^/view/(.*)$', StaticFileHandler, {"path":os.path.join(current_path, "statics/html")}),
],
static_path=os.path.join(current_path, "statics"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
2. 模板語法
2-1 變量與表達式
在tornado的模板中使用{{}}作爲變量或表達式的佔位符,使用render渲染後佔位符{{}}會被替換爲相應的結果值。
我們將index.html中的一條房源信息記錄
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>398</span>/晚</div>
<div class="house-intro">
<span class="house-title">寬窄巷子+160平大空間+文化保護區雙地鐵</span>
<em>整套出租 - 5分/6點評 - 北京市豐臺區六裏橋地鐵</em>
</div>
</div>
</li>
改爲模板:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{price}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title}}</span>
<em>整套出租 - {{score}}分/{{comments}}點評 - {{position}}</em>
</div>
</div>
</li>
渲染方式如下:
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}
self.render("index.html", **house_info)
{{}}不僅可以包含變量,還可以是表達式,如:
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{p1 + p2}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{"+".join(titles)}}</span>
<em>整套出租 - {{score}}分/{{comments}}點評 - {{position}}</em>
</div>
</div>
</li>
class IndexHandler(RequestHandler):
def get(self):
house_info = {
"p1": 198,
"p2": 200,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}
self.render("index.html", **house_info)
2-2 控制語句
可以在Tornado模板中使用Python條件和循環語句。控制語句以{\%和\%}包圍,並以類似下面的形式被使用:
{% if page is None %}
或
{% if len(entries) == 3 %}
控制語句的大部分就像對應的Python語句一樣工作,支持if、for、while,注意end:
{% if ... %} ... {% elif ... %} ... {% else ... %} ... {% end %}
{% for ... in ... %} ... {% end %}
{% while ... %} ... {% end %}
再次修改index.html:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{house["title"]}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
python中渲染語句爲:
class IndexHandler(RequestHandler):
def get(self):
houses = [
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"title": "寬窄巷子+160平大空間+文化保護區雙地鐵",
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}]
self.render("index.html", houses=houses)
2-3 函數
static_url()
Tornado模板模塊提供了一個叫作static_url的函數來生成靜態文件目錄下文件的URL。如下面的示例代碼:
<link rel="stylesheet" href="{{ static_url("style.css") }}">
這個對static_url的調用生成了URL的值,並渲染輸出類似下面的代碼:
<link rel="stylesheet" href="/static/style.css?v=ab12">
優點:
- static_url函數創建了一個基於文件內容的hash值,並將其添加到URL末尾(查詢字符串的參數v)。這個hash值確保瀏覽器總是加載一個文件的最新版而不是之前的緩存版本。無論是在你應用的開發階段,還是在部署到生產環境使用時,都非常有用,因爲你的用戶不必再爲了看到你的靜態內容而清除瀏覽器緩存了。
- 另一個好處是你可以改變你應用URL的結構,而不需要改變模板中的代碼。例如,可以通過設置static_url_prefix來更改Tornado的默認靜態路徑前綴/static。如果使用static_url而不是硬編碼的話,代碼不需要改變。
轉義
我們新建一個表單頁面new.html
<!DOCTYPE html>
<html>
<head>
<title>新建房源</title>
</head>
<body>
<form method="post">
<textarea name="text"></textarea>
<input type="submit" value="提交">
</form>
{{text}}
</body>
</html>
對應的handler爲:
class NewHandler(RequestHandler):
def get(self):
self.render("new.html", text="")
def post(self):
text = self.get_argument("text", "")
print text
self.render("new.html", text=text)
當我們在表單中填入如下內容時:
<script>alert("hello!");</script>
寫入的js程序並沒有運行,而是顯示出來了:
我們查看頁面源代碼,發現<、>、"等被轉換爲對應的html字符。
<script>alert("hello!");</script>
這是因爲tornado中默認開啓了模板自動轉義功能,防止網站受到惡意攻擊。
我們可以通過raw語句來輸出不被轉義的原始格式,如:
{% raw text %}
注意:在Firefox瀏覽器中會直接彈出alert窗口,而在Chrome瀏覽器中,需要set_header("X-XSS-Protection", 0)
若要關閉自動轉義,一種方法是在Application構造函數中傳遞autoescape=None,另一種方法是在每頁模板中修改自動轉義行爲,添加如下語句:
{% autoescape None %}
escape()
關閉自動轉義後,可以使用escape()函數來對特定變量進行轉義,如:
{{ escape(text) }}
自定義函數
在模板中還可以使用一個自己編寫的函數,只需要將函數名作爲模板的參數傳遞即可,就像其他變量一樣。
我們修改後端如下:
def house_title_join(titles):
return "+".join(titles)
class IndexHandler(RequestHandler):
def get(self):
house_list = [
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
},
{
"price": 398,
"titles": ["寬窄巷子", "160平大空間", "文化保護區雙地鐵"],
"score": 5,
"comments": 6,
"position": "北京市豐臺區六裏橋地鐵"
}]
self.render("index.html", houses=house_list, title_join = house_title_join)
前段模板我們修改爲:
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
2-4 塊
我們可以使用塊來複用模板,塊語法如下:
{% block block_name %} {% end %}
現在,我們對模板index.html進行抽象,抽離出父模板base.html如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
{% block page_title %}{% end %}
<link href="{{static_url('plugins/bootstrap/css/bootstrap.min.css')}}" rel="stylesheet">
<link href="{{static_url('plugins/font-awesome/css/font-awesome.min.css')}}" rel="stylesheet">
<link href="{{static_url('css/reset.css')}}" rel="stylesheet">
<link href="{{static_url('css/main.css')}}" rel="stylesheet">
{% block css_files %}{% end %}
</head>
<body>
<div class="container">
<div class="top-bar">
{% block header %}{% end %}
</div>
{% block body %}{% end %}
<div class="footer">
{% block footer %}{% end %}
</div>
</div>
<script src="{{static_url('js/jquery.min.js')}}"></script>
<script src="{{static_url('plugins/bootstrap/js/bootstrap.min.js')}}"></script>
{% block js_files %}{% end %}
</body>
</html>
而子模板index.html使用extends來使用父模板base.html,如下:
{% extends "base.html" %}
{% block page_title %}
<title>愛家-房源</title>
{% end %}
{% block css_files %}
<link href="{{static_url('css/index.css')}}" rel="stylesheet">
{% end %}
{% block js_files %}
<script src="{{static_url('js/index.js')}}"></script>
{% end %}
{% block header %}
<div class="nav-bar">
<h3 class="page-title">房 源</h3>
</div>
{% end %}
{% block body %}
<ul class="house-list">
{% if len(houses) > 0 %}
{% for house in houses %}
<li class="house-item">
<a href=""><img src="/static/images/home01.jpg"></a>
<div class="house-desc">
<div class="landlord-pic"><img src="/static/images/landlord01.jpg"></div>
<div class="house-price">¥<span>{{house["price"]}}</span>/晚</div>
<div class="house-intro">
<span class="house-title">{{title_join(house["titles"])}}</span>
<em>整套出租 - {{house["score"]}}分/{{house["comments"]}}點評 - {{house["position"]}}</em>
</div>
</div>
</li>
{% end %}
{% else %}
對不起,暫時沒有房源。
{% end %}
</ul>
{% end %}
{% block footer %}
<p><span><i class="fa fa-copyright"></i></span>愛家租房 享受家的溫馨</p>
{% end %}
4.3 練習
-
對比Django模板與Tornado模板的異同。
-
練習使用Tornado模板的語法
前端模板 騰訊模板https://github.com/aui/art-template