雖然能讓瀏覽器顯示“Hello World”是很有趣的一件事情,但是如果能讓用戶通過表單(form)向你的應(yīng)用程序提交文本就更有趣了。這節(jié)習題中,我們將使用 form 改進你的 web 程序,并且將用戶相關(guān)的信息保存到他們的“會話(session)”中。
該學點無趣的東西了。在創(chuàng)建 form 前你需要先多學一點關(guān)于 web 的工作原理。這里講的并不完整,但是相當準確,在你的程序出錯時,它會幫你找到出錯的原因。另外,如果你理解了 form 的應(yīng)用,那么創(chuàng)建 form 對你來說就會更容易了。
我將以一個簡單的圖示講起,它向你展示了 web 請求的各個不同的部分,以及信息傳遞的大致流程:
http://wiki.jikexueyuan.com/project/learn-python-hard-way/images/13.jpg" alt="" />
為了方便講述 HTTP 請求(request) 的流程,我在每條線上面加了字母標簽以作區(qū)別:
1.你在瀏覽器中輸入網(wǎng)址 http://test.com//,然后瀏覽器會通過你的電腦的網(wǎng)絡(luò)設(shè)備發(fā)出 request(線路 A)。 2.你的 request 被傳送到互聯(lián)網(wǎng)(線路 B),然后再抵達遠端服務(wù)器(線路 C),然后我的服務(wù)器將接受這個 request。 3.我的服務(wù)器接受 request 后,我的 web 應(yīng)用程序就去處理這個請求(線路 D),然后我的 Python 代碼就會去運行 index.GET 這個“處理程序(handler)”。 4.在代碼 return 的時候,我的 Python 服務(wù)器就會發(fā)出響應(yīng)(response),這個響應(yīng)會再通過線路 D 傳遞到你的瀏覽器。 5.這個網(wǎng)站所在的服務(wù)器將響應(yīng)由線路 D 獲取,然后通過線路 C 傳至互聯(lián)網(wǎng)。 6.響應(yīng)通過互聯(lián)網(wǎng)由線路 B 傳至你的計算機,計算機的網(wǎng)卡再通過線路 A 將響應(yīng)傳給你的瀏覽器。 7.最后,你的瀏覽器顯示了這個響應(yīng)的內(nèi)容。
這段解釋中用到了一些術(shù)語。你需要掌握這些術(shù)語,以便在談?wù)撃愕?web 應(yīng)用時你能明白而且應(yīng)用它們:
瀏覽器(browser) 這是你幾乎每天都會用到的軟件。大部分人不知道它真正的原理,他們只會把它叫作“the internet”。它的作用其實是接收你輸入到地址欄網(wǎng)址(例如 http://test.com/),然后使用該信息向該網(wǎng)址對應(yīng)的服務(wù)器提出請求(request)。
地址(address) 通常這是一個像 http://test.com/一樣的 URL (Uniform Resource Locator,統(tǒng)一資源定位器),它告訴瀏覽器該打開哪個網(wǎng)站。前面的 http 指出了你要使用的協(xié)議(protocol),這里我們用的是“超文本傳輸協(xié)議(Hyper-Text Transport Protocol)”。你還可以試試 ftp://ibiblio.org/ ,這是一個“FTP 文件傳輸協(xié)議(File Transport Protocol)”的例子。test.com 這部分是“主機名(hostname)”,也就是一個便于人閱讀和記憶的字串,主機名會被匹配到一串叫作“IP 地址”的數(shù)字上面,這個“IP 地址”就相當于網(wǎng)絡(luò)中一臺計算機的電話號碼,通過這個號碼可以訪問到這臺計算機。最后,URL 中還可以尾隨一個“路徑”,例如 http://test.com//book/中的/book/,它對應(yīng)的是服務(wù)器上的某個文件或者某些資源,通過訪問這樣的網(wǎng)址,你可以向服務(wù)器發(fā)出請求,然后獲得這些資源。網(wǎng)站地址還有很多別的組成部分,不過這些是最主要的。
連接(connection) 一旦瀏覽器知道了協(xié)議(http)、服務(wù)器(http://test.com/)、以及要獲得的資源,它就要去創(chuàng)建一個連接。這個過程中,瀏覽器讓操作系統(tǒng)(Operating System, OS)打開計算機的一個“端口(port)”(通常是 80 端口),端口準備好以后,操作系統(tǒng)會回傳給你的程序一個類似文件的東西,它所做的事情就是通過網(wǎng)絡(luò)傳輸和接收數(shù)據(jù),讓你的計算機和 http://test.com/這個網(wǎng)站所屬的服務(wù)器之間實現(xiàn)數(shù)據(jù)交流。 當你使用 http://localhost:8080/訪問你自己的站點時,發(fā)生的事情其實是一樣的,只不過這次你告訴了瀏覽器要訪問的是你自己的計算機(localhost),要使用的端口 不是默認的 80,而是 8080。你還可以直接訪問 http://test.com:80/, 這和不輸入端口效果一樣,因為 HTTP 的默認端口本來就是 80。
請求(request) 你的瀏覽器通過你提供的地址建立了連接,現(xiàn)在它需要從遠端服務(wù)器要到它(或你)想要的資源。如果你在 URL 的結(jié)尾加了 /book/,那你想要的就是 /book/ 對應(yīng)的文件或資源,大部分的服務(wù)器會直接為你調(diào)用 /book/index.html 這個文件,不過我們就假裝不存在好了。瀏覽器為了獲得服務(wù)器上的資源,它需要向服務(wù)器發(fā)送一個“請求”。這里我就不講細節(jié)了,為了得到服務(wù)器上的內(nèi)容,你必須先向服務(wù)器發(fā)送一個請求才行。有意思的是,“資源”不一定非要是文件。例如當瀏覽器向你的應(yīng)用程序提出請求的時候,服務(wù)器返回的其實是你的 Python 代碼生成的一些東西。
服務(wù)器(server) 服務(wù)器指的是瀏覽器另一端連接的計算機,它知道如何回應(yīng)瀏覽器請求的文件和資源。大部分的 web 服務(wù)器只要發(fā)送文件就可以了,這也是服務(wù)器流量的主要部分。不過你學的是使用 Python 組建一個服務(wù)器,這個服務(wù)器知道如何接受請求,然后返回用 Python 處理過的字符串。當你使用這種處理方式時,你其實是假裝把文件發(fā)給了瀏覽器,其實你用的都只是代碼而已。就像你在《習題 50》中看到的,要構(gòu)建一個“響應(yīng)”其實也不需要多少代碼。
響應(yīng)(response) 這就是你的服務(wù)器回復你的請求,發(fā)回至瀏覽器的 HTML,它里邊可能有 css、javascript、或者圖像等內(nèi)容。以文件響應(yīng)為例,服務(wù)器只要從磁盤讀取文件,發(fā)送給瀏覽器就可以了,不過它還要將這些內(nèi)容包在一個特別定義的“頭部信息(header)”中,這樣瀏覽器就會知道它獲取的是什么類型的內(nèi)容。以你的 web 應(yīng)用程序為例,你發(fā)送的其實還是一樣的東西,包括 header 也一樣,只不過這些數(shù)據(jù)是你用 Python 代碼即時生成的。
這個可能是你能在網(wǎng)上找到的關(guān)于瀏覽器如何訪問網(wǎng)站的最快的快速課程了。這節(jié)課程應(yīng)該可以幫你更容易地理解本節(jié)的習題,如果你還是不明白,就到處找資料多多了解這方面的信息,直到你明白為止。有一個很好的方法,就是你對照著上面的圖示,將你在《習題 50》中創(chuàng)建的 web 程序中的內(nèi)容分成幾個部分,讓其中的各部分對應(yīng)到上面的圖示。如果你可以正確地將程序的各部分對應(yīng)到這個圖示,你就大致明白它的工作原理了。
熟悉“表單”最好的方法就是寫一個可以接收表單數(shù)據(jù)的程序出來,然后看你可以對它做些什么。先將你的 bin/app.py 修改成下面的樣子:
import web
urls = (
'/hello', 'Index'
)
app = web.application(urls, globals())
render = web.template.render('templates/')
class Index(object):
def GET(self):
form = web.input(name="Nobody")
greeting = "Hello, %s" % form.name
return render.index(greeting = greeting)
if __name__ == "__main__":
app.run()
重啟你的 web 程序(按 CTRL-C 后重新運行),確認它有運行起來,然后使用瀏覽器訪問 http://localhost:8080/hello,這時瀏覽器應(yīng)該會顯示“I just wanted to say Hello, Nobody.”,接下來,將瀏覽器的地址改成 http://localhost:8080/hello?name=Frank,然后你可以看到頁面顯示為“Hello, Frank.”,最后將 name=Frank 修改為你自己的名字,你就可以看到它對你說“Hello”了。
讓我們研究一下你的程序里做過的修改:
1.我們沒有直接為 greeting 賦值,而是使用了 web.input 從瀏覽器獲取數(shù)據(jù)。這個函數(shù)會將一組 key=value 的表述作為默認參數(shù),解析你提供的 URL 中的?name=Frank 部分,然后返回一個對象,你可以通過這個對象方便地訪問到表單的值。 2.然后我通過 form 對象的 form.name 屬性為 greeting 賦值,這句你應(yīng)該已經(jīng)熟悉了。 3.其他的內(nèi)容和以前是一樣的。
URL 中該還可以包含多個參數(shù)。將本例的 URL 改成這樣子: http://localhost:8080/hello?name=Frank&greet=Hola。然后修改代碼,讓它去獲取 form.name 和 form.greet,如下所示:
greeting = "%s, %s" % (form.greet, form.name)
修改完畢后,試著訪問新的 URL。然后將 &greet=Hola 部分刪除,看看你會得到什么樣的錯誤信息。由于我們在 web.input(name="Nobody") 中沒有為 greet 設(shè)定默認值,這樣 greet 就變成了一個必須的參數(shù),如果沒有這個參數(shù)程序就會報錯?,F(xiàn)在修改一下你的程序,在 web.input 中為 greet 設(shè)一個默認值試試看。另外你還可以設(shè) greet=None,這樣你可以通過程序檢查 greet 的值是否存在,然后提供一個比較好的錯誤信息出來,例如:
form = web.input(name="Nobody", greet=None)
if form.greet:
greeting = "%s, %s" % (form.greet, form.name)
return render.index(greeting = greeting)
else:
return "ERROR: greet is required."
你可以通過 URL 參數(shù)實現(xiàn)表單提交,不過這樣看上去有些丑陋,而且不方便一般人使用,你真正需要的是一個“POST 表單”,這是一種包含了