# 路由

  关于什么是路由,我就不过多的介绍了,在学习 vue 的时候有做过详细的笔记。

  在编写调试 Node.js 项目的时候,如果修改了代码,则需要频繁的手动 close 掉,然后再重启,非常繁琐。

 当前,我们可以使用 nodemon 或 node-dev 解决这一问题:

# 1、安装
npm i nodemon -g

# 2、使用
nodemon <文件名>

提示

  如果不想看后面的升级过程,直接看结果的,可以直接 ✍ 查看路由案例 (opens new window)

# 一、路由示例

思考打印结果:

http
  .createServer((req, res) => {
    console.log(req.url)
    res.end()
  })
  .listen(3000, '127.0.0.1', function () {
    console.log('Server running at http://127.0.0.1:3000')
  })

# 1、简单路由

  • server.js
const http = require('http')
const fs = require('fs')

http
  .createServer((req, res) => {
    const myURL = new URL(req.url, 'http://127.0.0.1')
    switch (myURL.pathname) {
      case '/home':
        res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
        res.write(fs.readFileSync('./static/home.html'), 'utf-8')
        break
      case '/login':
        res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
        res.write(fs.readFileSync('./static/login.html'), 'utf-8')
        break
      default:
        res.writeHead(404, { 'Content-Type': 'text/html;charset=utf8' })
        res.write(fs.readFileSync('./static/404.html'), 'utf-8')
    }
    res.end()
  })
  .listen(3000, '127.0.0.1', function () {
    console.log('Server running at http://127.0.0.1:3000')
  })

# 2、升级(降低耦合度)

  • server.js
const http = require('http')
const route = require('./route')

http
  .createServer((req, res) => {
    const myURL = new URL(req.url, 'http://127.0.0.1')

    route(req, res, myURL.pathname)
    res.end()
  })
  .listen(3000, '127.0.0.1', function () {
    console.log('Server running at http://127.0.0.1:3000')
  })
  • route.js
const fs = require('fs')

function route(req, res, pathname) {
  switch (pathname) {
    case '/home':
      res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
      res.write(fs.readFileSync('./static/home.html'), 'utf-8')
      break
    case '/login':
      res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
      res.write(fs.readFileSync('./static/login.html'), 'utf-8')
      break
    default:
      res.writeHead(404, { 'Content-Type': 'text/html;charset=utf8' })
      res.write(fs.readFileSync('./static/404.html'), 'utf-8')
  }
}

module.exports = route

# 3、升级(switch 方式 改为 对象方式)

  • server.js
const http = require('http')
const route = require('./route')

http
  .createServer((req, res) => {
    const myURL = new URL(req.url, 'http://127.0.0.1')

    // 1、细节一
    // 使用try...catch达到类似switch中default🚩的效果
    try {
      route[myURL.pathname](req, res)
    } catch (error) {
      route['/404'](req, res)
    }
    res.end()
  })
  .listen(3000, '127.0.0.1', function () {
    console.log('Server running at http://127.0.0.1:3000')
  })
  • route.js
const fs = require('fs')

const route = {
  '/home': (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
    res.write(fs.readFileSync('./static/home.html'), 'utf-8')
  },
  '/login': (req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8' })
    res.write(fs.readFileSync('./static/login.html'), 'utf-8')
  },
  '/404': (req, res) => {
    res.writeHead(404, { 'Content-Type': 'text/html;charset=utf8' })
    res.write(fs.readFileSync('./static/404.html'), 'utf-8')
  },
  // 解决图标请求
  '/favicon.ico': (req, res) => {
    // 2、细节二✨
    res.writeHead(200, { 'Content-Type': 'image/x-icon;charset=utf8' })
    res.write(fs.readFileSync('./static/favicon.ico'))
  }
}

module.exports = route

在浏览器上按Ctrl + F5强制刷新,才会显示图标。

# 4、升级(二次解耦、封装思想)

  • server.js
const http = require('http')
const route = require('./route')

