从用户那里收集信息的常用方法就是使用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重定向告诉浏览器“是的,你的请求有效,可以在这里找到响应”,并且不会缓存重定向目标。