如何理解express框架

语言: CN / TW / HK

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

nodejs web 服务器

```js const http = require('http')

const hostname = '127.0.0.1' const port = 3000

function callback(req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') res.end('Hello World\n') }

const server = http.createServer(callback)

server.listen(port, hostname, () => { console.log(Server running at http://${hostname}:${port}/) }) ```

http 的 createServer() 方法创建新的 HTTP 服务器并返回。每当接收到新请求时,都会调用 request 事件,即执行callback 回调,其提供两个对象:请求(http.IncomingMessage 对象)和响应(http.ServerResponse 对象)。

Node.js 是一个底层平台,为了让开发者的工作变得轻松有趣,社区在 Node.js 上构建了数千个库,比如: express。

express 提供了最简单而强大的方式来创建 Web 服务器,它的极简主义方法、没有偏见、专注于服务器的核心功能,是其成功的关键。

express 的使用

```js import express from 'express'

const app = express()

app.get('/', (req, res, next) => { res.end('hello world') })

app.listen('7001', () => { console.log('listen at 7001') }) ```

下面我们从源码的角度简要的梳理下express的执行过程。

express 源码简要解析

```js var bodyParser = require('body-parser') var EventEmitter = require('events').EventEmitter; var mixin = require('merge-descriptors'); var proto = require('./application'); var Route = require('./router/route'); var Router = require('./router'); var req = require('./request'); var res = require('./response');

/* * Expose createApplication(). / // 暴露出一个createApplication方法 exports = module.exports = createApplication;

/* * Create an express application. 创建一个express的应用 / // 返回一个app,这个app是一个函数,同时在函数下面挂载了很多属性 function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; // 往app上挂载属性
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);

// 以req为原型生成request对象,同时在request挂载app app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } })

// 以res为原型生成response对象,同时在response挂载app app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } })

app.init(); // 返回app return app; }

/* * Expose the prototypes. / // 因为createApplication = exports,所以下面的代码就是在createApplication上挂载一些属性 exports.application = proto; exports.request = req; exports.response = res;

/* * Expose constructors. 往createApplication挂载 路由的构造函数 Route 和 Router /

exports.Route = Route; exports.Router = Router;

/* * Expose middleware 在createApplication挂载一些属性 /

exports.json = bodyParser.json exports.query = require('./middleware/query'); exports.raw = bodyParser.raw exports.static = require('serve-static'); exports.text = bodyParser.text exports.urlencoded = bodyParser.urlencoded

/* * Replace removed middleware with an appropriate error message. / // 下面的这些中间件不在绑定到express上,如果需要则需单独安装,express保持基本的功能即可 var removedMiddlewares = [ 'bodyParser', 'compress', 'cookieSession', 'session', 'logger', 'cookieParser', 'favicon', 'responseTime', 'errorHandler', 'timeout', 'methodOverride', 'vhost', 'csrf', 'directory', 'limit', 'multipart', 'staticCache' ]

removedMiddlewares.forEach(function (name) { Object.defineProperty(exports, name, { get: function () { throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.'); }, configurable: true }); }); ```

下面主要分析核心代码:

```js function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; // 往app上挂载属性
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);

// 以req为原型生成request对象,同时在request挂载app app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } })

// 以res为原型生成response对象,同时在response挂载app app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } })

app.init(); // 返回app return app; } ``` var app = express()

var app = express()执行后返回一个app,这个app其实是一个函数:

js var app = function(req, res, next) { app.handle(req, res, next); }; 在这个函数app下挂载了很多属性:

```js var proto = require('./application');

mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); ``` app到底是什么?

application.js代码如下,就是往app上挂载很多属性:

```js var app = exports = module.exports = {};

app.use = () => {} app.route = () => {} // 在app上把get, post等方法挂载上去 methods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } // 在this上生成一个Router的实例 this.lazyrouter();

var route = this._router.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;

}; });

app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; ```

每当请求过来的时候,就会执行http.createServer里面的回调函数,即app(app是一个函数)。所以,app到底什么呢?app就是一个回调函数,每当发起请求的时候就会执行app,然后把req, res传入,然后执行:

```js var app = function(req, res, next) { app.handle(req, res, next); };

app.handle = function handle(req, res, callback) { var router = this._router; ......
// 在这里就来处理路由的逻辑,并返回给前端 router.handle(req, res, done); }; ```

在项目中我们写的路由,都会被收集起来,然后根据req的method和path来匹配,匹配后就执行对应的逻辑。 ```js app.get('/', (req, res, next) => { res.end('hello world') })

var express = require('express') var router = express.Router() router.get('/list', function (req, res, next) {}) ```

express-generator脚手架

生成express项目

js npm install express-generator -g express express-test npm install && npm start 用脚手架生成的项目是一个全栈的项目,包括服务端和前端页面,目前流行的开发模式是前后端分离,前端开发前端的,后端开发后端的。所以我们后端只要求开发接口,就可以忽略这两个文件。

通过--no-view可以创建不带前端页面的项目: js express express-test --no-view

app.js 详解

```js // 如果找不到页面,就返回404 var createError = require('http-errors') var express = require('express') var path = require('path') var fs = require('fs') // 解析cookie var cookieParser = require('cookie-parser') // 日志模块 var logger = require('morgan') // 路由 const blogRouter = require('./routes/blog') const userRouter = require('./routes/user')

// app就是上面解析的回调函数,但是每次不同的请求,里面的req,res不同 var app = express()

app.use(logger('dev'))

// 下面两行是对post方法传递的数据的处理,处理后的数据放在req.body上 // express.json()只处理json格式的post app.use(express.json()) // 这个处理除了json格式的其他post数据,比如表单数据,表单数据格式就是x-www-form-urlencoded app.use(express.urlencoded({ extended: false })) app.use(cookieParser()) // 返回静态文件,我们这里是api开发,不涉及到前端 // app.use(express.static(path.join(__dirname, 'public')));

// 注册路由 这里的路径和blogRouter里面的路径进行合并,也就是这里的路径是一个父路径 app.use('/api/blog', blogRouter) app.use('/api/user', userRouter)

// 下面是用来捕获错误的代码 // catch 404 and forward to error handler app.use(function (req, res, next) { next(createError(404)) })

// error handler app.use(function (err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message res.locals.error = req.app.get('env') === 'dev' ? err : {}

// render the error page res.status(err.status || 500) res.render('error') })

module.exports = app ```

启动文件 bin/www

```js // 引用app.js导出的app var app = require('../app'); var http = require('http');

var server = http.createServer(app); server.listen(port); ``` package.json

js "scripts": { "start": "node ./bin/www", "dev": "cross-env NODE_ENV=dev nodemon ./bin/www", "prd": "cross-env NODE_ENV=production nodemon ./bin/www" },