在實現了路由以後,就能夠以此爲基礎實現服務器了。css
實現服務器分爲如下幾個步驟:html
示例代碼:/lesson28/lib/http.js前端
接下來,按步驟實現每部分代碼。node
// 引入建立服務器所需的模塊
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}`)
複製代碼
要處理全部請求接口,須要的參數爲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}`)
複製代碼
根據請求的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}`)
複製代碼
判斷請求頭的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}`)
複製代碼
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頁面。