用 Node.js 能夠快速搭建一個 http 服務器, 本文將會手把手從頭搭建, 最後還會實現一個簡易的 express 服務器, 開始吧~
javascript
首先, 搭建 http 服務器, 須要先引用 Node.js 的核心模塊: http, 實際上這是一個對象, 利用 http 上的 createServer 方法來建立一個 http 服務器, createServer 方法須要接受一個事件函數, 用於處理請求和響應html
const http = require('http')
const server = http.createServer((req, res) => {
// req 是請求對象, 能夠獲取請求的一些方法路徑數據等屬性
// res 是響應對象, 能夠進行設置響應數據等操做
})
複製代碼
到如今, 這個 http 服務器的雛形已經基本搭建好了!
可是想一想還差點東西, 一個 url 裏包含 協議 域名 端口, 咱們還沒指定端口呢.前端
const http = require('http')
const server = http.createServer((req, res) => {
})
server.listen(8000) // 監聽 8000 端口
複製代碼
OK, 大功告成
java
如今這個 http 服務器的框架已經搭好了. 啓動一下, 在瀏覽器輸入localhost:8000
試一下吧
什麼? 你說你試了一下, 沒有響應?
固然, 咱們這裏尚未返回任何數據呢, 若是沒有訪問數據, 瀏覽器端確定是顯示無響應的.
git
這裏咱們先隨便返回點數據給瀏覽器, 而後重啓服務器github
const http = require('http')
const server = http.createServer((req, res) => {
res.end('這是我返回的數據噢!')
})
server.listen(8000)
複製代碼
相信你已經看到頁面上顯示的....一堆亂碼了吧^_^, 是的, 由於 JavaScript 默認字符集對中文的支持很差. 咱們須要指定一下返回數據的 Content-Type
express
const http = require('http')
const server = http.createServer((req, res) => {
res.setHeader("Content-Type","text/html;charset=utf-8")
res.end('這是我返回的數據噢!')
})
server.listen(8000)
複製代碼
接下來, 咱們須要對路由進行處理, 如今咱們無論訪問什麼路徑, 都統一返回同樣的數據.
接下來咱們實現一下, 訪問 /a , /b, /c 三個路由, 返回不一樣的數據
數組
以前說過, req 對象上存放着請求的一些屬性. req.url
上記錄着請求的路徑, 獲取後就能夠判斷訪問路徑來返回不一樣的數據了瀏覽器
const {url} = req
複製代碼
完整代碼:服務器
const http = require('http')
const server = http.createServer((req, res) => {
const {url} = req
res.setHeader("Content-Type","text/html;charset=utf-8")
if(url === '/a') res.end(`訪問a路由`)
else if(url === '/b') res.end(`訪問b路由`)
else if(url === '/c') res.end(`訪問c路由`)
})
server.listen(8000)
複製代碼
接下來, 咱們對查詢參數作一下處理, 這時候, 是否是想到了什麼, 上面咱們的 url 沒有考慮到查詢參數的狀況, 路由裏應該濾除掉查詢參數, 咱們來一併處理
咱們知道, 查詢參數的形式是 a=x&b=x
, 這裏爲了方便使用, 咱們引用另外一個模塊 querystring
, 他能夠把查詢參數字符串切割成鍵值對形式
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url} = req
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1]) // 保存着查詢參數的對象
res.setHeader("Content-Type","text/html;charset=utf-8")
if(path === '/a') res.end(`訪問a路由, 查詢參數對象爲${JSON.stringify(query)}`) // 返回序列化的查詢參數
else if(path === '/b') res.end(`訪問b路由`)
else if(path === '/c') res.end(`訪問c路由`)
})
server.listen(8000)
複製代碼
OK, 接下來要作啥呢? 想了想, 咱們目前好像只處理了 GET 請求, 那咱們來處理一下 POST 請求吧.
同樣的, 請求的 method 能夠經過req.method
獲取
這裏要注意: req 對象實現了 ReadableStream 接口, 咱們能夠用信息流的方式讀取傳來的數據 (關於流的概念能夠看後面個人文章)
let postData = ''
req.on('data', chunk => { // 接收數據流
postData += chunk.toString() // 拼接信息流, 注意chunk是二進制格式, 須要轉爲二進制
})
req.on('end', () => {
// 接收數據流完畢的回調函數
})
複製代碼
完整代碼 :
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url, method} = req
const path = url.split('?')[0]
const query = querystring.parse(url.split('?')[1])
res.setHeader("Content-Type","text/html;charset=utf-8")
if(path === '/a' && method === 'GET') res.end(`訪問a路由, 查詢參數對象爲${JSON.stringify(query)}`)
else if(path === '/b' && method === 'GET') res.end(`訪問b路由`)
else if(path === '/c' && method === 'GET') res.end(`訪問c路由`)
else if(path === 'p' && method === 'POST'){
let postData = ''
req.on('data', chunk => { // 接收數據流
postData += chunk.toString() // 拼接信息流, 注意chunk是二進制格式, 須要轉爲二進制
})
req.on('end', () => {
res.end(`我接受到了數據了:${postData}`)
})
}
})
server.listen(8000)
複製代碼
OK, 來回顧一下咱們作了什麼:
咱們如今來回顧一下本身的代碼, 能夠看到, 在路由處理的部分有一堆的 if else, 假如每多一個路由就多一個 if , 那就太冗餘了.
這裏咱們用一個數組來存放一個個路由對象, 路由對象裏包含了路徑, 方法, 回調等必要信息
const http = require('http')
const querystring = require('querystring')
const server = http.createServer((req, res) => {
const {url, method: realMethod} = req
const realPath= url.split('?')[0]
const query = querystring.parse(url.split('?')[1])
let router = [] // 存放路由信息
res.setHeader("Content-Type","text/html;charset=utf-8")
router.push({
path: '/a',
method: 'GET',
handler(req, res){
res.end(`訪問a路由, 查詢參數對象爲${JSON.stringify(query)}`)
}
})
router.push({
path: '/b',
method: 'GET',
handler(req, res){
res.end(`訪問b路由`)
}
})
router.push({
path: '/c',
method: 'GET',
handler(req, res){
res.end(`訪問c路由`)
}
})
router.push({
path: '/p',
method: 'POST',
handler(req, res){
let postData = ''
req.on('data', chunk => {
postData += chunk.toString()
})
req.on('end', () => {
res.end(`我接受到了數據了:${postData}`)
})
}
})
// 統一處理路由
router.forEach(route => {
let {path, method, handler} = route
console.log(realPath, realMethod)
if(realPath === path && realMethod === method){
return handler()
}
})
})
server.listen(8000)
複製代碼
是否是感受稍微好看一點了, 加一個路由就 push 一個路由對象.
咱們離最終目標很接近了, 接下來, 讓咱們模仿 express , 寫一個 express 形式的 http 服務器(^▽^)
先來看看 express 是怎麼寫的
const express = require("express");
const app = express();
app.get("/a",
(req, res) => {
res.end("a路由");
}
);
app.get("/b",
(req, res) => {
res.end('b路由');
});
app.listen(3000, () => {
console.log("Example app listen at 3000");
});
複製代碼
能夠看到, 導出的 express 是一個函數, 函數內部會 new 一個實例對象出來, 大概的架子即是這樣.
const http = require('http')
class Express{
}
module.exports = function(){
return new Express()
}
複製代碼
接下來讓咱們實現完整代碼:
const http = require('http')
class Express{
constructor(){
this.router = [] // 存放路由對象
}
get(path, handler){
this.router.push({
path,
method: 'GET',
handler
})
}
post(path, handler){
this.router.push({
path,
method: 'POST',
handler
})
}
listen(port, listenCallback){
const server = http.createServer((req,res) => {
const {url, method:realMethod} = req
const realPath = url.split('?')[0]
this.router.forEach((route) => { // 遍歷路由對象
const {path, method, handler} = route
if(realPath === path && method === realMethod){
handler(req, res)
}
})
})
server.listen(port)
listenCallback()
}
}
module.exports = function(){
return new Express()
}
複製代碼
到這裏, 咱們已經簡單將咱們的 http 服務器改寫成 express 形式了, 不過考慮的細節還遠遠不夠 express 那麼完善.
撒花ヾ(◍°∇°◍)ノ゙
大三小前端一枚, 最近在寫一些博客, 歡迎關注(^▽^)
我的博客地址 : github