本节是 expross 的第五次迭代,主要的目标是封装 request 和 response 两个对象,方便使用。
其实 nodejs 已经给我们提供这两个默认的对象,之所以要封装是因为丰富一下二者的接口,方便框架使用者,目前框架在 response 对象上已经有一个接口:
if(!res.send) {
res.send = function(body) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(body);
};
}
如果需要继续封装,也要类似的结构在代码上添加显然会给人一种很乱的感觉,因为 request 和 response 的原始版本是 nodejs 提供给框架的,框架获取到的是两个对象,并不是类,要想在二者之上提供另一组接口的办法有很多,归根结底就是将新的接口加到该对象上或者加到该对象的原型链上,目前的代码选择了前者,Express 的代码选择了后者。
首先建立两个文件:request.js
和 response.js
,二者分别导出 req
和 res
对象。
//request.js
var http = require('http');
var req = Object.create(http.IncomingMessage.prototype);
module.exports = req;
//response.js
var http = require('http');
var res = Object.create(http.ServerResponse.prototype);
module.exports = res;
二者文件的代码都是创建一个对象,分别指向 nodejs 提供的 request 和 response 两个对象的原型,以后 expross 自定的接口可以统一挂载到这两个对象上。
接着修改 Application.handle
函数,因为这个函数里面有新鲜出炉的 request 和 response。思路很简单,就是将二者的原型指向我们自建的 req
和 res
。因为 req
和 res
对象的原型和 request 和 response 的原型相同,所以并不影响原有 nodejs 的接口。
var request = require('./request');
var response = require('./response');
...
Application.prototype.handle = function(req, res) {
Object.setPrototypeOf(req, request);
Object.setPrototypeOf(res, response);
...
};
这里将原有的 res.send
转移到了 response.js 文件中。
res.send = function(body) {
this.writeHead(200, {
'Content-Type': 'text/plain'
});
this.end(body);
};
注意函数中不再是 res.writeHead
和 res.end
,而是 this.writeHead
和 this.end
。
在整个路由系统中,Router.stack
每一项其实都是一个中间件,每个中间件都有可能用到 req
和 res
这两个对象,所以代码中修改 nodejs 原生提供的 request 和 response 对象的代码放到了 Application.handle
中,这样做并没有问题,但是有一种更好的方式,expross 框架将这部分代码封装成了一个内部中间件。
为了确保框架中每个中间件接收这两个参数的正确性,需要将该内部中间放到 Router.stack
的首项。这里将原有 Application 的构造函数中的代码去掉,不再是直接创建 Router() 路由系统,而是用一种惰性加载的方式,动态创建。
去除原有 Application 构造函数的代码。
function Application() {}
添加惰性初始化函数。
var middleware = require('./middleware/init');
Application.prototype.lazyrouter = function() {
if(!this._router) {
this._router = new Router();
this._router.use(middleware.init);
}
};
因为是惰性初始化,所以在使用 this._router
对象前,一定要先调用该函数动态创建 this._router
对象。类似如下代码:
//获取router
this.lazyrouter();
router = this._router;
当前代码一共有两处需要添加,一个添加中间的 Application.prototype.use
函数:
Application.prototype.use = function(fn) {
var path = '/',
router;
//获取router
this.lazyrouter();
router = this._router;
//路径挂载
if(typeof fn !== 'function') {
path = fn;
fn = arguments[1];
}
router.use(path, fn);
return this;
};
另一处是添加普通路由的函数:
http.METHODS.forEach(function(method) {
method = method.toLowerCase();
Application.prototype[method] = function(path, fn) {
this.lazyrouter();
this._router[method].apply(this._router, arguments);
return this;
};
});
接下来创建一个叫 middleware
文件夹,专门放内部中间件的文件,再创建一个 init.js
文件,放置 Application.handle
中用来初始化 res
和 req
的代码。
var request = require('../request');
var response = require('../response');
//不要忘记移除 Application.handle 中的两行代码!!!
exports.init = function expressInit(req, res, next) {
//request文件可能用到res对象
req.res = res;
//response文件可能用到req对象
res.req = req;
//修改原始req和res原型
Object.setPrototypeOf(req, request);
Object.setPrototypeOf(res, response);
//继续
next();
};
修改原有的 Applicaton.handle
函数。
Application.prototype.handle = function(req, res) {
...
// 这里无需调用lazyrouter,因为listen前一定调用了 .use 或者 .METHODS 方法。
// 如果二者都没有调用,没有必要创建路由系统。this._router 为 undefined。
var router = this._router;
if(router) {
router.handle(req, res, done);
} else {
done();
}
};
运行 node test/index.js
走起……
Express 框架中,request 和 response 两个对象有很多非常好用的函数,不过大部分和框架结构无关,并且这些函数内部过于专研细节,对框架本身的理解没有多少帮助。不过接下来有一个方面需要仔细研究一下,那就是前后端参数的传递,Express 如何获取并分类这些参数的,这一点还是需要略微了解。
默认情况下,一共有三种参数获取方式。
req.query
代表查询字符串。req.params
代表路径变量。req.body
代表表单提交变量。req.query
是最常用的方式,例如:
// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
后台获取这些参数最简单的方式就是通过 nodejs 自带的 querystring
模块分析 URL。Express 使用的是另一个 npm 包:qs
。并且将其封装为另一个内部中间件,专门负责解析查询字符串,默认加载。
req.params
是另一种从URL获取参数的方式,例如:
//路由规则 /user/:name
// GET /user/tj
req.params.name
// => "tj"
这是一种 Express 框架规定的参数获取方式,对于批量处理逻辑非常实用。在 expross 中并没有实现,因为路径匹配问题过于细节化,如果对此感兴趣可以研究研究 path-to-regexp
模块,Express 也是在其上的封装。
req.body
是获取表单数据的方式,但是默认情况下框架是不会去解析这种数据,直接使用只会返回 undefined
。如果想要支持需要添加其他中间件,例如 body-parser
或 multer
。
本小节主要介绍了 request 和 response 两个对象,并且讲解了一下现有 expross 框架中获取参数的方式,整体上并没有太深入的仿制,主要是这方面内容涉及的细节过多,过于复杂,本质上是一些工具函数的使用方式,知道了就是知道了,感觉没啥太大的帮助,除非重头再造一次轮子。
大佬,给点反馈?
平均评分 / 5. 投票数:
很抱歉,这篇文章不能帮助到你
请让我们改进这篇文章
告诉我们我们如何改善这篇文章?