nodejs项目实战教程12——Nodejs封装Express

  • 1. Express简介
  • 2. 封装get请求
  • 2.1 模拟express的get方法
  • 2.2 封装get方法
  • 2.3 在web服务器中使用封装的get方法
  • 3. 封装post请求
  • 3.1 使用闭包,避免注册方法变量暴露
  • 3.2 封装post方法
  • 3.3 在web服务器中使用封装的post方法
  • 3.4 扩展res.send方法
  • 4. 封装web静态服务
  • 4.1 封装静态web服务
  • 4.2 在web服务中使用封装的静态服务


1. Express简介

查看express的npmjs文档介绍,简单用法如下:

const express = require('express')
const app = express()
 
app.get('/', function (req, res) {
  res.send('Hello World')
})
 
app.listen(3000)

显然,express是对我们之前路由学习更加高效简便的一种路由封装方式,接下来我们将尝试完成一个简单的express。

2. 封装get请求

2.1 模拟express的get方法

/**
 * 最终目标是以这样的方式配置路由
 * 
 * app.get('/',(req,res)=>{
 *  res.send('hello world!')
 * })
 */

let app = ()=>{
  console.log('调用app方法')
}

app.get = ()=>{
  console.log('get方法')
}

app.post = ()=>{
  console.log('post方法')
}

app()
app.get()

Node.js实战 pdf版下载 node.js 12实战_封装

2.2 封装get方法

/**
 * 最终目标是以这样的方式配置路由
 * 
 * app.get('/',(req,res)=>{
 *  res.send('hello world!')
 * })
 */

let G = {}

 let app = (req,res)=>{
  if(G['/login']){
    // 执行方法
    G['/login'](req,res)
  }
}

app.get = (string,cb)=>{
  // 注册方法
  G[string] = cb

  /**
   * G['/login'] = (req,res)=>{
   *  res.send('hello world!')
   * }
   */
}

app.post = ()=>{
  console.log('post方法')
}

app.get('/login',(req,res)=>{
  console.log('执行了login方法')
})

setTimeout(()=>{
  app('req','res')
},1000)

Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_02


这段封装方法中,使用了一个公共变量G作为存储方法的对象,app.get方法用于往G中注册方法,app方法则是调用G中的方法。

2.3 在web服务器中使用封装的get方法

(1) 进一步封装,创建module/route.js,引入url,并且最终暴露app:

let G = {}

let app = (req,res)=>{
  const {url} = req
  const {host} = req.headers
  const myURL = new URL(url,`http://${host}`)
  pathname = myURL.pathname
  pathname = pathname === '/index.html'?'/':pathname

  if(G[pathname]){
    // 执行方法
    G[pathname](req,res)
  }
  else{
    res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end('当前页面不存在');
  }
}

app.get = (string,cb)=>{
  // 注册方法
  G[string] = cb
}

module.exports = app

(2)在web服务器中使用封装的app模块和get方法:

var http = require('http');
const app = require('./module/route');
http.createServer(app).listen(8080);

app.get('/',(req,res)=>{
  res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end('首页');
})

app.get('/login',(req,res)=>{
  res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end('执行登录操作');
})

app.get('/news',(req,res)=>{
  res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end('新闻页面');
})

console.log('Server running at http://127.0.0.1:8080/');

Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_03


Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_04


Node.js实战 pdf版下载 node.js 12实战_Node.js实战 pdf版下载_05

Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_06

3. 封装post请求

3.1 使用闭包,避免注册方法变量暴露

let server = ()=>{
  let G = {}
  let app = (req,res)=>{
    const {url} = req
    const {host} = req.headers
    const myURL = new URL(url,`http://${host}`)
    pathname = myURL.pathname
    pathname = pathname === '/index.html'?'/':pathname

    if(G[pathname]){
      // 执行方法
      G[pathname](req,res)
    }
    else{
      res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
    res.end('当前页面不存在');
    }
  }

  app.get = (string,cb)=>{
    // 注册方法
    G[string] = cb
  }

  return app
}

