express极简的 web 开发框架。
这里创建是一个最最简单的 Express 应用
var express = require('express');
var app = express();
app.get('/', function(req, res) {
res.send('hello, express');
});
app.listen(3000);
以上代码的意思是:生成一个 express
实例 app
,挂载了一个根路由控制器,然后监听3000
端口并启动程序。运行 node index
,打开浏览器访问 localhost:3000
时,页面应显示 hello, express
。
路由
// 对网站首页的访问返回 "Hello World!" 字样
app.get('/', function (req, res) {
res.send('Hello World!');
});
// 网站首页接受 POST 请求
app.post('/', function (req, res) {
res.send('Got a POST request');
});
以上就是基本的express路由使用
express.Router
但是我们也经常看到这样的路由定义
eg:在 创建名为 birds.js 的文件,内容如下:
var express = require('express');
var router = express.Router();
// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});
module.exports = router;
然后index.js为
var birds = require('./birds');
...
app.use('/birds', birds);
那么这两种方式有什么区别呢?
使用第一种方式的路由,每增加一个路由就会在index.js中进行修改增加。这样如果大型项目会有相当多的路由写在index.js中。这当然是不现实的。这时可以使用 express.Router 实现更优雅的路由解决方案。
那为什么index.js不能这么改呢?
var birds = require('./birds');
...
app.get('/birds', birds);
app.get 和app.use有什么区别的?
结论:
app.use(path,callback)中的callback既可以是router对象又可以是函数
app.get(path,callback)中的callback只能是函数
var express = require('express');
var app = express();
var index = require('./routes/index');
//1⃣️
app.use('/test1',function(req,res,next){
res.send('hello test1');
});
//2⃣️
app.get('/test2',function(req,res,next){
res.send('hello test2');
});
//3⃣️
app.get('/test3',index);
//4⃣️
app.use('/test4',index);
index是一个路由对象,结果,例1、2、4结果都能正确显示,而例3却报404。index.js很简单,如下:
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.send('hello world!');
});
module.exports = router;
app.use和app.get的区别
我打印router实例吗,发现也是个函数。那为什么就3不行了呢?回调的router也是个函数但却不能执行呢?
这时候就需要看源码了
express路由源码解析
express路由源码解析
express源码解析
express 源码分析
我们首先来看app.ues为什么可以是router对象。
我们的index.js为
// eg1
var express = require('express');
var app = express();
app.use('/1', require('./a.js'));
app.listen(1111);
// a.js
var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
res.send('hello, cexpresss');
});
module.exports = router;
我们根据express的源码来分析index.js的执行。
var express = require('express');这一句话引用的是express.js
exports = module.exports = createApplication;
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, proto, false); // proto 是application.js
app.init();
return app;
}
所以require('express')
最后返回一个createApplication
函数,var app = express();
的值为createApplication
函数中的app函数。接着分析你写的代码。app.use()
。这个方法就是proto
里的方法所以去查看application.js
。
app.use = function use(fn) {
...判断第一个参数是不是字符串,是的话赋值给path,不是的话path为'/'。fns为fn去除字符串。
// setup router
this.lazyrouter(); // 这是重点
var router = this._router;
fns.forEach(function (fn) {
if (!fn || !fn.handle || !fn.set) { // 如果fn不是express。我们这里分析的fn都不是express
return router.use(path, fn);
}
}, this);
return this;
};
上面这段代码我们先执行this.lazyrouter()
。预算看看lazyrouter
函数是什么
app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
}
};
上面代码的this
为 我们实例化出来的app=express()
的app。所以只有第一次调用这个函数时候才会进入if。才会new Router
。所以我们的this._router
也只有一个。这里的Router
为Router
下的index.js
。
var proto = module.exports = function(options) {
var opts = options || {};
function router(req, res, next) {
router.handle(req, res, next);
}
return router;
};
接下来我们返回到app.use
里继续执行router.use(path, fn)
。我们去Router
下的index.js
看router.use
//添加非路由中间件
proto.use = function use(fn) {
/* 此处略去部分代码 */
callbacks.forEach(function (fn) {
////实例化layer对象并进行初始化
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
//非路由中间件,该字段赋值为undefined
layer.route = undefined;
this.stack.push(layer);
}, this);
return this;
};
上述是Router
添加非路由中间件。接下来我们看看Router
添加路由中间件(就是我们的app.get(),app,post()
之类的)
//添加路由中间件
proto.route = function(path){
//实例化路由对象
var route = new Route(path);
//实例化layer对象并进行初始化
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route; //指向刚实例化的路由对象(非常重要),通过该字段将Router和Route关联来起来
this.stack.push(layer);
return route;
};
app.get
和app.use
主要区别在于layer.route。
接下来我们继续看看这些函数如何执行的。
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
从http.createServer(this)
这句代码可以看出,一旦有请求访问3000这个端口,就会调用this
,this
指的是app
, 程序会执行app()
调用eg1
里的handle
方法,这里就是整个app
的函数入口了。层层跟跟踪代码,发现app.handle
调用了router.handle(req, res, done)
;,这时候我们进入router
的handle
看看里面写了什么。
router
的handle
代码比较长主要实现的逻辑为
每当有请求到来,都会调用router
的handle
方法,首先直接遍历router
的stack
(app.use, app.get
之类的都会被添加到stack
),取出每一个layer看是否有一个与请求的path
匹配,如果没有匹配,调用done
函数结束request
请求,如果有匹配的layer
查看layer
的route
不等于undefined
,就执行layer
的handle_request
函数,如果layer
的route
等于undefined
,那么就执行trim_prefix
函数。我们先来看看layer
的handler_request
是什么,代码如下
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
可以看出这个方法只是单纯的执行layer
的handle
函数,前面已经讲到当layer
的route
不等于undefined
时,他的handle
函数是route.dispach.bind(route),
这个函数会去遍历route
的stack
里面的layer
,然后再调用layer
的handle_request
函数进一步执行回调函数。
trim_prefix
只是执行那些没有route
的layer
的handle
函数。
所以现在最主要的区别就是layer的handle函数。
app.use('/' ,require('./a.js'))
这个layer的handle为就是a.js的输出就是module.exports = router; 就是Router下的index.js
function router(req, res, next) {
router.handle(req, res, next);
}
app.get('/' ,require('./a.js'))
这个前面已经讲到当layer
的route
不等于undefined
时,他的handle
函数是route.dispach.bind(route)
。执行handle
函数时候回发现layer的正则表达式跟当前的url不匹配。layer
的正则只匹配app.get('')
里的路由,那app.use
的正则为什么就可以匹配当前路由呢?
function Layer(path, options, fn) {
var opts = options || {};
this.path = undefined;
this.regexp = pathRegexp(path, this.keys = [], opts);
this.regexp.fast_star = path === '*'
this.regexp.fast_slash = path === '/' && opts.end === false
}
主要是由于第二个参数options的不同。
遇到的问题
使用supervisor index.js
启动node
。更改文件可以自动刷新。但是不能在控制台开启进程后,关掉控制台,再开启。这样可能没有杀死进程,再次启动的话会报错,就是端口被占用的意思。
处理僵尸进程
ps
查看进行。
kill 进程ID
如果kill 进程ID
不行就 kill -9 进程ID
如果已经知道被占用的端口就lsof -i:“端口”
查询进程ID 然后kill掉
以上问题应该supervisor
的问题是换成 nodemon
来监听文件更改。
vscode
中,debug
的时候需要把启动的服务关了,因为debug
就会启动服务。不然debug
的时候会报错端口已经被占用。