Express

Express 是一个简洁、灵活的 node.js Web 应用开发框架, 它提供一系列强大的特性,帮助你创建各种 Web 和移动设备应用。丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件,让你创建健壮、友好的 API 变得既快速又简单。此文章会讲解express常用方法以及实现的原理.

1.Express介绍

Express 是一个小巧且灵活的 Node.js Web应用框架,它有一套健壮的特性,可用于开发单页、多页和混合Web应用。

2.express的应用

2.1 安装express

  • npm安装
    1
    $ npm install express

2.2 使用express

  • 创建http服务
    1
    2
    3
    4
    5
    6
    //引入express
    var express = require('express');
    //执行express**函数
    var app = express();
    //监听端口
    app.listen(3000);

2.3 express的get方法

  • 根据请求路径来处理客户端发出的GET请求
  • 第一个参数path为请求的路径
  • 第二个参数为处理请求的回调函数
    1
    app.get(path,function(req, res));

2.3.1 get方法的使用

  • get方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //引入express
    var express = require('./express');
    //执行express函数
    var app = express();
    //监听端口
    app.get('/hello', function (req,res) {
    res.end('hello');
    });
    app.get('/world', function (req,res) {
    res.end('world');
    });
    app.get('*', function (req,res) {
    res.setHeader('content-type','text/plain;charset=utf8');
    res.end('没有找到匹配的路径');
    });
    app.listen(3000);

2.3.2 get方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//声明express函数
var express = function () {
var app = function (req,res) {
var urlObj = require('url').parse(req.url,true);
var pathname = urlObj.pathname;
var method = req.method.toLowerCase();
//找到匹配的路由
var route = app.routes.find(function (item) {
return item.path==pathname&&item.method==method;
});
if(route){
route.fn(req,res);
}
res.end(`CANNOT ${method} ${pathname}`)
};
//增加监听方法
app.listen = function (port) {
require('http').createServer(app).listen(port);
};
app.routes = [];
//增加get方法
app.get = function (path,fn) {
app.routes.push({method:'get',path:path,fn:fn});
};
return app;
};
module.exports = express;

2.3.3 增加 * 匹配

  • 使用 * 匹配所有路径
    1
    2
    3
    4
    var route = app.routes.find(function (item) {
    - return item.path==pathname&&item.method==method;
    + return (item.path==pathname||item.path=='*')&&item.method==method;
    });

2.4 express的post方法

  • 根据请求路径来处理客户端发出的POST请求
  • 第一个参数path为请求的路径
  • 第二个参数为处理请求的回调函数
    1
    app.post(path,function(req,res));

2.4.1 post的方法使用

  • post方法的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //引入express
    var express = require('./express');
    //执行express函数
    var app = express();
    //监听端口
    app.post('/hello', function (req,res) {
    res.end('hello');
    });
    app.post('*', function (req,res) {
    res.end('post没找到');
    });
    app.listen(3000);
  • 通过linux命令发送post请求

    1
    $ curl -X POST http://localhost:3000/hello

2.4.2 post的实现

  • 增加所有请求的方法
    1
    2
    3
    4
    5
    6
    var methods = ['get','post','delete','put','options'];
    methods.forEach(function (method) {
    app[method] = function (path,fn) {
    app.routes.push({method:method,path:path,fn:fn});
    };
    });

2.5 express的all方法

  • 监听所有的请求方法,可以匹配所有的HTTP动词
  • 根据请求路径来处理客户端发出的所有请求
  • 第一个参数path为请求的路径
  • 第二个参数为处理请求的回调函数
    1
    app.all(path,function(req, res));

2.5.1 all的方法使用

  • all方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var express = require('./express');
    var app = express();
    app.all('/hello', function (req,res) {
    res.end('hello');
    });
    app.all('*', function (req,res) {
    res.end('没找到');
    });
    app.listen(3000);

2.5.2 注册所有方法

  • 增加all方法匹配所有method
    1
    + var methods = ['get','post','delete','put','options','all'];

2.5.3 all方法的实现

  • 对all方法进行判断
    1
    2
    3
    4
    var route = app.routes.find(function (item) {
    - return (item.path==pathname||item.path=='*')&&item.method==method;
    + return (item.path==pathname||item.path=='*')&&(item.method==method||method=='all');
    });

2.6 中间件

中间件就是处理HTTP请求的函数,用来完成各种特定的任务,比如检查用户是否登录、检测用户是否有权限访问等,它的特点是:

  • 一个中间件处理完请求和响应可以把相应数据再传递给下一个中间件
  • 回调函数的next参数,表示接受其他中间件的调用,函数体中的next(),表示将请求数据继续传递
  • 可以根据路径来区分返回执行不同的中间件