http
  .createServer((req, res) => {
    const myURL = new URL(req.url, 'http://127.0.0.1')

    try {
      route[myURL.pathname](req, res)
    } catch (error) {
      route['/404'](req, res)
    }
  })
  .listen(3000, '127.0.0.1', function () {
    console.log('Server running at http://127.0.0.1:3000')
  })
  • route.js
const fs = require('fs')

function render(res, path, type = '') {
  res.writeHead(200, {
    'Content-Type': `${type ? type : 'text/html'};charset=utf8`
  })
  res.write(fs.readFileSync(path), 'utf-8')
  res.end()
}

const route = {
  '/home': (req, res) => {
    render(res, './static/login.html')
  },
  '/login': (req, res) => {
    render(res, './static/login.html')
  },
  '/404': (req, res) => {
    res.writeHead(404, {
      'Content-Type': 'text/html;charset=utf8'
    })
    res.write(fs.readFileSync('./static/404.html'), 'utf-8')
    res.end()
  },
  '/favicon.ico': (res) => {
    render(req, res, './static/favicon.ico', 'image/x-icon')
  }
}

module.exports = route

# 5、升级(封装思想)

  • index.js(启动入口)
const server = require('./server')

server.start()
  • server.js
const http = require('http')
const route = require('./route')

function start() {
  http
    .createServer((req, res) => {
      const myURL = new URL(req.url, 'http://127.0.0.1')

      try {
        route[myURL.pathname](req, res)
      } catch (error) {
        route['/404'](req, res)
      }
    })
    .listen(3000, '127.0.0.1', function () {
      console.log('Server running at http://127.0.0.1:3000')
    })
}

exports.start = start

# 二、api 接口路由示例

  为了降低耦合度,下面 api 部分的代码不会写到 route.js 中,会单独抽离出来 api.js。

普通路由:text/html + static 文件
api 路由:application/json + data 数据

# 1、api 接口

  • api.js
const fs = require('fs')

function render(res, data, type = '') {
  res.writeHead(200, {
    'Content-Type': `${type ? type : 'application/json'};charset=utf8`
  })
  res.write(data)
  res.end()
}

const apiRoute = {
  '/api/login': (req, res) => {
    render(res, `{"ok":1}`)
  }
}

module.exports = apiRoute

# 2、合并路由

使用 ES6 语法(浅拷贝)实现

  • server.js
const http = require('http')

const route = require('./route')
const apiRoute = require('./apiRoute')

// 实现路由合并
const Router = {}
Object.assign(Router, route)
Object.assign(Router, apiRoute)

function start() {
  http
    .createServer((req, res) => {
      const myURL = new URL(req.url, 'http://127.0.0.1')

      try {
        Router[myURL.pathname](req, res)
      } catch (error) {
        Router['/404'](req, res)
      }
    })
    .listen(3000, '127.0.0.1', function () {
      console.log('Server running at http://127.0.0.1:3000')
    })
}

exports.start = start

# 3、升级(降低耦合度)

  • index.js
const server = require('./server')

const route = require('./route')
const api = require('./api')

// 提前注册(合并)路由
server.use(route)
server.use(api)

server.start()
  • server.js
const http = require('http')

const Router = {}

function use(obj) {
  Object.assign(Router, obj)
}

function start() {
  http
    .createServer((req, res) => {
      const myURL = new URL(req.url, 'http://127.0.0.1')

      try {
        Router[myURL.pathname](req, res)
      } catch (error) {
        Router['/404'](req, res)
      }
    })
    .listen(3000, '127.0.0.1', function () {
      console.log('Server running at http://127.0.0.1:3000')
    })
}

exports.start = start
exports.use = use

# 三、路由的简单应用

# 1、数据请求(get、post)

① 前端:

  • index.html
