阅读 164

Nodejs教程28:Node.js项目之三:实现服务器

阅读更多系列文章请访问我的GitHub博客,示例代码请访问这里

创建服务器

在实现了路由之后,就可以以此为基础实现服务器了。

实现服务器分为以下几个步骤:

示例代码:/lesson28/lib/http.js

  1. 引入所需Node.js模块、服务器配置、路由模块
  2. 封装统一处理请求数据的方法
  3. 接收到的请求分为POST请求、GET请求,区分并进行处理
  4. POST请求分为数据请求、文件上传请求,区分并进行处理
  5. GET请求分为数据请求、读取文件请求,区分并进行处理

接下来,按步骤实现每部分代码。

1. 引入所需Node.js模块、服务器配置、路由模块

// 引入创建服务器所需的模块
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const zlib = require('zlib')
const fs = require('fs')
const { Form } = require('multiparty')

// 引入服务器配置
const {
  HTTP_PORT,
  HTTP_ROOT,
  HTTP_UPLOAD
} = require('../config')

// 引入路由模块的查找路由方法
const { findRouter } = require('./router')

const server = http.createServer((req, res) => {
  // 服务器代码
})

// 监听配置的端口
server.listen(HTTP_PORT)
// 打印创建服务器成功信息
console.log(`Server started at ${HTTP_PORT}`)
复制代码

2. 封装统一处理请求数据的方法

要处理所有请求接口,需要的参数为method(请求方法)、pathname(请求接口路径)、query(query数据)、post(post数据)、files(文件数据)。

首先,根据method(请求方法)、pathname(请求接口路径),获取在路由配置时,已经配置好的相应接口的回调函数。

其次,若回调函数存在,则直接将参数传入回调函数处理。

最后,若回调函数不存在,则默认为请求一个静态文件,即可将文件读取之后发送给前端。

// 引入创建服务器所需的模块
...

// 引入服务器配置
...

// 引入路由模块的查找路由方法
...

const server = http.createServer((req, res) => {
  // 通过路由处理请求数据的公共方法
  async function processData(method, pathname, query, post, files) {
    const callback = findRouter(method, pathname)  // 获取处理请求的回调函数

    // 若回调函数存在,则表示路由有配置相应的数据处理,即该请求不是获取静态文件。
    if (callback) {
      try {
        // 根据路由处理接口数据
        await callback(res, query, post, files)
      } catch (error) {
        // 出现错误的处理
        res.writeHead(500)
        res.write('Internal Server Error')
        res.end()
      }
    } else {
      // 若回调函数不存在,则表示该请求为请求一个静态文件,如html、css、js等
      ...
    }
  }
})

// 监听配置的端口
server.listen(HTTP_PORT)
// 打印创建服务器成功信息
console.log(`Server started at ${HTTP_PORT}`)
复制代码

3. 接收到的请求分为POST请求、GET请求,区分并进行处理

根据请求的method,将请求分为POST请求、GET请求。

若为POST请求,则需要进一步判断是普通数据请求,还是文件请求,并分别进行处理。

而GET请求,只需要将数据传入processData方法进行处理,在processData方法中,区分GET请求获取数据,还是获取静态文件。

// 引入创建服务器所需的模块
...

// 引入服务器配置
...

// 引入路由模块的查找路由方法
...

const server = http.createServer((req, res) => {
  // 解析请求数据
  // 获取请求路径及query数据
  const method = req.method
  const {
    pathname,
    query
  } = url.parse(req.url, true)

  // 处理POST请求
  if (method === 'POST') {
    // POST请求分为数据请求、文件上传请求,区分并进行处理
    ...
  } else {  // 处理GET请求
    // 通过路由处理数据,因为此时是GET请求,只有query数据
    processData(method, url, query, {}, {})
  }

  // 通过路由处理请求数据的公共方法
  async function processData(method, pathname, query, post, files) {
    ...
  }
})

// 监听配置的端口
server.listen(HTTP_PORT)
// 打印创建服务器成功信息
console.log(`Server started at ${HTTP_PORT}`)
复制代码

4. POST请求分为数据请求、文件上传请求,区分并进行处理

判断请求头的content-type为application/x-www-form-urlencoded时,表示该请求只是单纯传输数据,可以直接当做字符串处理。

若请求头的content-type不对,则表示该请求是上传文件,可以用multiparty进行处理。

// 引入创建服务器所需的模块
...

// 引入服务器配置
...

// 引入路由模块的查找路由方法
...