2.6.1 中间件的使用方法

  • 增加中间件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var express = require('express');
    var app = express();
    app.use(function (req,res,next) {
    console.log('过滤石头');
    next();
    });
    app.use('/water', function (req,res,next) {
    console.log('过滤沙子');
    next();
    });
    app.get('/water', function (req,res) {
    res.end('water');
    });
    app.listen(3000);

2.6.2 use方法的实现

  • 在路由数组中增加中间件

    1
    2
    3
    4
    5
    6
    7
    app.use = function (path,fn) {
    if(typeof fn !='function'){
    fn = path;
    path = '/';
    }
    app.routes.push({method:'middle',path:path,fn:fn});
    }
  • app方法中增加Middleware判断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    - var route = app.routes.find(function (item) {
    - return item.path==pathname&&item.method==method;
    - });
    - if(route){
    - route.fn(req,res);
    - }
    var index = 0;
    function next(){
    if(index>=app.routes.length){
    return res.end(`CANNOT ${method} ${pathname}`);
    }
    var route = app.routes[index++];
    if(route.method == 'middle'){
    if(route.path == '/'||pathname.startsWith(route.path+'/')|| pathname==route.path){
    route.fn(req,res,next)
    }else{
    next();
    }
    }else{
    if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){
    route.fn(req,res);
    }else{
    next();
    }
    }
    }
    next();

2.6.3 错误中间件

  • next中可以传递错误,默认执行错误中间件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var express = require('express');
    var app = express();
    app.use(function (req,res,next) {
    console.log('过滤石头');
    next('stone is too big');
    });
    app.use('/water', function (req,res,next) {
    console.log('过滤沙子');
    next();
    });
    app.get('/water', function (req,res) {
    res.end('water');
    });
    app.use(function (err,req,res,next) {
    console.log(err);
    res.end(err);
    });
    app.listen(3000);

2.6.4 错误中间件的实现

  • 对错误中间件进行处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function next(err){
    if(index>=app.routes.length){
    return res.end(`CANNOT ${method} ${pathname}`);
    }
    var route = app.routes[index++];
    + if(err){
    + if(route.method == 'middle'&&route.fn.length==4){
    + route.fn(err,req,res,next);
    + }else{
    + next(err);
    + }
    + }else{
    if(route.method == 'middle'){
    if(route.path == '/'||pathname.startsWith(route.path+'/')|| pathname==route.path){
    route.fn(req,res,next)
    }else{
    next();
    }
    }else{
    if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){
    route.fn(req,res);
    }else{
    next();
    }
    }
    + }
    }

2.7 获取参数和查询字符串

  • req.hostname 返回请求头里取的主机名
  • req.path 返回请求的URL的路径名
  • req.query 查询字符串
    1
    2
    3
    4
    5
    //http://localhost:3000/?a=1
    app.get('/',function(req,res){
    res.write(JSON.stringify(req.query))
    res.end(req.hostname+" "+req.path);
    });

2.7.1 具体实现

  • 对请求增加方法
    1
    2
    3
    + req.path = pathname;
    + req.hostname = req.headers['host'].split(':')[0];
    + req.query = urlObj.query;

2.8 获取params参数

  • req.params 匹配到的所有路径参数组成的对象
    1
    2
    3
    4
    app.get('/water/:id/:name/home/:age', function (req,res) {
    console.log(req.params);
    res.end('water');
    });

2.8.1 params实现

  • 增加params属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    methods.forEach(function (method) {
    app[method] = function (path,fn) {
    var config = {method:method,path:path,fn:fn};
    if(path.includes(":")){
    //是路径参数 转换为正则
    //并且增加params
    var arr = [];
    config.path = path.replace(/:([^\/]+)/g, function () {
    arr.push(arguments[1]);
    return '([^\/]+)';
    });
    config.params = arr;
    }
    app.routes.push(config);
    };
    });
    + if(route.params){
    + var matchers = pathname.match(new RegExp(route.path));
    + if(matchers){
    + var params = {};
    + for(var i = 0; i<route.params.length;i++){
    + params[route.params[i]] = matchers[i+1];
    + }
    + req.params = params;
    + route.fn(req,res);
    + }else{
    + next();
    + }
    +}else{
    if((route.path==pathname||route.path=='*')&&(route.method==method||route.method=='all')){
    route.fn(req,res);
    }else{
    next();
    }
    +}
    //如果不等于就是路由
    if(item.params){
    //带params
    var arr = pathname.match(new RegExp(item.path));
    if(arr){
    var obj = {};
    for(var i = 1; i<=item.params.length;i++){
    obj[item.params[i-1]] = arr[i];
    }
    req.params = obj;
    item.fn(req,res);
    }else{
    next();
    }
    }else{
    if(item.path==pathname||item.path=='*'&&item.method ==method){
    item.fn(req,res);
    }else{
    next();
    }
    }

