Express是目前比较流行的基于Nodejs的web开发框架之一,该框架建立在Nodejs的http(s)模块之上。

// Node.js官网示例
javascript
var http = require('http');

var app = http.createServer(function(req, res){
    res.writeHead(200,{"Content-type":"text/plain"});
    res.end('hello world')
});

app.listen(3000,'lcoalhost')

上面代码使用 http.createServer 方法创建了一个HTTP web服务实例。该方法接受一个回调函数,回调函数的参数分别为HTTP request和HTTP response对象。

使用中间件

Express是一个完全由路由和中间件构成的web开发框架,Express应用程序基本上是一系列中间件函数调用。

中间件(middleware)函数能够访问请求对象 (req)、响应对象 (res) 以及应用程序的请求/响应循环中的下一个中间件函数。下一个中间件函数通常由名为next 的变量来表示。

中间件函数可以执行以下任务:

  • 执行任何代码。
  • 对请求和响应对象进行更改。
  • 结束请求/响应循环。
  • 调用堆栈中的下一个中间件函数。

如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。

Express 应用程序可以使用以下类型的中间件:

  • 应用层中间件
  • 路由器层中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件

您可以使用可选挂载路径来使用应用层和路由器层中间件。 还可以将一系列中间件函数一起载入使用,这样会在安装点创建中间件系统的子堆栈。

应用层中间件

使用 app.use() 和 app.METHOD() 函数将应用层中间件绑定到应用程序对象的实例,其中 METHOD 是中间件函数处理的请求的小写 HTTP 方法(例如 GET、PUT 或 POST)。

没有挂载路径的中间件函数。应用程序每次收到请求时执行该函数。

var app = express();

app.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

挂载在 /user/:id 路径中的中间件函数。在 /user/:id 路径中为任何类型的 HTTP 请求执行此函数。

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');
});

中间件子堆栈,用于显示针对 /user/:id 路径的任何类型 HTTP 请求的信息

app.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

路由处理程序使您可以为一个路径定义多个路由。

以下示例为针对 /user/:id 路径的 GET 请求定义两个路由。第二个路由不会导致任何问题,但是永远都不会被调用,因为第一个路由结束了请求/响应循环。

此示例显示一个中间件子堆栈,用于处理针对 /user/:id 路径的 GET 请求。

app.get('/user/:id', function (req, res, next) {
  console.log('ID:', req.params.id);
  next();
}, function (req, res, next) {
  res.send('User Info');
});

// handler for the /user/:id path, which prints the user ID
app.get('/user/:id', function (req, res, next) {
  res.end(req.params.id);
});

要跳过路由器中间件堆栈中剩余的中间件函数,请调用 next(‘route’) 将控制权传递给下一个路由。 注:next(‘route’) 仅在使用 app.METHOD() 或 router.METHOD() 函数装入的中间件函数中有效。

中间件子堆栈,用于处理针对 /user/:id 路径的 GET 请求。

app.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next route
  if (req.params.id == 0) next('route');
  // otherwise pass the control to the next middleware function in this stack
  else next(); //
}, function (req, res, next) {
  // render a regular page
  res.render('regular');
});

// handler for the /user/:id path, which renders a special page
app.get('/user/:id', function (req, res, next) {
  res.render('special');
});

路由器层中间件

路由器层中间件的工作方式与应用层中间件基本相同,差异之处在于它绑定到 express.Router() 的实例。

var router = express.Router();

使用 router.use() 和 router.METHOD() 函数装入路由器层中间件。

var app = express();
var router = express.Router();

// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
  console.log('Time:', Date.now());
  next();
});

// a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
router.use('/user/:id', function(req, res, next) {
  console.log('Request URL:', req.originalUrl);
  next();
}, function (req, res, next) {
  console.log('Request Type:', req.method);
  next();
});

// a middleware sub-stack that handles GET requests to the /user/:id path
router.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next router
  if (req.params.id == 0) next('route');
  // otherwise pass control to the next middleware function in this stack
  else next(); //
}, function (req, res, next) {
  // render a regular page
  res.render('regular');
});