const server = http.createServer((req, res) => {
  // 解析请求数据
  // 获取请求路径及query数据
  const method = req.method
  const {
    pathname,
    query
  } = url.parse(req.url, true)

  // 处理POST请求
  if (method === 'POST') {
    // 根据请求头的content-type属性值,区分是普通POST请求,还是文件请求。
    // content-type为application/x-www-form-urlencoded时,表示是普通POST请求
    // 普通POST请求直接进行处理,文件请求使用multiparty处理
    if (req.headers['content-type'].startsWith('application/x-www-form-urlencoded')) {
      // 普通POST请求
      let arr = []  // 存储Buffer数据

      // 接收数据
      req.on('data', (buffer) => {
        arr.push(buffer)
      })

      // 数据接收完成
      req.on('end', () => {
        const data = Buffer.concat(arr)  // 合并接收到的数据
        const post = querystring.parse(data.toString()) // 将接收到的数据转换为JSON

        // 通过路由处理数据,因为此时是普通POST请求,不存在文件数据
        processData(method, pathname, query, post, {})
      })
    } else {
      // 文件POST请求
      const form = new Form({
        uploadDir: HTTP_UPLOAD  // 指定文件存储目录
      })

      // 处理请求数据
      form.parse(req)

      let post = {} // 存储数据参数
      let files = {}  // 存储文件数据

      // 通过field事件处理普通数据
      form.on('field', (name, value) => {
        post[name] = value
      })

      // 通过file时间处理文件数据
      form.on('file', (name, file) => {
        files[name] = file
      })

      // 处理错误
      form.on('error', (error) => {
        console.error(error)
      })

      // 数据传输完成时,触发close事件
      form.on('close', () => {
        // 通过路由处理数据,因为此时是POST文件请求,query、post、files数据都存在
        processData(method, pathname, query, post, files)
      })
    }
  } else {  // 处理GET请求
    // 通过路由处理数据,因为此时是GET请求,只有query数据
    processData(method, url, query, {}, {})
  }

  // 通过路由处理请求数据的公共方法
  async function processData(method, pathname, query, post, files) {
    ...
  }
})

// 监听配置的端口
server.listen(HTTP_PORT)
// 打印创建服务器成功信息
console.log(`Server started at ${HTTP_PORT}`)
复制代码

5. GET请求分为数据请求、读取文件请求,区分并进行处理

GET请求可以直接用processData方法统一处理,若路由中未配置处理数据的方法,则表示该请求为获取静态文件,需要进行单独处理,否则只需要调用路由配置的回调函数处理即可。

// 引入创建服务器所需的模块
...

// 引入服务器配置
...

// 引入路由模块的查找路由方法
...

const server = http.createServer((req, res) => {
  // 解析请求数据
  // 获取请求路径及query数据
  const method = req.method
  const {
    pathname,
    query
  } = url.parse(req.url, true)

  // 处理POST请求
  if (method === 'POST') {
    ...
  } else {  // 处理GET请求
    // 通过路由处理数据,因为此时是GET请求,只有query数据
    processData(method, url, query, {}, {})
  }

  // 通过路由处理请求数据的公共方法
  async function processData(method, pathname, query, post, files) {
    const callback = findRouter(method, pathname)  // 获取处理请求的回调函数

    // 若回调函数存在,则表示路由有配置相应的数据处理,即该请求不是获取静态文件。
    if (callback) {
      try {
        // 根据路由处理接口数据
        await callback(res, query, post, files)
      } catch (error) {
        // 出现错误的处理
        res.writeHead(500)
        res.write('Internal Server Error')
        res.end()
      }
    } else {
      // 若回调函数不存在,则表示该请求为请求一个静态文件,如html、css、js等
      const filePath = HTTP_ROOT + pathname

      // 检查文件是否存在
      fs.stat(filePath, (error, stat) => {
        if (error) {
          // 出现错误表示文件不存在
          res.writeHead(404)
          res.write('Not Found')
          res.end()
        } else {
          // 文件存在则进行读取
          // 创建一个可读流。
          const readStream = fs.createReadStream(filePath)

          // 创建一个Gzip对象,用于将文件压缩成
          const gz = zlib.createGzip()

          // 向浏览器发送经过gzip压缩的文件,设置响应头,否则浏览器无法识别,会自动进行下载。
          res.setHeader('content-encoding', 'gzip')

          // 将读取的内容,通过gzip压缩之后,在通过管道推送到res中,由于res继承自Stream流,因此也可以接收管道的推送。
          readStream.pipe(gz).pipe(res)

          readStream.on('error', (error) => {
            console.error(error)
          })
        }
      })
    }
  }
})

// 监听配置的端口
server.listen(HTTP_PORT)
// 打印创建服务器成功信息
console.log(`Server started at ${HTTP_PORT}`)
复制代码

测试服务器

在server.js中引入封装的http模块:

const http = require('./lib/http')
复制代码

再使用node server.js启动服务器,就可以在浏览器中访问http://localhost:8080/index.html,看到html页面。

关注下面的标签,发现更多相似文章
评论