module.exports = server()

3.2 封装post方法

let server = ()=>{
  let G = {}
  G._get = {}
  G._post = {}
  let app = (req,res)=>{
    const {url} = req
    const {host} = req.headers
    const myURL = new URL(url,`http://${host}`)
    pathname = myURL.pathname
    pathname = pathname === '/index.html'?'/':pathname

    // 获取请求类型
    let method = req.method.toLowerCase()
    if(G['_'+method][pathname]){
      if(method === 'get'){
        G['_'+method][pathname](req,res)
      }
      else{
        let postData = ''
        req.on('data',(chunk)=>{
          postData += chunk
        })
        req.on('end',()=>{
          res.body = postData
          G['_'+method][pathname](req,res)
        })
      }
    }
    else{
      res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
    res.end('当前页面不存在');
    }
  }

  app.get = (string,cb)=>{
    // 注册方法
    G._get[string] = cb
  }
  app.post = (string,cb)=>{
    // 注册方法
    G._post[string] = cb
  }

  return app
}

module.exports = server()

3.3 在web服务器中使用封装的post方法

(1)在此之前,先引入需要的文件和ejs:

Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_07


(2)在app.js中使用post方法:

const http = require('http');
const app = require('./module/route');
const ejs = require('ejs')

http.createServer(app).listen(8080);

app.get('/',(req,res)=>{
  res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end('首页');
})

app.get('/login',(req,res)=>{
  ejs.renderFile('./views/form.ejs',{},(err,data)=>{
    res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
    res.end(data);
  })
})

app.post('/doLogin',(req,res)=>{
  console.log(res.body)
  res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
  res.end(res.body);
})

console.log('Server running at http://127.0.0.1:8080/');

Node.js实战 pdf版下载 node.js 12实战_node.js_08


Node.js实战 pdf版下载 node.js 12实战_nodejs封装express_09

3.4 扩展res.send方法

在web服务器上我们经常使用到以下两句:

res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
res.end(data);

可以一并在route.js中对res进行扩展:

function changeRes(res){
  res.send = (data)=>{
    res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    res.end(data);
  }
}

Node.js实战 pdf版下载 node.js 12实战_nodejs项目实战教程_10

4. 封装web静态服务

复制demo15的代码到demo16中,并加入需要的资源文件:

(1)文件类型json

(2)css文件

Node.js实战 pdf版下载 node.js 12实战_node.js_11

4.1 封装静态web服务

完整route.js:

const fs = require('fs')
const path = require('path')
// 扩展res
function changeRes(res){
  res.send = (data)=>{
    res.writeHead(200, {'Content-Type': 'text/html;charset="utf-8"'});
    res.end(data);
  }
}

// 根据后缀名获取文件类型
function getFileMine(extname){
  // 同步获取数据
  let data = fs.readFileSync('./data/mime.json')
  let mimeObj = JSON.parse(data.toString())
  // 变量名属性只能通过数组的形式进行访问
  console.log(mimeObj[extname])
  return mimeObj[extname]
}

// 静态web服务的方法
function initStatic(req,res,staticPath){
  const {url} = req
  const {host} = req.headers
  const myURL = new URL(url,`http://${host}`)
  pathname = myURL.pathname
  // 默认加载页面
  pathname = pathname === '/'?'/index.html':pathname
  // 获取文件后缀
  let extname = path.extname(pathname)
  // 2.通过fs模块读取文件
  try {
    let data = fs.readFileSync('./' + staticPath + pathname)
    if(data){
      let mime = getFileMine(extname);
      res.writeHead(200, {'Content-Type': ''+ mime +';charset="utf-8"'});
      res.end(data);
      return true
    }
    return false
  } catch (error) {
    return false
  }
}