// handler for the /user/:id path, which renders a special page
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id);
  res.render('special');
});

// mount the router on the app
app.use('/', router);

错误处理中间件

错误处理中间件有四个参数且必须提供四个参数,以将函数标识为错误处理中间件函数。即使不需使用 next 对象,也必须在参数中声明。否则,该中间件将被识别为常规中间件,从而无法处理错误。

错误处理中间件函数的定义方式与其他中间件函数基本相同,差别在于错误处理函数有四个自变量而不是三个,专门具有特征符 (err, req, res, next):

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

内置中间件

自 V4.x 起,Express 不再依赖于 Connect。除 express.static 外,先前 Express 随附的所有中间件函数现在以单独模块的形式提供。请查看中间件函数的列表。

express.static(root, [options])
Express 中唯一内置的中间件函数是 express.static。此函数基于 serve-static,负责提供 Express 应用程序的静态资源。

root 自变量指定从其中提供静态资源的根目录。

可选的 options 对象可以具有以下属性:

属性 描述 类型 缺省值
dotfiles 是否对外输出文件名以点(.)开头的文件。有效值包括“allow”、“deny”和“ignore” 字符串 “ignore”
etag 启用或禁用 etag 生成 布尔 true
extensions 用于设置后备文件扩展名。 数组 []
index 发送目录索引文件。设置为 false 可禁用建立目录索引。 混合 “index.html”
lastModified 将 Last-Modified 的头设置为操作系统上该文件的上次修改日期。有效值包括 true 或 false。 布尔 true
maxAge 设置 Cache-Control 头的 max-age 属性(以毫秒或者 ms 格式中的字符串为单位) 数字 0
redirect 当路径名是目录时重定向到结尾的“/”。 布尔 true
setHeaders 用于设置随文件一起提供的 HTTP 头的函数。 函数

以下示例使用了 express.static 中间件,并且提供了一个详细的’options’对象(作为示例):

var options = {
  dotfiles: 'ignore',
  etag: false,
  extensions: ['htm', 'html'],
  index: false,
  maxAge: '1d',
  redirect: false,
  setHeaders: function (res, path, stat) {
    res.set('x-timestamp', Date.now());
  }
}

app.use(express.static('public', options));

对于每个应用程序,可以有多个静态目录:

app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));

有关 serve-static 函数及其选项的更多详细信息,请参阅:serve-static 文档。

第三方中间件

使用第三方中间件向 Express 应用程序添加功能。

安装具有所需功能的 Node.js 模块,然后在应用层或路由器层的应用程序中将其加装入。

以下示例演示如何安装和装入 cookie 解析中间件函数 cookie-parser。

$ npm install cookie-parser
var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');

// load the cookie-parsing middleware
app.use(cookieParser());

有关 Express 常用的第三方中间件函数的部分列表,请参阅:第三方中间件

中间件原理

中间件是什么

中间件是一个函数,在响应发送之前对请求进行一些操作

function middleware(req, res, next){
  // todo something

  next();
}

简单实现

var http = require('http');

function express() {
  var stack = [];

  var app = function (req, res) {
    var i = 0;

    function next() {
      var task = stack[i++];
      if (!task) {
        return;
      }
      task(req, res, next);
    }
    next();
  }

  app.use = function (task) {
    stack.push(task);
  }

  return app;
}

var app = express();
http.createServer(app).listen('3000', function () {
    console.log('listening 3000....');
});

function middlewareA(req, res, next) {
  console.log('middlewareA before next()');
  next();
  console.log('middlewareA after next()');
}

function middlewareB(req, res, next) {
  console.log('middlewareB before next()');
  next();
  console.log('middlewareB after next()');
}

function middlewareC(req, res, next) {
  console.log('middlewareC before next()');
  next();
  console.log('middlewareC after next()');

  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}

app.use(middlewareA);
app.use(middlewareB);
app.use(middlewareC);

// console.log result:
// middlewareA before next()
// middlewareB before next()
// middlewareC before next()
// middlewareC after next()
// middlewareB after next()
// middlewareA after next()