Nodejs教程28:Node.js項目之三:實現服務器

閱讀更多系列文章請訪問個人GitHub博客,示例代碼請訪問這裏

建立服務器

在實現了路由以後,就能夠以此爲基礎實現服務器了。css

實現服務器分爲如下幾個步驟:html

示例代碼:/lesson28/lib/http.js前端

  1. 引入所需Node.js模塊、服務器配置、路由模塊
  2. 封裝統一處理請求數據的方法
  3. 接收到的請求分爲POST請求、GET請求,區分並進行處理
  4. POST請求分爲數據請求、文件上傳請求,區分並進行處理
  5. GET請求分爲數據請求、讀取文件請求,區分並進行處理

接下來,按步驟實現每部分代碼。node

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(文件數據)。git

首先,根據method(請求方法)、pathname(請求接口路徑),獲取在路由配置時,已經配置好的相應接口的回調函數。github

其次,若回調函數存在,則直接將參數傳入回調函數處理。瀏覽器

最後,若回調函數不存在,則默認爲請求一個靜態文件,便可將文件讀取以後發送給前端。bash

// 引入建立服務器所需的模塊
...

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

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

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請求,則須要進一步判斷是普通數據請求,仍是文件請求,並分別進行處理。app

而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頁面。

相關文章
相關標籤/搜索