express基础

express.js是nodejs的框架。它将http-server进行了封装。

1. express基础源码

let http = require('http');
let methods = require('methods');//express依赖的第三方库,包含所有的方法
let url = require('url');
let fs = require('fs');
let path = require('path');

function application() {
  function app(req, res) {// http.createServer(function(){})中的监听函数
    let { pathname } = url.parse(req.url);
    let reqMethod = req.method.toLowerCase();
    let index = 0;
    function next(err) {//如果next传参,表示抛出异常
      // 递归终止条件;即遍历完app.routes仍然调用了next方法
      if (index === app.routes.length) {
        res.statusCode = 404;
        // 任何路由都不匹配
        return res.end(`Cannot ${reqMethod.toUpperCase()} ${pathname}`);
      };

      const layer = app.routes[index++];
      const { path, method, handler } = layer;
      if (err) {
        // 错误捕获中间件
        if (method === 'middleware' && handler.length === 4) {
          return handler(err, req, res)
        } else {
          next();
        }
      } else {
        if (method === 'middleware') {// 中间件 
          // 中间件的路由匹配
          if(path === pathname || path === '/' || path.startsWith(path+'/')) {
            return handler(req,res,next);
          } else {//如果不能匹配中间件的路由
            next();
          }
        } else {
          if(path.params) {// 带参数的路径
            let matches = pathname.match(path);// 取出路径中对应的参数
            if (matches && method === reqMethod) {
              let [, ...args] = matches;
              req.params = path.params.reduce((memo, curr, index) => (memo[curr] = args[index],memo), {});
              return handler(req, res);          
            }
          }
          if((pathname === path || pathname === '*') && 
            (method === reqMethod || method === 'all')) {
            return handler(req,res);
          }  
          next();      
        }  
      }
    }
    next();
  }
  app.routes = [];
  [...methods, 'all'].forEach(item => {
    app[item] = function(path, handler) {
      if(path.includes(':')) {// 路径含有路径参数
        let params = [];
        let str = path.replace(/:([^\/]*)/g, function() {
          params.push(arguments[1]);
          return '([^\/]*)';// 用于匹配用户输入的带参数的路由
        })
        path = new RegExp(str);
        path.params = params;
      }
      const layer = {
        method: item,
        path,
        handler
      }
      app.routes.push(layer);
    }
  })
  // app中使用中间件;
  app.use = function(path, handler) {
    if(typeof handler !== 'function') {// 如果有路由
      handler = path;
      path = '/';
    }
    const layer = {
      path, 
      method: 'middleware',
      handler
    }
    app.routes.push(layer);
  } 
  //内置中间件执行;添加公共属性和方法
  app.use(function(req, res, next) {
    const { query, path } = url.parse(req.url, true);
    req.query = query;
    req.path = path;
    res.send = function(value) {
      if(typeof value === 'object') {//json对象
        res.setHeader('Content-Type', 'application/json;charset=utf-8');
        res.end(JSON.stringify(value));
      } else if(typeof value === 'number') {//校验码
        const status = require('_http_servet').status_CODES;
        res.end(status[value]);
      } else if(typeof value === 'string' || Buffer.isBuffer(value)) {
        res.setHeader('Content-Type', 'text/html;charset=utf-8');
        res.end(value);
      }
    }
    res.sendFile = function(pathname) {
      return fs.createReadStream(pathname).pipe(res);
    }
    next();
  })
  app.listen = function(port) {
    let server = http.createServer(app);
    server.listen(port);
  }
  return app;
}
// 高阶函数
application.static = function(root) {
  return function(req, res, next) {
    let absPath = path.join(root, req.url);
    fs.stat(absPath, function(err, statObj) {
      if(err) {
        return next();
      }
      if (statObj.isDirectory()) {
        absPath = absPath + 'index.html';
      }
      fs.createReadStream(absPath).pipe(res);
    })
  }
}
module.exports = application;

2. express中间件

请求到路径处理之前,在中间执行的内容,就是中间件。
中间件的应用:
1. 可以在中间件中添加权限校验
2. 可以在中间件中添加公共属性和方法
3. 中间件的回调函数中有三个参数(req, res, next), 其中用户可以通过next方法决定是否继续执行

 中间件分为三类:

1. 自定义中间件

 

app.use(path, function(req,res,next) {
    // 向下执行
    next();
})

2. 特殊中间件

1. 静态服务中间件

静态服务中间件用于服务端提供静态资源服务。

const app = express();

const root = path.join(__dirname, './index.html')
// 如果root=__dirname表示提供基于根目录的静态服务
app.use(express.static(root));

2. 内置中间件

内置中间件主要提供了公共属性(req.query, req.path)和公共方法res.send, res.sendFile。

1)公共属性

    const { query, path } = url.parse(req.url, true);
    req.query = query;
    req.path = path;

