03
建立
應用服務引擎
應用程式
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
在瞭解
應用服務引擎(
Google App Engine
)的運作機制,也準備好開發
環境之後,就可以開始瞭解如何撰寫應用程式了。本章將介紹如何在
應
用服務引擎環境中設定一個應用程式,接著處理用戶端的請求(
request
)及回應
(
response
)來完成網站應用程式的運作。最後再介紹
應用服務引擎提供
的
webapp
開發框架(
framework
),並且使用它來開發網站應用程式。
3-1 基本的
應用服務引擎應用程式
本節將會介紹如何從無到有建立一個
應用服務引擎應用程式專案,這是在
應用服務引擎上開發應用程式的根本,熟悉它的運作模式後,便能夠以此
為基礎繼續開發更複雜的網站。
3-1-1
∣應用程式的設定檔
一個
應用服務引擎應用程式,至少要有一個
app.yaml
檔案,用來作為整
個應用程式的設定檔,開發用的伺服器及
應用服務引擎線上環境都依據
app.yaml
來調整應用程式,除了在這個檔案設定應用程式環境之外,也要設定程
式要如何處理用戶端(
client
)的請求。
在上一章測試安裝環境時
app.yaml
檔案的內容為:
application: hello version: 1
runtime: python api_version: 1
handlers: - url: /
script: main.py
04
03-1
02
01
05
07
06
基
本
的G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
以這個設定檔的內容為例,它描述了一個基本應用程式的設定,包括:
application
屬性
■
:此為應用程式
ID
(
Application Identifier
),就上述範例將應
用程式
ID
設定為
hello
,上傳部署應用程式時,就是根據這個設定值來決定要
上傳到哪一個應用程式的空間。也就是說,在
應用服務引擎上建立的
應用程式是使用何種應用程式
ID
,這個屬性就要填寫相同的
ID
。
version
屬性
■
:此表示應用程式的版本,
應用服務引擎在部署應用程式
時,不同的上傳內容可以用版本作區隔,並且隨時切換上線應用程式的版本,
而且只要沒有刪除某個版本的內容,都可以透過特定的
URL
連接到特定版本
的程式。若在上傳部署時使用相同的版本,則會將同一版本中較舊的內容覆
蓋。在這個例子中,設定為
1
。值得注意的是,版本不一定要是數字,也可以
是
alpha
或
beta
這樣的字串。
runtime
屬性
■
:這是設定應用程式該使用何種程式語言在
應用服務引擎
上運行,由於本書主要是介紹
Python
,所以,這個屬性都會設定為
python
。
api_version
屬性
■
:是用來指定使用
應用服務引擎函式庫的版本,目前
最新的版本是
1.2.2
,如果將這個屬性設定為
1
,則會使用
1.*.*
版本之中最新
的版本(基本上目前都會設定為
1
,直接使用最新的版本)。
handlers
屬性
■
:就是用來設定應用程式要怎麼處理不同的
URL
請求。其底下
每一組設定以連字號(
-
)開頭符號區隔,接著就是設定某種形式的
URL
要對
應到哪一個
Python
檔案。以上述範例來說,當
URL
是「
/
」(根目錄)時,
就對應到
main.py
檔案去處理它。
URL
的設定也可以用正規表示式(
Regular
Expression
)的方式來設定,若將此例中的
URL
設定成「
/.*
」,則表示不論何
種
URL
都是交由
main.py
來處理。
在此只介紹
app.yaml
檔案裡最基本、必須要設定的部分,還沒有將所有設定的選
項全部列出,在本書後面章節中,有需要增加、調整
app.yaml
設定的部分,就會
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
3-1-2
∣處理
URL
的
script
檔案
在
app.yaml
裡設定了不同
URL
所對應的(
Python
)
script
檔案來處理請求,以一
個網站應用程式而言,「處理請求」就是根據
URL
以及用戶端送來的參數做一些
程式運算,最後再輸出結果(某些狀況可能輸出結果為空字串)給用戶端,在《第
2-3-4
節:驗證
SDK
安裝》的範例做了回應用戶端最簡單的示範:
print 'Content-Type: text/plain' print ''
print 'Hello, world'
程式碼3-2:main.py檔案內容
根據
HTTP
通訊協定的規範,輸出回應的標頭(
response headers
)是用來提示回
應內容的格式、類別或屬性等。在上述範例中輸出了一個「
Content-Type
」的回
應標頭,告訴客戶端以下的輸出內容是
text/plain
(純文字)型態。在回應標
頭後,空一行(
print ''
)之後開始輸出回應內容。這個範例輸出了一行文字
「
'Hello, world'
」。
回應標頭可以視情況來設定,如果沒有回應標頭,有的瀏覽器會將回應的內容看
作是
text/plain
,有的則是會以
text/html
型態來處理,所以,最好在輸出前加上
「
Content-Type
」回應標頭,讓所有瀏覽器處理方式一致。
如果將
main.py
的內容修改為:
print 'Content-Type: text/plain' print ''
print '<h1>Hello, world</h1>'
04
03-1
02
01
05
07
06
基
本
的G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
則執行此程式後,在瀏覽器執行的效果如圖
3-1
所示:
圖3-1:當Content-Type為text/plain型態時,瀏覽器處理的結果
從執行結果可以看出,瀏覽器會根據
Content-Type
的類型來處理伺服器回應的結
果。若是將
Content-Type
標頭修改為
text/html
,則會變成:
圖3-2:Content-Type為text/html時,瀏覽器處理的結果
此時,瀏覽器就會以
text/html
的型態來處理回應的內容,便會處理
<h1>
這個
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
其實,
應用服務引擎上的應用程式,結構就是這麼簡單。只要有一個設定
檔案,其他的
Python
程式碼或是靜態檔案(請見《第
3-1-4
節:加入靜態檔案》)
就可以構成一個應用程式。接下來,將要介紹如何在專案中加入其他的
Python
script
檔案。
3-1-3
∣加入一個新的
script
現在來為
hello
這個應用程式做擴充。首先,在
hello
的資料夾裡加入一個
now.
py
的檔案,預計當
URL
是
/now
時,便能呼叫
now.py
這個
Python script
,以回應
現在的時間。
首先,修改
app.yaml
檔案內容,在
handlers
區段中加入一個
URL
對應規則:
handlers: - url: /
script: main.py - url: /now script: now.py
程式碼3-4:在app.yaml檔案中加入一個URL對應規則
現在這個應用程式已經設定
URL
/now
會對應到
now.py
來處理,所以,下一個步
驟就是要修改
now.py
的內容:
from datetime import datetime
now = datetime.now()
print """Content-Type: text/html
It is <b>%s</b> now.""" % now
程式碼3-5:now.py的檔案內容
這段程式碼使用到
Python
標準函式庫中的
datetime
模組,是透過
datetime.
now()
方法來取得現在的時間。在輸出結果中,用到了
Python
的多行字串(是用
04
03-1
02
01
05
07
06
基
本
的G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
Tip
Python在字串輸出時,可以使用%s表示字串型態變數(或是該變數可以轉換成字串型 態),如果要輸出多個變數,我們可以這樣做:
name = 'eric'
email = '[email protected]'
print 'Your name is %s, and email is %s' % (name, email)
便會依照變數提供的順序擺在適當的位置。
在啟動
hello
這個專案之後,開啟瀏覽器讀取
http://localhost:8080/now
此網址,
就可以看到
now.py
的執行結果:
圖3-3:/now的執行結果
Tip
如果要在Python中輸入中文字,除了檔案的編碼要是UTF-8之外,也要在Python檔案 (*.py)的第一行加上:
# coding: utf-8
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
3-1-4
∣加入靜態檔案
如果輸出的回應是一個
HTML
的頁面,很多時候會需要加入一些,如
CSS
、
JavaScript
或圖片影像等靜態檔案,不過,光是把靜態檔案放在同一個目錄(資料
夾)下,
應用服務引擎的伺服器是找不到這些檔案。因為一個
HTML
頁
面在敘述靜態檔案位置時,對於瀏覽器來說,這只是另一個
URL
,瀏覽器依然需
要對伺服器做
HTTP GET
請求,所以,必須要透過調整
app.yaml
裡的設定才能
存取靜態檔案。
就上述例子來說,若是打算在
/now
的輸出頁面上放一個時鐘的圖案,假設圖檔
是
time.png
,那麼,在
app.yaml
裡的
handlers
必須加入一組
URL
規則:
handlers: - url: /
script: main.py - url: /now script: now.py
- url: /time.png
static_files: time.png upload: time.png
程式碼3-6:在app.yaml檔案中,加入靜態檔案的設定
仍必須指定圖片的
URL
以及檔案的位置,這樣才能在
/now
的輸入裡透過
<img>
這個
HTML
標籤選到「
time.png
」這張圖片。
from datetime import datetime
print """Content-Type: text/html
It is <img src="/time.png"><b>%s</b> now.""" % datetime.now()
程式碼3-7:在now.py檔案中加入 <img> 標籤用來輸出圖片
04
03-1
02
01
05
07
06
基
本
的G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
圖3-4:在應用程式中使用靜態檔案
雖然這樣的設定可以存取到靜態檔案,但是,如果靜態檔案一多,而每個檔案都
必須逐一設定,就顯得十分麻煩。幸好,在
app.yaml
設定檔案中,是可以設定
「靜態檔案目錄」,如此一來,只需要將靜態檔案放在同一個設定的目錄下就可
以存取。
只要在
hello
這個專案目錄下建立一個
static
目錄,然後把靜態檔案(如:
time.png
)放在
static
目錄裡。再修改
app.yaml
檔案的設定如下:
handlers: - url: /
script: main.py - url: /now script: now.py
- url: /s
static_dir: static
程式碼3-8:在app.yaml檔案中加入靜態目錄的設定
透過以上的設定,
URL
「
/s
」
就會指到
static
資料夾,
now.py
的內容就可以修改
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
from datetime import datetime
print """Content-Type: text/html
It is <img src="/s/time.png"><b>%s</b> now.""" % datetime.now()
程式碼3-9:使用靜態目錄後,修改的now.py檔案內容
重新讀取
http://localhost:8080/now
後,也會有一樣的效果。即使在
static
目錄下
放置其他的檔案,也只需要在輸出的結果中修改
URL
,不必再去修改
app.yaml
檔案的設定。
在本節介紹了如何在
app.yaml
檔案中加入
URL
對應規則,以及該如何輸出純文
字或是
HTML
頁面給瀏覽器。在下一節中,將會介紹一個網站應用程式該如何處
理一般的
HTTP GET/POST
請求。
3-2 處理
HTTP
請求及回應
HTTP
通訊協定就是一連串的請求及回應,本節將要介紹如何利用
Python
標準函
式庫中的
cgi
模組來處理
HTTP
的請求及回應。
3-2-1
∣處理參數資料
雖然在上一章已經可以建立一個應用程式,而且也能夠把結果輸出到用戶端,但
是處理
HTTP
請求參數的部分,還需要借助
cgi
模組來處理。
首先,將
main.py
的內容修改為:
import cgi
query = cgi.FieldStorage()
name = query.getvalue('who', 'anonymous')
print """Content-Type: text/html
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
在此,利用了
cgi
模組中的
FieldStorage
物件,然後取得客戶端作
HTTP
請求
時的
who
參數,若
who
參數不存在,就以預設值
anonymous
取代,再把值儲存在
name
變數供後面的字串輸出。如果讀取網址
http://localhost:8080/
而不帶任何的參
數,看到的畫面會是:
圖3-5:未帶參數時,name變數使用預設值anonymous
因為找不到
who
參數的值,
name
變數的值就會是
anonymous
,倘若存取網址時帶
了
who
參數,如同
http://localhost:8080/?who=eric
,那麼看到的畫面就會是:
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
能夠處理
HTTP GET
請求所帶參數之後,依照同樣的原理,也可以用來處理表
單資料(通常是
HTTP POST
請求)。在
hello
專案資料夾中新增一個
register.
html
檔案,這個
HTML
檔案是用來呈現在一個表單,然後處理表單資料的
URL
先設定為
/reg
,所以,先將
app.yaml
的設定修改為:
handlers: - url: /
script: main.py
- url: /reg script: reg.py - url: /register.html
static_files: register.html upload: register.html
程式碼3-11:在app.yaml檔案中加入 /reg及 /register.html兩個URL規則
以下是
register.html
檔案的內容:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/ strict.dtd">
<html> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>註冊表單</title>
</head> <body>
<form method="post" action="/reg"> <fieldset>
<legend>註冊表單</legend> <p>
<label for="name">您的大名: </label> <input type="text" id="name" name="name"> </p>
<p>
<label for="email">您的 E-Mail: </label> <input type="text" id="email" name="email"> </p>
<p>
<input type="submit" value="送出資料"> </p>
</fieldset> </form> </body>
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
表單會將資料送至
/reg,接下來就是要寫處理表單的
reg.py
檔案內容:
# coding: utf-8 -*-import cgi
form = cgi.FieldStorage() name = form.getvalue('name', '') email = form.getvalue('email', '')
print """Content-Type: text/html; charset=utf-8
<h1>您註冊的資料如下:</h1> <ul>
<li>您的大名: %s</li> <li>您的Email: %s</li> </ul>
""" % (name, email)
程式碼3-13:處理表單的reg.py檔案內容
因 為
reg.py
檔 案 會 輸 出 中 文 字, 為 了 避 免 瀏 覽 器 處 理 編 碼 錯 誤, 在 此 於
Content-Type
回 應 標 頭 後 加 上
charset=utf-8
, 提 示 瀏 覽 器 字 元 編 碼 要 使 用
UTF-8
。
從這個範例可以看出,透過
cgi
模組來處理
HTTP GET/POST
的參數資料是很容
易的,只要再加上操作資料庫系統的部分,很快就可以做出一個簡單的報名系
統。
3-2-2
∣利用
template
引擎輸出網頁
從上一節的範例可以看到,輸出網頁的部分是用靜態檔案的方式處理;或是在處
理回應時,產生一大串
HTML
字串之後再輸出。如果要回應的網頁很複雜,用
Python
字串的方式來處理就會變得不好維護。
應用服務引擎提供了一套
template
系統,讓開發者可以把複雜的網頁寫成
檔案,然後,在需要變動數值的地方,再以特殊的語法將變數的值放入。例如,
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
首先,在
hello
資料夾下建立一個
result.html
檔案,作為表單處理後的輸出結
果:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/ strict.dtd">
<html> <head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <title>註冊結果</title>
</head> <body>
<h1>註冊資料如下:</h1> <ul>
<li>您的大名:{{name}}</li> <li>您的Email:{{email}}</li> </ul>
</body> </html>
程式碼3-14:result.html檔案的內容
在這個檔案中,其不同的地方就是名稱及
的變數,在此使用了這套
template
系統的語法「
{{}}
」
來輸出變數內容。為了讓處理的表單用這個檔案作為輸出,
所以
reg.py
的檔案內容必須要做一些修改:
import cgi import os
from google.appengine.ext.webapp import template
form = cgi.FieldStorage() name = form.getvalue('name', '') email = form.getvalue('email', '')
template_path = os.path.join(os.path.dirname(__file__), 'result.html')
print 'Content-Type: text/html;charset=utf-8' print ''
print template.render(template_path, {'name': name, 'email': email})
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
改寫後,在表單送出後所出現的執行結果:
圖3-7:用template改寫的註冊系統
使用
template
系統來輸出網頁,就可以把頁面呈現與程式運算的部分抽離,即使
頁面的版型要重新設計,也不必辛苦地修改
Python
檔案。關於
template
的用法,
將會在《第
6
章:
Template
引擎》逐一介紹。
Tip
Google應用服務引擎提供的template系統是django (http://www.djangoproject.com/)開 發框架的template系統,目前仍是django 0.96版的template系統。
3-2-3
∣
HTTP
請求與回應標頭
除了
GET/POST
的參數資料之外,在處理一個請求時,往往還需要更多用戶端或
是請求的資料。例如,用戶端在作
HTTP
請求時,也會根據
HTTP
通訊協定送出
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
舉例來說,大部分的瀏覽器會送出一個
User-Agent
的標頭,告訴伺服器自己是個
什麼樣的用戶端。而這樣的標題會放在程式的
HTTP_USER_AGENT
環境變數裡。所
以,我們可以寫出一個如同下列的程式碼:
import os
def main():
ua = os.environ.get('HTTP_USER_AGENT') print """Content-Type: text/html
Your browser's User-Agent is [ <b>%s</b> ]""" % ua
if __name__ == '__main__': main()
程式碼3-16:取得User-Agent請求標頭
Tip
os.environ是 以Python的dict資 料 結 構 來 儲 存 環 境 變 數, 理 論 上, 也 可 以 使 用 os.environ['HTTP_USER_AGENT'] 來取得資料,但是,這樣的寫法可能會有問題。如果 os.environ中沒有HTTP_USER_AGENT這個key,則會產生程式錯誤。所以,通常在做這樣 的操作時,都會使用has_key作檢查:
if os.environ.has_key('HTTP_USER_AGENT'): ua = os.environ['HTTP_USER_AGENT']
或是直接使用get方法來取值,如果key不存在,只會傳回None而不會產生錯誤,而且 利用get方法還可以指定預設值,因此,一般都會建議使用get方法來取dict資料結構的 值。
ua = os.environ.get('HTTP_USER_AGENT')
或是可用:
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
如果以
Mac OS X
上的
Mozilla Firefox
瀏覽器來開啟,就會得到以下的畫面:
圖3-8:取得用戶端的User-Agent請求標頭
表
3-1
列出常見的
HTTP
請求標頭名稱、對應的環境變數名稱,以及使用的時機
與意義(完整的
HTTP
請求標頭可參考
HTTP
通訊協定的文件
http://www.w3.org/
Protocols/rfc2616/rfc2616.html
)。
請求標頭名稱 環境變數名稱 使用時間及意義
Accept HTTP_ACCEPT 用 戶 端 接 受 的 回 應 格 式,
如 t e x t / h t m l , a p p l i c a t i o n / x h t m l + x m l , a p p l i c a t i o n / xml;q=0.9,*/*;q=0.8。伺服器可以
根據這個標題來決定是否要有不同 的回應型態。
Accept-Charset HTTP_ACCEPT_CHARSET 用戶端所接受的字元編碼列表。如 Big5,utf-8;q=0.7,*;q=0.7。
Accept-Languange HTTP_ACCEPT_LANGUAGE 用 戶 端 使 用 的 語 系 列 表。 如 zh-tw,en-us;q=0.7,en;q=0.3。
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
請求標頭名稱 環境變數名稱 使用時間及意義
Connection HTTP_CONNECTION 用戶端使用的連接方式。如 keep-alive。
Content-Length CONTENT_LENGTH 用戶端請求時資料的長度。通常是
HTTP POST請求,時才會傳送此 標頭。
Content-Type CONTENT_TYPE 用戶端請求時,請求資料的型態。
通常是在HTTP POST請求時,才 會傳送此標頭。
Cookie HTTP_COOKIE 如果用戶端有cookie資料,則會以 此標頭將cookie資料傳送到伺服器 端。
Host HTTP_HOST 用戶端進行HTTP請求的伺服器名 稱。 例 如,http://example.com/foo/
bar的Host就是example.com。
Keep-Alive HTTP_KEEP_ALIVE 用戶端若使用keep-alive的連接方
式時的逾時秒數。如300。
Referer HTTP_REFERER 用戶端進行HTTP請求時,URI的
參考來源(是從何處取得該URI來
作為請求)。
User-Agent HTTP_USER_AGENT 用戶端的描述,通常包括瀏覽器的
名稱以及系統平台。如果要針對不 同瀏覽器而提供不同內容時,可以 參考此標頭來作決定。
表3-1:常用的HTTP請求標頭(續)
除了
HTTP
請求標頭之外,也有一些環境變數與開發網站應用程式有關:
環境變數名稱 使用時機與意義
PATH_INFO 用戶端請求的URI。如:/ 或 /now。
QUERY_STRING 用戶端作HTTP GET請求時,所帶的參數字串。
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
環境變數名稱 使用時機與意義
REMOTE_ADDR 用戶端作HTTP請求時的IP位址。如果用戶端透過Proxy
伺服器操作,則為Proxy伺服器的IP位址。
SERVER_NAME 伺服器的名稱。
SERVER_PORT 伺服器開放服務的連接埠號碼。
表3-2:與網站應用程式開發相關的環境變數(續)
用戶端在對伺服器作
HTTP
請求時,會根據情況送出一連串的請求標頭(
request
header
);而伺服器在回應用戶端時,也可以送出回應標頭(
response header
)來
說明回應內容,或是提示瀏覽器下一步應該做什麼事。在之前的程式碼中已經介
紹過回應標頭中
Content-Type
的用法及效果,表
3-3
列出一些常用的回應標頭。
環境變數名稱 使用時機與意義
Cache-Control 用來通知用戶端,針對這個請求的回應內容要快取的時間, 讓瀏覽器決定是否快取。
使用範例:
Cache-Control: max-age=3600
Content-Length 提示用戶端回應內容的長度,單位是byte。
使用範例:
Content-Length: 4096
Content-Type 提示用戶端回應內容的型態。
使用範例:
Content-Type: application/x-javascript
Expires 用來通知用戶端,針對這個請求的回應,會在什麼時間過
期,讓瀏覽器決定是否快取。
使用範例:
Expires: Wed, 05 Oct 2016 19:16:20 GMT
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
環境變數名稱 使用時機與意義
Last-Modified 用來提示用戶端,針對這個請求的回應內容,最後一次修改
的時間,讓瀏覽器決定是否快取。
使用範例:
Last-Modified: Wed, 25 Mar 2009 02:00:00 GMT
Location 告訴瀏覽器要導向哪一個網址,送出這個標頭之後,就不能
再送出其他的回應內容。
使用範例:
Location: http://www.google.com/
Set-Cookie 如果要存放一些資料在用戶端的cookie,就可以使用這個標 頭來作設定。參數以「;」隔開。
使用範例:
Set-Cookie: name=eric; gender=m
表3-3:常用的回應標頭(續)
3-2-4
∣使用
Cookie
記錄資料
在
HTTP
通訊協定中,伺服器端如果有資料要儲存在用戶端,可以利用
cookie
機
制來儲存,伺服器只要加上
Set-Cookie
回應標頭,遵守
HTTP
通訊協定的用戶端
就會將
cookie
值儲存,在下一次對伺服器作請求時,就會加上
Cookie
這個請求
標題傳送到伺服器端。
Tip
很多網站的身分確認機制都會利用cookie來儲存身分識別的資料,如果有安全性的顧慮, 可以對cookie加上過期時間以及限制使用網域。詳情可以參考維基百科的介紹:
http://en.wikipedia.org/wiki/HTTP_cookie
不過,知名的XSS(Cross-Site Scripting)攻擊手法就是利用cookie來危害網站安全, 因此,在使用cookie時,還是要十分小心。
04
03-2
02
01
05
07
06
處
理H
T
T
P
請
求
及
回
應
在用戶端的
cookie
中加入一個
counter
參數,用來計算同一個用戶端做了多少次
請求。
# coding: utf-8 -*-import os
def main():
cookie = os.environ.get('HTTP_COOKIE') counter = 0
if cookie: # 如果有 cookie,看看裡面是不是已經有 counter 值 pos = cookie.find('counter')
if pos != -1:
counter_param = cookie[pos:].split(';')[0] counter = int(counter_param.split('=')[1])
counter = counter + 1
# 送出新的 counter 值到用戶端的 cookie print """Set-Cookie: counter=%d Content-Type: text/html; charset=utf-8
您已經瀏覽了 <b>%d</b> 次""" % (counter, counter)
if __name__ == '__main__': main()
程式碼3-17:利用cookie做成簡單的訪客計數器
此程式從環境變數中取得
HTTP_COOKIE
的值,觀看用戶端是否有帶
cookie
資料
到伺服器端。如果訪客是第一次瀏覽這一個
URI
,那麼
cookie
變數的值就會是
None
,而
counter
就會從
0
開始計算。
如果
cookie
值不為
None
,程式就會開始從
cookie
尋找是否有出現「
counter
」,
由 於
cookie
值 可 能 不 只 一 個 參 數,
cookie
資 料 內 容 可 能 會 是
a=b; x=123;
foo=bar
的 字 串。 如 果 找 到「
counter
」( 也 就 是
pos
不 為
-1
), 就 會 先 把 原 本
cookie
資料裡「
counter
」之前的內容刪除,再拿「以『;』作區隔」的第一筆資
料,應該會是「
counter=xxx
」,這時只要取出「
=
」右側的值,就是
counter
目前
的數字了。
04
03
02
01
05
07
06
建
立G
o
o
g
le
應
用
服
務
引
擎
應
用
程
式
(字串第
6
個字元),那麼
cookie[5:]
就會是
'x=123; foo=bar
'
的字串,此
時, 再 將 這 個 字 串 用「
cookie[5:].split(';')
」
分 開, 就 會 產 生
['x=123',
'foo=bar']
的
Python list
資料結構,
list
中的第一個元素正是要取得的字串,所
以
cookie[5:].split(';')[0]
就 會 是
'x=123'
。 最 後, 再 使 用
split
方 法 把
「
x
」及「
123
」用「
=
」隔開,就能取到
123
。
不過,要注意的是,因為本例的
counter
是數字型態的變數,用
split
方法拿到
的結果則是字串型態,所以,在上面的程式碼中使用
int()
來將字串型態強制轉
換為數字型態。
取得
counter
的資料後,剩下的工作就是把它加一,然後再利用回應標頭中的
Set-Cookie
將
counter
的資料送給用戶端儲存,下一次當用戶端對這個
URI
作請
求時,就會把
cookie
中的
counter
參數一起傳送到伺服器了。
Tip
在本例中,輸出變數時用了「%d」作為「數字型態」變數的輸出。
04
03-3
02
01
05
07
06
使
用w
e
b
a
p
p
開
發
框
架
3-3 使用
webapp
開發框架
除了使用
cgi
模組來處理
HTTP
通訊協定之外,
應用服務引擎提供了一個
簡單的開發框架(
framework
)-
webapp
,可供開發者使用。利用
webapp
開發框
架來撰寫
應用服務引擎上的網站應用程式,許多複雜的
HTTP
通訊協定的
處理都已經有可以直接使用的函式庫或是物件。而且,只用
cgi
模組開發網站應
用程式還有許多限制(例如,
CGI
程式無法修改
HTTP
狀態代碼),使用開發框
架,對於開發者來說,是可以省下許多力氣處理繁瑣的
HTTP
通訊協定(或者說
有更高的處理彈性),專注於應用程式本身的開發。
3-3-1
∣建立應用程式
webapp
開發框架中裡有一套處理
URI
規則對應的方式,在此,我們可以建立一
個
webapp_hello
專案,而其中的
app.yaml
可以改為:
application: webapp_hello version: 1
runtime: python api_version: 1
handlers:
- url: /.*
script: main.py
程式碼3-18:改寫後的app.yaml部分檔案內容
因為
URL
對應的部分將交由
webapp
開發框架來處理,在此就先把
app.yaml
裡
的
URL
全部對應到
main.py
,如果有靜態檔案或目錄要保留時,請注意要將這些
URL
的對應放在
/.*
之前,如下列程式碼:
handlers: - url: /s
static_dir: static - url: /robot.txt
static_files: robot.txt upload: robot.txt
- url: /.*