Express是一基于Node的一个框架,用来快速建立Web服务的一个工具,为何要使用Express呢,由于建立Web服务若是从Node开始有不少繁琐的工做要作,而Express为你解放了不少工做,从而让你更加关注于逻辑业务开发。举个例子:html
建立一个很简单的网站:node
1. 使用Node来开发:express
var http = require('http'); var url = require("url"); http.createServer(function(req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); var url_str = url.parse(req.url,true); res.end('Hello World\n' + url_str.query); }).listen(8080, "127.0.0.1"); console.log('Server running at http://127.0.0.1:8080/');
这是一个简单的 hello world,运行之后访问http://127.0.0.1会打印相关字符串,这是最普通的页面,但实际上真正的网站要比这个复杂不少,主要有:数组
(1) 多个页面的路由功能app
(2) 对请求的逻辑处理框架
那么使用node原生写法就要进行如下处理函数
// 加载所需模块 var http = require("http"); // 建立Server var app = http.createServer(function(request, response) { if(request.url == '/'){ response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Home Page!\n"); } else if(request.url == '/about'){ response.writeHead(200, { "Content-Type": "text/plain" }); response.end("About Page!\n"); } else{ response.writeHead(404, { "Content-Type": "text/plain" }); response.end("404 Not Found!\n"); } }); // 启动Server app.listen(1984, "localhost");
代码里在createServer函数里传递一个回调函数用来处理http请求并返回结果,在这个函数里有两个工做要作:工具
(1)路由分析,对于不一样的路径须要进行分别处理post
(2)逻辑处理和返回,对某个路径进行特别的逻辑处理学习
在这里会有什么问题?若是一个大型网站拥有海量的网站(也就是路径),每一个网页的处理逻辑也是交错复杂,那这里的写法会很是混乱,无法维护,为了解决这个问题,TJ提出了Connect的概念,把Java里面的中间件概念第一次进入到JS的世界,Web请求将一个一个通过中间件,并经过其中一个中间件返回,大大提升了代码的可维护性和开发效率。
// 引入connect模块 var connect = require("connect"); var http = require("http"); // 创建app var app = connect(); // 添加中间件 app.use(function(request, response) { response.writeHead(200, { "Content-Type": "text/plain" }); response.end("Hello world!\n"); });
// 启动应用 http.createServer(app).listen(1337);
可是TJ认为还应该更好一点,因而Express诞生了,经过Express开发以上的例子:
2. 使用Express来开发:
var express = require('express'); var app = express(); app.get('/', function (req, res) { res.send('Hello World!'); });
app.get('/about', function (req, res) { res.send('About'); });
var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
从Express例子能够看出,使用Express大大减小了代码函数,并且逻辑更为简洁,因此使用Express能够提升开发效率并下降工程维护成本。
首先Express有几个比较重要的概念:路由,中间件和模版引擎
开发人员能够为Web页面注册路由,将不一样的路径请求区分到不一样的模块中去,从而避免了上面例子1所说的海量路径问题,例如
var express = require("express"); var http = require("http"); var app = express(); app.all("*", function(request, response, next) { response.writeHead(404, { "Content-Type": "text/plain" }); next(); }); app.get("/", function(request, response) { response.end("Welcome to the homepage!"); }); app.get("/about", function(request, response) { response.end("Welcome to the about page!"); }); app.get("*", function(request, response) { response.end("404!"); }); http.createServer(app).listen(1337);
开发人员能够为特定的路由开发中间件模块,中间件模块能够复用,从而解决了复杂逻辑的交错引用问题,例如
var express = require('express'); var app = express(); // 没有挂载路径的中间件,应用的每一个请求都会执行该中间件 app.use(function (req, res, next) { console.log('Time:', Date.now()); next(); }); // 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它 app.use('/user/:id', function (req, res, next) { console.log('Request Type:', req.method); next(); }); // 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求 app.get('/user/:id', function (req, res, next) { res.send('USER'); }); var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
同时Express对Request和Response对象进行了加强,添加了不少工具函数。
其中路由和中间件还有不少细节问题,能够参考http://www.expressjs.com.cn/来学习
下面咱们来看看Express的工做原理
咱们首先来看看Express的源码结构:
简单介绍下:
Middleware:中间件
init.js 初始化request,response
query.js 格式化url,将url中的rquest参数剥离, 储存到req.query中
Router:路由相关
index.js: Router类,用于存储中间件数组
layer.js 中间件实体类
route.js route类,用于处理不一样Method
Application.js 对外API
Express.js 入口
Request.js 请求加强
Response.js 返回加强
Utils.js 工具函数
View.js 模版相关
如今看不明白不要紧,能够先看看后面的解释而后再回头看就明白了:
咱们前面有说道路由和中间件,那么咱们就须要有地方来保存这些信息,好比路由信息,好比中间件回调函数等等,express中有一个对象Router对象专门用来存储中间件对象,他有一个数组叫stack,保存了全部的中间件对象,而中间件对象是Layer对象。
Router对象就是router/index.js文件,他的代码是:
Router对象的主要做用就是存储中间件数组,对请求进行处理等等。
Layer对象在router/layer.js文件中,是保存中间件函数信息的对象,主要属性有:
源码见:
这里面的细节先很少考虑,只须要了解关键的信息path,handler和route
handler是保存中间件回调函数的地方,path是路由的url,route是一个指针,指向undefined或者一个route对象,为什么会有两种状况呢,是由于中间件有两种类型:
(1)普通中间件:普通中间件就是不论是什么请求,只要路径匹配就执行回调函数
(2)路由中间件:路由中间件就是区分了HTTP请求的类型,好比get/post/put/head 等等(有几十种)类型的中间件,就是说还有区分请求类型才执行。
因此有两种Layer,一种是普通中间件,保存了name,回调函数已经undefined的route变量。
另一种是路由中间件,除了保存name,回调函数,route还会建立一个route对象:
route对象在router/route.js文件中,
咱们看到route对象有path变量,一个methods对象,也有一个stack数组,stack数组其实保存的也是Layer对象,这个Layer对象保存的是对于不一样HTTP方法的不一样中间件函数(handler变量)。
也许你会问,这个route的数组里面的Layer和上面router的数组里面的Layer有何不一样,他们有一些相同之处也有一些不一样之处,主要是由于他们的做用不一样:
相同之处:他们都是保存中间件的实例对象,当请求匹配到指定的中间件时,该对象实例将会触发。
不一样之处:
Router对象的Layer对象有route变量,若是为undefined表示为普通中间件,若是指向一个route对象表示为路由中间件,没有method对象。而route对象的Layer实例是没有route变量的,有method对象,保存了HTTP请求类型。
因此Router对象中的Layer对象是保存普通中间件的实例或者路由中间件的路由,而route对象中的Layer是保存路由中间件的真正实例。
咱们来看个例子,加入有段设置路由器的代码:
app.use("/index.html",function(){ //此处省略一万行代码});
app.use("/contract.html",function(){ //此处省略一万行代码});
app.get("/index.html",function(){ //此处省略一万行代码});
app.post("/index.html",function(){ //此处省略一万行代码});
app.get("/home.html",function(){ //此处省略一万行代码});
代码中注册了2个普通中间件about.html和contract.html,两个路由中间件,index.html和home.html,对index.html有get和post两种中间件函数,对home.html只有get中间件函数,在内存中存储的形式就是:
咱们上面看到了几种注册中间件的方式,下面就来介绍下路由器的几个动做逻辑:
route对象:
router.METHOD(path,callback);//METHOD是HTTP请求方法(get/post等),他的实现过程在这里:
methods变量是一个数组包含了几十个http请求类型,这段代码给route对象添加了几十个方法,主要逻辑就是建立一个Layer对象,保存中间件函数对象和Method方法,添加到route的stack数组中去。
咱们再来看看Router对象的方法:
proto.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate router.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) { throw new TypeError('Router.use() requires middleware functions'); } for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn)); } // add the middleware debug('use %s %s', path, fn.name || '<anonymous>'); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this; };
这个就是app.use的实现方法,实际上app.use就是调用了router.use,后面详细介绍,先看看这个方法作了什么,当咱们调用app.use(function(){XXX});的时候,这里的函数首先判断了参数类型,看看有没有path传递进来,没有path就是"/"有的话保存到path变量,而后对后面的全部中间件函数进行了如下处理:
建立了一个layer对象,保存了路径,中间件函数而且设置了route变量为undefined,最后把这个变量保存到router的stack数组中去,到此一个普通中间件函数建立完成,为什么要设置route变量为undefined,由于app.use建立的中间件确定是普通中间件,app.METHOD建立的才是路由中间件。
当我面调用app.get("",function(){XXX})的时候调用的实际上是router对象的route方法:
proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; };
route方法也建立了一个layer对象,可是由于自己是路由中间件,因此还会建立一个route对象,而且保存到layer的route变量中去。
如今咱们总结一下:
1. route对象的主要做用是建立一个路由中间件,而且建立多个方法的layer保存到本身的stack数组中去。
2. router对象的主要做用是建立一个普通中间件或者路由中间件的引导着(这个引导着Layer对象连接到一个route对象),而后将其保存到本身的stack数组中去。
因此route对象的stack数组保存的是中间件的方法的信息(get,post等等)而router对象的stack数组保存的是路径的信息(path)
好了,说完了这些基础组件,下面说一下真正暴露给开发者的对外接口,很显然刚才说的都是内部实现细节,咱们开发者一般不须要了解这些细节,只须要使用application提供的对外接口。
application在application.js文件下,主要保存了一些配置信息和配置方法,而后是一些对外操做接口,也就是咱们说的app.use,app.get/post等等,有几个重要的方法:
app.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate app.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires middleware functions'); } // setup router this.lazyrouter(); var router = this._router; fns.forEach(function (fn) { // non-express app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); } debug('.use app under %s', path); fn.mountpath = path; fn.parent = this; // restore .app property on req and res router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { req.__proto__ = orig.request; res.__proto__ = orig.response; next(err); }); }); // mounted an app fn.emit('mount', this); }, this); return this; };
咱们看到app.use在进行了一系列的参数处理后,最终调用的是router的use方法建立一个普通中间件。
methods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; });
同route同样,将全部的http请求的方法建立成函数添加到application对象中去,从而可使用app.get/post/等等,最终的效果是调用router的route方法建立一个路由中间件。
全部的方法再经过express入口文件暴露在对外接口中去。而middleware中的两个文件是对application作的一些初始化操做,request.js和response.js是对请求的两个对象的一些加强。
到此咱们基本了解了express的建立路由和中间件函数的基本原理,下一篇咱们来了解如何调用这些路由和中间件函数。