2.9 express中的send方法

  • 参数为要响应的内容,可以智能处理不同类型的数据,在输出响应时会自动进行一些设置,比如HEAD信息、HTTP缓存支持等等

    1
    res.send([body]);
  • 当参数是一个字符串时,这个方法会设置Content-type为text/html

    1
    2
    3
    app.get('/', function (req,res) {
    res.send('<p>hello world</p>');
    });
  • 当参数是一个Array或者Object,这个方法返回json格式

    1
    2
    3
    4
    5
    6
    app.get('/json', function (req,res) {
    res.send({obj:1});
    });
    app.get('/arr', function (req,res) {
    res.send([1,2,3]);
    });
  • 当参数是一个number类型,这个方法返回对应的状态码短语

    1
    2
    3
    4
    app.get('/status', function (req,res) {
    res.send(404); //not found
    //res.status(404).send('没有找到');设置短语
    });

2.9.1 send方法的实现

  • 自定义send方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    res.send = function (msg) {
    if(typeof msg=='string'||Buffer.isBuffer(msg)){
    res.contentType('text/html');
    res.end(msg);
    }else if(typeof msg=='object'){
    res.contentType('application/json');
    res.end(JSON.stringify(msg));
    }else if(typeof msg == 'number'){
    res.contentType('text/plain');
    var status_code = require('_http_server').STATUS_CODES
    res.end(status_code[msg]);
    }
    };

3. 模板的应用

3.1 安装ejs

  • npm安装ejs
    1
    $ npm install ejs

3.2 设置模板

  • 使用ejs模版
    1
    2
    3
    4
    5
    6
    var express = require('express');
    var path = require('path');
    var app = express();
    app.set('view engine','ejs');
    app.set('views',path.join(__dirname,'views'));
    app.listen(3000);

3.3 渲染html

  • 配置成html格式
    1
    2
    3
    app.set('view engine','html')
    app.set('views',path.join(__dirname,'views'));
    app.engine('html',require('ejs').__express);

3.4 渲染视图

  • 第一个参数 要渲染的模板
  • 第二个参数 渲染所需要的数据
    1
    2
    3
    app.get('/', function (req,res) {
    res.render('hello',{title:'hello'},function(err,data){});
    });

3.5 模板的实现

  • 读取模版渲染
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    res.render = function (tmpl,obj,fn) {
    tmpl = tmpl+(tmpl.endsWith('.ejs')?'':'.ejs');
    var filepath = path.join(app.get('views'),tmpl);
    fs.readFile(filepath,'utf8', function (err,data) {
    data = data.replace(/<%=(\w+)%>/g,function () {
    return obj[arguments[1]];
    });
    if(typeof fn =='function'){
    fn(err,data);
    }else{
    res.end(data);
    }
    })
    };

4.静态文件服务器

  • 如果要在网页中加载静态文件(css、js、img),就需要另外指定一个存放静态文件的目录,当浏览器发出非HTML文件请求时,服务器端就会到这个目录下去寻找相关文件
    1
    2
    3
    4
    5
    var express = require('express');
    var app = express();
    var path = require('path');
    app.use(express.static(path.join(__dirname,'public')));
    app.listen(3000);

4.1 静态文件服务器实现

  • 配置静态服务器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function static(p){
    return function (req,res,next) {
    var exists = fs.existsSync(p);
    if(exists){
    fs.createReadStream(path.join(p,req.path)).pipe(res);
    }else{
    next();
    }
    }
    }

5.重定向

  • redirect方法允许网址的重定向,跳转到指定的url并且可以指定status,默认为302方式。
  • 参数1 状态码(可选)
  • 参数2 跳转的路径
    1
    res.redirect([status], url);

5.1 redirect使用

  • 使用重定向
    1
    2
    3
    app.get('/', function (req,res) {
    res.redirect('http://www.baidu.com')
    });

5.2 redirect的实现

  • 302重定向
    1
    2
    3
    4
    5
    6
    7
    8
    9
    app.use(function (req,res,next) {
    res.redirect = function (url) {
    console.log(url);
    res.statusCode = 302;
    res.setHeader('Location','http://www.baidu.com');
    res.end('');
    };
    next();
    });

6. 接收post响应体

  • 安装body-parser
    1
    $ npm install body-parser

6.1 使用body-parser

  • 接收请求体中的数据
    1
    2
    3
    4
    5
    6
    7
    8
    app.get('/login', function (req,res) {
    res.sendFile('./login.html',{root:__dirname})
    });
    app.post('/user', function (req,res) {
    console.log(req.body);
    res.send(req.body);
    });
    app.listen(3000);

6.2 req.body的实现

  • 实现bodyParser
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function bodyParser () {
    return function (req,res,next) {
    var result = '';
    req.on('data', function (data) {
    result+=data;
    });
    req.on('end', function () {
    try{
    req.body = JSON.parse(result);
    }catch(e){
    req.body = require('querystring').parse(result);
    }
    next();
    })
    }
    };