前端er,什麼時候,你想寫一個 HTTP 服務器?
前端 er,什麼時候,你想寫一個 HTTP 服務器?
當你第一次接觸工程化的項目時,看到項目控制枱正在 building,過一會突然跳出一個 URL 地址,你點開它居然是你剛寫好的網頁,好神奇。
當你接後端同伴的接口時,你把數據帶去,接口竟然給你返回 500 錯誤;你去找後端,後端説這樣傳不行,要怎麼怎麼改,你按照他的改完,返回 200 成功了。
有時候你的請求莫名其妙的就跨域了,後端説讓你們自己處理,你就找呀找解決方案。但是為什麼會跨域?後端怎麼配置的,你也不清楚。
終於有一天,你痛定思痛,決定痛改前非,一定要自己搭一個 HTTP 服務器,徹底理清這裏面的彎彎繞繞,從此拒絕被忽悠,拒絕做只聽命令的大頭兵。
但是話説回來了,怎麼入手呢?
別急,這都給您備好啦。寫 HTTP 服務器需要後端語言,不用説,自然首選 Node.js。
下面我們基於 Node.js 的 http
模塊,一起搭建一個的 HTTP 服務器。
http 模塊
一個超簡單的 HTTP web 服務器的示例:
```js const http = require('http')
const server = http.createServer((request, response) => { response.statusCode = 200 response.end('hello world') })
server.listen(3000) ```
這裏引入了 http 模塊,提供了 createServer
方法,傳入一個回調函數,創建了一個服務器。
現在把代碼寫進 index.js
,再超簡單的把它運行起來:
sh
$ node index.js
運行起來後,接着來剖析一下這段代碼 👇。
代碼剖析
http.createServer 方法的參數是一個回調函數,這個回調函數有兩個參數 —— 它們是 HTTP 服務器的核心。
第一個參數是請求對象 request
,第二個參數是響應對象 response
。你可以把它們看作兩個袋子,一個袋子裏裝着請求相關的數據,一個袋子裏裝着響應相關的操作。
request
包含了詳細的請求數據,也就是我們前端調接口傳遞過來的數據。通過它可以獲取請求頭,請求參數,請求方法等等。
response
主要用於響應相關的設置和操作。什麼是響應?就是我收到了客户端的請求,我可以設置狀態碼為 200 並返給前端數據;或者設置狀態碼為 500 並返給前端錯誤。
總之一句話,調用接口返回什麼,是由 response
決定的。
事實上,createServer 返回的是一個 EventEmitter,因此上面的寫法等同於這樣:
```js const server = http.createServer()
server.on('request', (request, response) => { response.statusCode = 200 response.end('hello world') }) ```
request 解析
用户發起請求的相關數據,都包含在 request 對象中。
這些數據包含常用的請求方法,請求頭,url,請求體等等數據。
js
const { method, url, headers } = request
method 表示請求方法可直接使用,headers 返回請求頭對象,使用也比較簡便:
js
const { headers } = request
const userAgent = headers['user-agent'] // 請求頭全是小寫字母
唯獨 url 字符串不好解析,裏面包含了協議,hostname,path,query 等等。
所幸 Node.js 提供了 url
和 querystring
兩個模塊解析 url 字符串。
URL 解析
先看一個 url 模塊的例子:
```js const url = require('url') // 解析url字符串 var string = 'http://localhost:8888/start?foo=bar&hello=world'
var url_object = url.parse(string) // { protocol: 'http:', host:'localhost:8888', pathname: '/start', query: 'foo=bar&hello=world' } ```
看到了吧,url
模塊可以將一個完整的 URL 地址字符串,拆分成一個包含各部分屬性的對象。
但是美中不足,其他部分都解析出來了,唯獨 query 還是一個字符串。
query
需要二次解析。怎麼辦呢?這時候第二個模塊 querystring
出場了:
```js const querystring = require('querystring') // 解析query字符串 var string = 'http://localhost:8888/start?foo=bar&hello=world'
var url_object = url.parse(string) // { query: 'foo=bar&hello=world' } var query_object = querystring.parse(url_object.query) // { foo: 'bar', hello: 'world' } ```
這下就完美了。用 url + querystring 組合,可以完整解析你的 URL。
請求體解析
對於 POST
或者 PUT
請求,我們需要接收請求體的數據。
這裏請求體比較特殊,它不是一次性傳過來的數據,而是通過 Stream
流的方式流式傳遞來的,因此要通過監聽 data 和 end 事件一點點的接收。
獲取方法如下:
js
let body = []
request.on('data', chunk => {
// 這裏的 chunk 是一個 Buffer
body.push(chunk)
})
request.on('end', () => {
body = Buffer.concat(body)
})
response 設置
服務器收到客户端請求,要通過 response 設置如何響應給客户端。
響應設置,主要就是狀態碼,響應頭,響應體三部分。
首先是狀態碼,比如 404:
js
response.statusCode = 404
再有是響應頭:
js
response.setHeader('Content-Type', 'text/plain')
最後是響應體:
js
response.end('找不到數據')
這三部分也可以合在一起:
js
response
.writeHead(404, {
'Content-Type': 'text/plain',
'Content-Length': 49
})
.end('找不到數據')
發送 http 請求
http 模塊除了接受客户端的請求,還可以作為客户端去發送請求。
發送 http 請求是指,在 Node.js 中請求其他接口獲取數據。
發送請求主要通過 http.request
方法來實現。
GET
下面是一個發送 GET 請求的簡單示例:
```js const http = require('http') const options = { hostname: 'nodejs.cn', port: 80, path: '/learn', method: 'GET' }
const req = http.request(options, res => {
console.log(狀態碼: ${res.statusCode}
)
res.on('data', d => {
process.stdout.write(d)
})
res.on('end', () => {})
})
req.on('error', error => { console.error(error) })
req.end() ```
使用 http.request 發送請求後,必須顯示調用 req.end()
來表示完成請求發送。
POST
與上面 GET 請求基本一致,區別是看請求體怎麼傳:
```js const http = require('http') const options = { hostname: 'nodejs.cn', port: 80, path: '/learn', method: 'POST' }
const body = { sex: 'man', name: 'ruims' }
const req = http.request(options, res => {
console.log(狀態碼: ${res.statusCode}
)
res.on('data', d => {
process.stdout.write(d)
})
res.on('end', () => {})
})
req.on('error', error => { console.error(error) })
req.write(JSON.stringify(body)) // 傳遞 body 參數寫法
req.end() ```
詭異之處
看到這裏,如果你對 nodejs 理解不深,可能會發現幾處詭異的地方。
比如,正常情況下 POST 請求傳遞 body
參數可能是這樣的:
js
var body = { desc: '請求體參數' }
var req = http.request({
path: '/',
method: 'POST',
data: body
})
而上面説到的正確姿勢是這樣的:
js
var body = { desc: '請求體參數' }
var req = http.request({
path: '/',
method: 'POST'
})
req.write(JSON.stringify(body))
還有上面獲取請求體也是如此,不能直接通過 res.body
獲取,非得監聽事件,然後拼接數據。
這幾處應該是大家理解 http 模塊最困惑的地方。其實刨根問底,這不屬於 http 的難點,而是 Node.js Stream
流的特有語法。
事實上,http 模塊的核心 ——— request
和 response
都屬於 Stream
,一個是可讀流,一個是可寫流。
因此,徹底理解 http 模塊,還需要深入瞭解 Stream
流是什麼。
這篇就到這裏,下一篇我們繼續探索 Stream
流,記得關注我哦。
本文來自我的專欄 前端砍柴人,如果對您有幫助,不妨點個贊鼓勵一下哈,成功我再接再厲~
- 為什麼前端不能沒有監控系統?
- 4月份,我月更了25篇文章,擼了4w多字
- 怒肝 JavaScript 數據結構 — 雙端隊列篇
- 怒肝 JavaScript 數據結構 — 棧篇(三)
- 怒肝 JavaScript 數據結構 — 數組篇(二)
- TypeScript 之 any:哪裏可以用?哪裏真不用?
- 大裁員下,程序員如何做副業?
- 一個小廠前端 Leader 如何篩選候選人?
- 音視頻通信加餐 —— WebRTC一肝到底
- 用一個 flv.js 播放監控的例子,帶你深撅直播流技術
- 前端架構師破局技能,NodeJS 落地 WebSocket 實踐
- 前端架構師的 git 功力,你有幾成火候?
- 前端er,什麼時候,你想寫一個 HTTP 服務器?
- 以 Vuex 為引,一窺狀態管理全貌
- 前端架構師神技,三招統一代碼風格(一文講透)