<body>
  <!-- 注册 -->
  <div>
    <div>
      用户名:
      <input id="username" />
    </div>
    <div>
      密码:
      <input type="password" id="password" />
    </div>
    <div>
      <button id="login_get">登录1</button>
      <button id="login_post">登录2</button>
    </div>
  </div>

  <script>
    var username = document.querySelector('#username')
    var password = document.querySelector('#password')
    var login_get = document.querySelector('#login_get')
    var login_post = document.querySelector('#login_post')

    login_get.onclick = () => {
      // 思考:为什么http://127.0.0.1:3000可以省略
      fetch(
        `http://127.0.0.1:3000/api/login_get?username=${username.value}&password=${password.value}`
      )
        .then((res) => res.json())
        .then((res) => {
          console.log(res)
        })
    }

    login_post.onclick = () => {
      fetch('http://127.0.0.1:3000/api/login_post', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        // 字符串化
        body: JSON.stringify({
          username: username.value,
          password: password.value
        })
      })
        // json格式化
        .then((response) => response.json())
        .then((res) => {
          console.log(res)
        })
        .catch((error) => console.log(error))
    }
  </script>
</body>

② 后端:

  • api.js
const fs = require('fs')

function render(res, data, type = '') {
  res.writeHead(200, {
    'Content-Type': `${type ? type : 'application/json'};charset=utf8`
  })
  res.write(data)
  res.end()
}

const apiRoute = {
  '/api/login_get': (req, res) => {
    // render(res, `{"ok":1}`)
    const myURL = new URL(req.url, 'http://127.0.0.1')

    // get🚩方法
    if (
      myURL.searchParams.get('username') === 'lencamo' &&
      myURL.searchParams.get('password') === '123'
    ) {
      render(res, `{"ok":1}`)
    } else {
      render(res, `{"ok":0}`)
    }
  },

  '/api/login_post': (req, res) => {
    // 收集数据
    var post = ''
    req.on('data', (chunk) => {
      post += chunk
    })
    req.on('end', () => {
      console.log(post)
      // 将数据还原为json格式
      post = JSON.parse(post)
      if (post.username === 'lencamo' && post.password === '123') {
        render(res, `{"ok":1}`)
      } else {
        render(res, `{"ok":0}`)
      }
    })
  }
}

module.exports = apiRoute

# 2、不同类型的静态资源访问(升级)

  为了获取 Content-Type 类型,我们可以使用 mime 包来自动识别并获取。

  • route.js
const fs = require('fs')
const path = require('path')
const mime = require('mime')

function render(res, path, type = '') {
  res.writeHead(200, {
    'Content-Type': `${type ? type : 'text/html'};charset=utf8`
  })
  res.write(fs.readFileSync(path), 'utf-8')
  res.end()
}

const route = {
  '/home': (req, res) => {
    render(res, './static/login.html')
  },
  '/': (req, res) => {
    render(res, './static/login.html')
  },
  '/login': (req, res) => {
    render(res, './static/login.html')
  },
  '/404': (req, res) => {
    // 1、🚩静态资源处理
    if (readStaticFile(req, res)) {
      return
    }

    // 2、非静态资源处理
    res.writeHead(404, {
      'Content-Type': 'text/html;charset=utf8'
    })
    res.write(fs.readFileSync('./static/404.html'), 'utf-8')
    res.end()
  }
  // ico的内容也可以同时处理了,不用写
  // '/favicon.ico': (res) => {
  //   render(req, res, './static/favicon.ico', 'image/x-icon')
  // }
}

// 3、静态处理函数
function readStaticFile(req, res) {
  const myURL = new URL(req.url, 'http://127.0.0.1:3000')
  // 使用绝对路径、mime包
  const pathname = path.join(__dirname, '/static/', myURL.pathname)

  // 对pathname路径处理
  if (myURL.pathname === '/') return false

  // 处理静态资源
  if (fs.existsSync(pathname)) {
    // Content-Type类型自动识别(需要扩展名文件)
    console.log(mime.getType(myURL.pathname.split('.')[1]))
    render(res, pathname, mime.getType(myURL.pathname.split('.')[1]))
    return true
  } else {
    return false
  }
}

module.exports = route
更新于 : 8/7/2024, 2:16:31 PM