let server = ()=>{
  let G = {
    _get: {},
    _post: {},
    staticPath:'static' // 静态web目录
  }
  let app = (req,res)=>{
    // 扩展res方法
    changeRes(res)

    // 配置静态服务
    let flag = initStatic(req,res,G.staticPath)
    console.log('flag',flag)
    if(flag){
      return
    }

    const {url} = req
    const {host} = req.headers
    const myURL = new URL(url,`http://${host}`)
    pathname = myURL.pathname
    pathname = pathname === '/index.html'?'/':pathname

    // 获取请求类型
    let method = req.method.toLowerCase()
    if(G['_'+method][pathname]){
      if(method === 'get'){
        G['_'+method][pathname](req,res)
      }
      else{
        let postData = ''
        req.on('data',(chunk)=>{
          postData += chunk
        })
        req.on('end',()=>{
          res.body = postData
          G['_'+method][pathname](req,res)
        })
      }
    }
    else{
      res.writeHead(404, {'Content-Type': 'text/html;charset="utf-8"'});
      res.end('当前页面不存在');
    }
  }

  // get请求
  app.get = (string,cb)=>{
    // 注册方法
    G._get[string] = cb
  }
  // post请求
  app.post = (string,cb)=>{
    // 注册方法
    G._post[string] = cb
  }
  // 配置静态web服务目录
  app.static = (staticPath)=>{
    G.staticPath = staticPath
  }

  return app
}

module.exports = server()

(1)首先在app中加入配置静态资源目录(即css、js等文件所在目录)的方法:

// 配置静态web服务目录
  app.static = (staticPath)=>{
    G.staticPath = staticPath
  }

之后可以在app.js中调用

(2)加入initStatic方法,给对应的静态资源文件配上对应的响应头:

// 根据后缀名获取文件类型
function getFileMine(extname){
  // 同步获取数据
  let data = fs.readFileSync('./data/mime.json')
  let mimeObj = JSON.parse(data.toString())
  // 变量名属性只能通过数组的形式进行访问
  console.log(mimeObj[extname])
  return mimeObj[extname]
}

// 静态web服务的方法
function initStatic(req,res,staticPath){
  const {url} = req
  const {host} = req.headers
  const myURL = new URL(url,`http://${host}`)
  pathname = myURL.pathname
  // 默认加载页面
  pathname = pathname === '/'?'/index.html':pathname
  // 获取文件后缀
  let extname = path.extname(pathname)
  // 2.通过fs模块读取文件
  try {
    let data = fs.readFileSync('./' + staticPath + pathname)
    if(data){
      let mime = getFileMine(extname);
      res.writeHead(200, {'Content-Type': ''+ mime +';charset="utf-8"'});
      res.end(data);
      return true
    }
    return false
  } catch (error) {
    return false
  }
}

(3)调用时,注意添加对应

// 配置静态服务
    let flag = initStatic(req,res,G.staticPath)
    if(flag){
      return
    }

4.2 在web服务中使用封装的静态服务

(1)app.js中app.static('public')指定静态资源目录位置。完整app.js:

const http = require('http');
const app = require('./module/route');
const ejs = require('ejs')

http.createServer(app).listen(8080);

app.static('public')

app.get('/',(req,res)=>{
  res.send('首页');
})

app.get('/login',(req,res)=>{
  ejs.renderFile('./views/form.ejs',{},(err,data)=>{
    res.send(data);
  })
})

app.post('/doLogin',(req,res)=>{
  console.log(res.body)
  res.send(res.body);
})

console.log('Server running at http://127.0.0.1:8080/');

(2)form.ejs 中 <link rel="stylesheet" href="./css/style.css"> 会被initStatic方法自动解析对应资源。完整form.ejs:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <!-- 这个地址是相对app.js -->
  <link rel="stylesheet" href="./css/style.css">
</head>

<body>
  <h2>登录页面</h2>
  <form action="/doLogin" method="post">
    用户名:<input type="text" name="username">
    <br>
    <br>
    密码:<input type="password" name="password">
    <br>
    <br>
    <input type="submit" value="提交">
  </form>
</body>

</html>

Node.js实战 pdf版下载 node.js 12实战_封装_12