基础功能
Node是十分贴近网络协议的,它非阻塞、事件机制使得我们在网络编程时十分轻便。
下面的内容,将从http模块中服务器端的request事件开始分析。
官方经典的HelloWorld代码:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
对于一个应用而言,上面这样的响应远远无法达到业务需求。我们需要从下面这个函数开始展开:
function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end();
}
这个方法是createServer方法的参数。我们的应用可能非常复杂,但是只要最终返回这个函数作为参数,作为request事件的侦听器就可以了。
请求方法
常用请求方法一览:GET(查看)、POST(更新)、HEAD、DELETE(删除)、PUT(新建)、CONNECT。
请求方法存在于报文第一行的第一个单词。
HTTP_Parser在解析请求报文的时候,将会把报文头抽取出来,设置为req.method。我们可以通过req.method获得请求方法来决定响应的行为。
function (req, res) {
switch (req.method) case 'POST':
update(req, res);
break;
case 'DELETE':
remove(req, res);
break;
case 'PUT':
create(req, res);
break;
case 'GET':
default:
get(req, res);
}
}
上面这段代码代表了一种根据请求方法将复杂的业务逻辑分发的思路。
路径解析
路径部分存在于报文第一行的第二部分。HTTP_Parser将其解析为req.url。
最常见的根据路径进行业务处理的应用时静态文件服务器,它会根据路径去查找磁盘中的文件,然后将其响应给客户端。
function (req, res) {
var pathname = url.parse(req.url).pathname;
fs.readFile(path.join(ROOT, pathname), function (err, file) {
if (err) {
res.writeHead(404);
res.end('找不到相关文件');
return;
}
res.writeHead(200);
res.end(file);
});
}
还有一种比较常见的分发场景是根据路径来选择控制器,它预设路径为控制器和行为的组合,无须额外配置路由信息:
/controller/action/a/b/c
controller会对应一个控制器,action则对应控制器的行为:
这样,业务部分可以只关心具体业务的实现。
handles.index = {};
handles.index.index = function (req, res, foo, bar) {
res.writeHead(200);
res.end(foo);
};
查询字符串
查询字符串位于路径之后,在地址栏中路径后的?foo=bar&baz=val就是查询字符串。
Node提供了querystring模块用于处理这部分的数据。
var url = require('url');
var querystring = require('querystring');
var query = querystring.parse(url.parse(req.url).query);
更简单的语法:
var query = url.parse(req.url, true).query;
它会讲查询字符串解析为一个JSON对象。
注意:如果查询字符串的键出现了多次,那么它的值就会是一个数组。所以业务的判断一定要检查值是数组还是字符串。
Cookie
cookie用来标识和认证一个用户。Cookie的处理分为如下几步:
1、服务器向客户端发送Cookie
2、浏览器保存Cookie
3、以后每次,浏览器都会将Cookie发送给服务器端。
HTTP_Parser会将所有的报文字段解析到req.headers上,那么Cookie就是req.headers.cookie。
cookie的值的格式是key=value; key2=value2
的形式。我们需要解析cookie的话也非常容易:
var parseCookie = function (cookie) {
var cookies = {};
if (!cookie) {
return cookies;
}
var list = cookie.split(';');
for (var i = 0; i < list.length; i++) {
var pair = list[i].split('=');
cookies[pair[0].trim()] = pair[1];
}
return cookies;
};
在业务逻辑代码真正执行之前,我们可以将其挂载在req对象上,让业务代码直接访问:
function (req, res) {
req.cookies = parseCookie(req.headers.cookie);
hande(req, res);
}
cookie的几个可选参数:
1、path:表示这个cookie影响到的路径,当前访问的路径不满足这个匹配时,浏览器不发送这个cookie。
2、Expires和Max-Age是用来告知浏览器这个cookie多久之后过期的。如果不设置该选项,在关闭浏览器时就会丢失这个Cookie。
3、HttpOnly告知浏览器不允许通过脚本document.cookie去更改这个cookie。事实上,设置了HttpOnly后,这个值在document.cookie中不可见。
4、Secure。当Secure为true的时候,在HTTP中无效,仅在HTTPS中才有效,表示创建的Cookie只能在HTTPS中被浏览器传递到服务器端进行验证。如果是HTTP连接则不会传递该信息。所以很难被窃听。
Session
通过Cookie,浏览器和服务器可以实现状态的记录。但是cookie不是完美的,例如,体积过大,还有就是Cookie可以在前后端进行修改,因此数据就很容易被篡改和伪造。
如果一个服务器有部分逻辑是根据Cookie中的isVIP来判断用户级别,那么一个普通用户只需要修改cookie就可以轻松享受VIP服务了。
也就是说,Cookie对于敏感数据的保护是无效的。
为此Session应运而生。Session数据只保留在服务器端,客户端无法修改。
如何将每个客户和服务器中的数据一一对应呢:
方法1:基于Cookie。
方法2:通过查询字符串来实现浏览器端和服务器端数据的对应。
缓存
Basic认证
数据上传
路由解析
中间件
对于Web应用而言,我们不希望接触到太多的细节性的处理。为此我们引入中间件,来简化和隔离这些基础设施与业务逻辑之间的细节。这样,开发者就能够关注在业务的开发商,达到提升效率的目的。
中间件的含义:借指了这种封装底层细节,为上层提供更方便的服务的意义。
例如,封装所有HTTP请求细节,所以开发者就可以脱离这些细节,专注在业务上。
从HTTP请求到具体的业务逻辑之间,其实有很多的细节需要处理。Node的http模块提供了应用层协议网络的封装,对具体的业务并没有支持。在业务逻辑之下,必须有开发框架对业务的支持。
这里我们可以通过中间件的形式搭建开发框架,这个开发框架用来组织各个中间件。对于Web应用的各种基础功能,我们通过中间件来完成,每个中间件处理掉相对简单的逻辑,最终汇成强大的基础框架。
为具体的业务逻辑添加中间件的方式:
app.use('/user/:username', querystring, cookie, session, function (req, res) {
// TODO
});
这里的querystring、cookie、session都是中间件。
路由配置:
app.get('/user/:username', querystring, cookie, session, getUser); app.put('/user/:username', querystring, cookie, session, updateUser);
// 更多路由
为每个路由都配置中间件并不是一个好的设计,我们可以将路由和中间件结合:
app.use(querystring);
app.use(cookie);
app.use(session);
app.get('/user/:username', getUser);
app.put('/user/:username', authorize, updateUser);
页面渲染
(待续)