從用戶那里收集信息的常用方法就是使用HTML表單。無論是使用瀏覽器提交表單,還是使用AJAX提交,或是運用精巧的前端控件,底層機制通常仍舊是HTML表單。
向服務器發送客戶端數據###
向服務器發送客戶端數據有兩種方式:查詢字符串和請求正文。通常,如果是使用查詢字符串,就發起了一個GET請求;如果是使用請求正文,就發起了一個POST請求。
有一種普遍的誤解是POST請求是安全的,而GET請求不安全。事實上如果使用HTTPS協議,兩者都是安全的;如果不使用,則都不安全。如果不使用HTTPS協議,入侵者會像查看GET請求的查詢字符串一樣,輕松查看POST請求的報文數據。然而,如果你使用GET請求,用戶會在查詢字符串中看到所有的輸入數據(包括隱藏域)。此外,瀏覽器會限制查詢字符串的長度(對請求正文沒有長度限制)。基於這些原因,一般推薦使用POST進行表單提交。
HTML表單###
例子:
<form action="/process" method="POST">
<input type="hidden" name="hush" val="hidden, but not secret!">
<div>
<label for="fieldColor">Your favorite color: </label>
<input type="text" id="fieldColor" name="color">
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
請注意,在<form>
標記中提交方法被明確地指定為POST:如果不這么做,默認進行GET提交。action的值被指定為用於接收表單數據的URL。如果你忽略這個值,表單會提交到它被加載進來時的同一URL。我建議你始終都為action提供一個有效值,即使是使用AJAX提交(這會防止你丟失數據)
從服務器的角度來看,最重要的屬性是<input>
域中的name屬性,這樣服務器才能識別字段。name屬性與id屬性是截然不同的,后者只適用於樣式和前端功能(它不會發送到服務器端)。
HTML並不會限制在同一個頁面上有多個表單(遺憾的是有些早期服務器框架有限制,比如ASP)。建議保持表達邏輯上的一致性:一個表單應該只包含想要提交的字段。如果一個頁面上有兩個不同的action,請使用兩個不同的表單。例如,在一個頁面上一個表單用於網站搜索,另一個表單用於登錄獲得電子簡訊。只用一個大表單是可行的,可以根據用戶點擊的按鈕判斷采用哪個action,但是這會讓人頭疼,而且通常對於殘疾人是不友好的(由於無障礙瀏覽器呈現表單的方式)。
當用戶提交表單時,/process URL被請求,字段值在請求正文中被傳輸到服務器。
編碼###
當表單被提交(通過瀏覽器或AJAX)時,某種程度上它必須被編碼。如果不明確地指定編碼,則默認為application/x-wwwform-urlencoded(這只是一個冗長的用於“URL編碼”的媒體類型)。它是受Express支持的基本、易用的編碼。
如果需要上傳文件,事情就開始變得復雜起來。使用URL編碼很難發送文件,所以不得不使用multipart/form-data編碼類型,這並不直接由Express處理(事實上,Express仍然支持這種編碼,但是在Express的下一個版本它會被移除,並且它也並不被建議使用)。
處理表單的不同方式###
如果不使用AJAX,唯一的選擇是用瀏覽器提交表單,這會重新加載頁面。然而,如何重新加載頁面由你來決定。處理表單時有兩件事需要考慮:處理表單是哪個路徑(action),以及向瀏覽器發出怎樣的響應。
如果表單使用的是method="POST"(推薦使用),那么展現表單和處理表單通常使用相同的路徑:這樣可以區分開來,因為前者是一個GET請求,而后者是一個POST請求。如果采用這種方法,就可以省略表單上的action屬性。
無論使用什么路徑來處理表單,必須決定如何響應瀏覽器。
- 直接響應HTML
處理表單之后,可以直接向瀏覽器返回HTML(例如,一個視圖)。如果用戶嘗試重新加載頁面,這種方法就會產生警告,並且會影響書簽和后退按鈕。基於這些原因,不推薦這種方法。
- 302重定向:
雖然這是一種常見的方法,但這是對響應代碼302本義的濫用。HTTP 1.1增加了響應代碼303,一種更合適的代碼。除非你有理由讓瀏覽器回到1996年,否則你應該改用303。
- 303重定向:
HTTP 1.1添加了響應代碼303用來解決302重定向的濫用。HTTP規范明確地表明瀏覽器303重定向后,無論之前是什么方法,都應該使用GET
請求。這是用於響應表單提交請求的推薦方法。
由於推薦通過303重定向來響應表單提交,接下來的問題是:“重定向指向哪里?”。下面是一些常用的方法。
- 重定向到專用的成功/失敗頁面
這種方法需要為適當的成功或失敗消息提供URL。例如,如果一個用戶通過促銷郵件注冊,但是有一個數據庫錯誤,可能希望重定向到/error/database。如果用戶的電子郵件地址是無效的,可以重定向到/error/invalid-email。如果一切順利,可以重定向到/promo-email/thank-you。這種方法的一個優點是便於分析:訪問/promo-email/thank-you頁面的人數應該和登錄促銷郵件的人數大致相關。而且這種方法也很容易實現。然而它還有一些缺點。這意味着你\必須針對每一種可能性來分配URL,這也意味着頁面設計、編寫復制和維護。另一個缺點是用戶體驗欠佳
- 運用flash消息重定向到原位置
由於有許多小表單分散在整個站點中(例如,電子郵件登錄),最好的用戶體驗是不干擾用戶的導航流。也就是說,需要一個不用離開當前頁面就能提交表單的方法。當然,要做到這一點,可以用AJAX,但是如果你不想用AJAX(或者你希望備用機制能夠提供一個好的用戶體驗),可以重定向回用戶之前瀏覽的頁面。最簡單的方法是在表單中使用一個隱藏域來存放當前URL。因為你想有一種反饋,表明用戶的提交信息已收到,所以你可以使用flash消息。
- 運用flash消息重定向到新位置
大型表單通常都會有自己的頁面,一旦提交就沒有必要停留在這個頁面上了。在這種情況下,就要考慮一下用戶接下來想去哪兒,並相應地進行重定向。
如果使用AJAX,推薦使用專門的URL。
Express表單處理###
如果使用GET
進行表單處理,表單域在req.query
對象中。如果使用POST
(推薦使用的),需要引入中間件來解析URL編碼體。
- 首先,安裝body-parser中間件(
npm install --save body-parser
),然后引入:
var bodyParser = require('body-parser');
app.use(bodyParser());
有時,你會發現有些地方不鼓勵使用express.bodyParser,並且理由充分。然而,這個問題在Epress 4.0中消失了,body-parser中間件是安全的並且推薦使用。
- 創建
/views/newsletter.handlebars
:
<h2>Sign up for our newsletter to receive news and specials!</h2>
<form class="form-horizontal" role="form"
action="/process?form=newsletter" method="POST">
<input type="hidden" name="_csrf" value="{{csrf}}">
<div class="form-group">
<label for="fieldName" class="col-sm-2 control-label">Name</label>
<div class="col-sm-4">
<input type="text" class="form-control"
id="fieldName" name="name">
</div>
</div>
<div class="form-group">
<label for="fieldEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-4">
<input type="email" class="form-control" required
id="fieldName" name="email">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button type="submit" class="btn btn-default">Register</button>
</div>
</div>
</form>
- 應用文件
app.get('/newsletter', function(req, res){
//我們會在后面學到CSRF……目前, 只提供一個虛擬值
res.render('newsletter', { csrf: 'CSRF token goes here' });
});
app.post('/process', function(req, res){
console.log('Form (from querystring): ' + req.query.form);
console.log('CSRF token (from hidden form field): ' + req.body._csrf);
console.log('Name (from visible form field): ' + req.body.name);
console.log('Email (from visible form field): ' + req.body.email);
res.redirect(303, '/thank-you');
});
在處理程序中,我們將重定向到“thank you”視圖。我們可以在此渲染視圖,但是如果這樣做,訪問者的瀏覽器地址欄仍舊是/process,這可能會令人困惑。發起一個重定向可以解決這個問題。
在這種情況下使用303(或302)重定向,而不是301重定向,這一點非常重要。301重定向是“永久”的,意味着瀏覽器會緩存重定向目標。如果使用301重定向並且試圖第二次提交表單,瀏覽器會繞過整個/process處理程序直接進入/thank you頁面,因為它正確地認為重定向是永久性的。另一方面,303重定向告訴瀏覽器“是的,你的請求有效,可以在這里找到響應”,並且不會緩存重定向目標。