2)公共方法

   // send方法根据传递的参数类型不同,返回不同的结果
   res.send = function(value) {
      if(typeof value === 'object') {//json对象
        res.setHeader('Content-Type', 'application/json;charset=utf-8');
        res.end(JSON.stringify(value));
      } else if(typeof value === 'number') {//校验码
        const status = require('_http_servet').status_CODES;
        res.end(status[value]);
      } else if(typeof value === 'string' || Buffer.isBuffer(value)) {
        res.setHeader('Content-Type', 'text/html;charset=utf-8');
        res.end(value);
      }
    }
    // sendFile接受一个路径参数,返回路径对应的文件内容
    res.sendFile = function(pathname) {
      return fs.createReadStream(pathname).pipe(res);
    }

3. 错误捕获中间件

next方法传递的参数即抛出的错误信息。通过错误捕获中间件进行捕获。

错误捕获中间件的特点是,回调函数含有4个参数,第一个参数是获取到的error信息。

app.use('/user/', function(req, res, next) {
  const absPath = path.resolve(__dirname, './index.html');
  res.sendFile(absPath);
  next('dddd'); //抛出错误信息;
});
// 由于上面抛出错误,该步不执行
app.get('/read', function(req, res) {
  res.send(req.session); 
});
// 错误捕获中间件;
app.use(function(err, req, res, next) {
  console.log(err); // dddd
});

3. 第三方中间件

1. body-parser

根据请求头的Content-Type获取请求体的数据类型,然后将其解析后的数据赋值到req.body中。

app.use(bodyParser.json());// application/json
// extended=true,表示使用qs库;=false表示使用querystring库
app.use(bodyParser.urlencoded({extended: true}));// application/x-www-form-urlencoded

2. cookie-parser

1) 接受一个参数,该参数是签名cookie的密钥。并且可以通过req.secret获取该密钥。

  参数存在时express中的API中res.cookie(key, value, options)中options中设置signed: true生效,

  写入客户端的cookie是签名后的cookie。

2)解析cookie, 通过req.cookies可以获取所有的非签名的cookie。

    req.signedCookies获取到的是签名的cookie

app.use(cookieParser('lyra'));

3. express-session

1)向客户端写入sessionId对应的cookie, session的数据存在服务器端。

可以通过服务器端设置req.session[key] = value进行数据添加和读取。

2)接受一个对象,用于设置参数。

其中secret对应的参数,如果使用可cookie-parser,则需要保持两者一致。

app.use(session({ // 会向客户端发送一个key为connect.id, value为唯一标识的cookie。
  resave: true,
  saveUninitialized: true,
  cookie: {httpOnly: true}, //设置connect.id对应的cookie的属性
  secret: 'lyra' //加密和cookieParser的密钥一致
}));

3. 示例

let express = require('express');
let path = require('path');
let bodyParser = require('body-parser');
let cookieParser = require('cookie-parser');
let session = require('express-session');


let app = express(); // app本质是监听函数
/**
 * 自定义中间件: 
 * 1. 可以在中间件中进行权限校验
 * 2. 可以在中间件中添加公共方法和属性
 * 3. 中间件的回调函数有next方法,可以由用户调用与否,决定是否向下执行
 * 特殊中间件:
 * 1. 错误捕获中间件: next方法传递的参数视为抛出的错误信息
 * 2. 内置中间件: 添加公共属性和方法(path, query, send, sendFile)
 * 3. 静态服务中间件: 启动静态服务(高阶函数)
 * 第三方中间件:
 * 1. body-parser: 解析请求体req.body(json/x-www-form-urlencoded)
 * 2. cookie-parser: 1)解析cookie, 赋值req.cookies返回未签名cookie 
 *                     req.signedCookies返回签过名的cookie;示例: {name1: lyra}
 *                   2)支持cookie签名,使得express的res.cookie的signed参数生效
 *                     cookieParser传递的参数是密钥,通过req.secret获取
 * 3. express-session: session数据存储在服务端,客户端存储的是sessionId
 *                     1)在req上提供一个session对象;session 
 *                     2)提供一个密钥
 */
// 静态服务中间件
app.use(express.static(__dirname));
app.use(function(req, res, next) {
  next();
});
app.use(bodyParser.json());// application/json
// extended=true,表示使用qs库;=false表示使用querystring库
app.use(bodyParser.urlencoded({extended: true}));// application/x-www-form-urlencoded

app.use(cookieParser('lyra'));

app.use(session({ // 会向客户端发送一个key为connect.id, value为唯一标识的cookie。
  resave: true,
  saveUninitialized: true,
  secret: 'lyra' //加密和cookieParser的密钥一致
}));
app.use('/user/', function(req, res, next) {
  const absPath = path.resolve(__dirname, './index.html');
  res.sendFile(absPath);
  next('dddd');
});
app.get('/read', function(req, res) {
  res.send(req.session);
});
app.get('/write', function(req, res) {
  // 手动设置session
  req.session.a = 'hello';
  //express设置cookie;时间为ms
  res.cookie('name', 'lyra', {maxAge: 20000, signed: false});
  res.cookie('name1', 'lyra', {maxAge: 20000, signed: true});
  res.end();
});
app.get('/user/:name/:id', function(req, res) {
  // console.log(req.params);
});
app.post('/post', function(req, res) {
  res.send(req.body);
});
app.use(function(err, req, res, next) {
  console.log(err);
});


app.listen(3000);

 

posted @ 2020-02-05 16:41  Lyra李  阅读(182)  评论(0编辑  收藏  举报