經過NodeJS,除了能夠編寫一些服務端程序來協助前端開發和測試外,還可以學習一些HTTP協議與Socket協議的相關知識,這些知識在優化前端性能和排查前端故障時說不定能派上用場。本章將介紹與之相關的NodeJS內置模塊。html
NodeJS原本的用途是編寫高性能Web服務器。咱們首先在這裏重複一下官方文檔裏的例子,使用NodeJS內置的http
模塊簡單實現一個HTTP服務器。前端
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, { 'Content-Type': 'text-plain' }); response.end('Hello World\n'); }).listen(8124);
以上程序建立了一個HTTP服務器並監聽8124
端口,打開瀏覽器訪問該端口http://127.0.0.1:8124/
就可以看到效果。node
1、APIapi
一、Http:官方文檔: http://nodejs.org/api/http.html瀏覽器
'http'模塊提供兩種使用方式:安全
做爲服務端使用時,建立一個HTTP服務器,監聽HTTP客戶端請求並返回響應。服務器
做爲客戶端使用時,發起一個HTTP客戶端請求,獲取服務端響應。網絡
首先咱們來看看服務端模式下如何工做。如例子所示,首先須要使用.createServer
方法建立一個服務器,而後調用.listen
方法監聽端口。以後,每當來了一個客戶端請求,建立服務器時傳入的回調函數就被調用一次。能夠看出,這是一種事件機制。併發
HTTP請求本質上是一個數據流,由請求頭(headers)和請求體(body)組成。例如如下是一個完整的HTTP請求數據內容。app
POST / HTTP/1.1 User-Agent: curl/7.26.0 Host: localhost Accept: */* Content-Length: 11 Content-Type: application/x-www-form-urlencoded Hello World
能夠看到,空行之上是請求頭,之下是請求體。HTTP請求在發送給服務器時,能夠認爲是按照從頭至尾的順序一個字節一個字節地以數據流方式發送的。而http
模塊建立的HTTP服務器在接收到完整的請求頭後,就會調用回調函數。在回調函數中,除了可使用request
對象訪問請求頭數據外,還能把request
對象看成一個只讀數據流來訪問請求體數據。
HTTP響應本質上也是一個數據流,一樣由響應頭(headers)和響應體(body)組成。
接下來咱們看看客戶端模式下如何工做。爲了發起一個客戶端HTTP請求,咱們須要指定目標服務器的位置併發送請求頭和請求體,如下示例演示了具體作法。
var options = { hostname: 'www.example.com', port: 80, path: '/upload', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }; var request = http.request(options, function (response) {}); request.write('Hello World'); request.end();
能夠看到,.request
方法建立了一個客戶端,並指定請求目標和請求頭數據。以後,就能夠把request
對象看成一個只寫數據流來寫入請求體數據和結束請求。另外,因爲HTTP請求中GET
請求是最多見的一種,而且不須要請求體,所以http
模塊也提供瞭如下便捷API。
http.get('http://www.example.com/', function (response) {});
當客戶端發送請求並接收到完整的服務端響應頭時,就會調用回調函數。在回調函數中,除了可使用response
對象訪問響應頭數據外,還能把response
對象看成一個只讀數據流來訪問響應體數據。
二、Https:官方文檔: http://nodejs.org/api/https.html
https
模塊與http
模塊極爲相似,區別在於https
模塊須要額外處理SSL證書。
在服務端模式下,建立一個HTTPS服務器的示例以下。
var options = { key: fs.readFileSync('./ssl/default.key'), cert: fs.readFileSync('./ssl/default.cer') }; var server = https.createServer(options, function (request, response) { // ...
});
能夠看到,與建立HTTP服務器相比,多了一個options
對象,經過key
和cert
字段指定了HTTPS服務器使用的私鑰和公鑰。
另外,NodeJS支持SNI技術,能夠根據HTTPS客戶端請求使用的域名動態使用不一樣的證書,所以同一個HTTPS服務器可使用多個域名提供服務。接着上例,可使用如下方法爲HTTPS服務器添加多組證書。
server.addContext('foo.com', { key: fs.readFileSync('./ssl/foo.com.key'), cert: fs.readFileSync('./ssl/foo.com.cer') }); server.addContext('bar.com', { key: fs.readFileSync('./ssl/bar.com.key'), cert: fs.readFileSync('./ssl/bar.com.cer') });
在客戶端模式下,發起一個HTTPS客戶端請求與http
模塊幾乎相同,示例以下。
var options = { hostname: 'www.example.com', port: 443, path: '/', method: 'GET' }; var request = https.request(options, function (response) {}); request.end();
但若是目標服務器使用的SSL證書是自制的,不是從頒發機構購買的,默認狀況下https
模塊會拒絕鏈接,提示說有證書安全問題。在options
里加入rejectUnauthorized: false
字段能夠禁用對證書有效性的檢查,從而容許https
模塊請求開發環境下使用自制證書的HTTPS服務器。
三、Url:官方文檔: http://nodejs.org/api/url.html
處理HTTP請求時url
模塊使用率超高,由於該模塊容許解析URL、生成URL,以及拼接URL。首先咱們來看看一個完整的URL的各組成部分。
href ----------------------------------------------------------------- host path --------------- ---------------------------- http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
----- --------- -------- ---- -------- ------------- ----- protocol auth hostname port pathname search hash ------------ query
咱們可使用.parse
方法來將一個URL字符串轉換爲URL對象,示例以下。
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash'); /* => { protocol: 'http:', auth: 'user:pass', host: 'host.com:8080', port: '8080', hostname: 'host.com', hash: '#hash', search: '?query=string', query: 'query=string', pathname: '/p/a/t/h', path: '/p/a/t/h?query=string', href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' } */
傳給.parse
方法的不必定要是一個完整的URL,例如在HTTP服務器回調函數中,request.url
不包含協議頭和域名,但一樣能夠用.parse
方法解析。
.parse
方法還支持第二個和第三個布爾類型可選參數。第二個參數等於true
時,該方法返回的URL對象中,query
字段再也不是一個字符串,而是一個通過querystring
模塊轉換後的參數對象。第三個參數等於true
時,該方法能夠正確解析不帶協議頭的URL,例如//www.example.com/foo/bar
。
反過來,format
方法容許將一個URL對象轉換爲URL字符串。
另外,.resolve
方法能夠用於拼接URL,示例以下。
url.resolve('http://www.example.com/foo/bar', '../baz'); /* => http://www.example.com/baz
*/
四、Query String:官方文檔: http://nodejs.org/api/querystring.html
querystring
模塊用於實現URL參數字符串與參數對象的互相轉換,示例以下。
querystring.parse('foo=bar&baz=qux&baz=quux&corge'); /* => { foo: 'bar', baz: ['qux', 'quux'], corge: '' } */ querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }); /* => 'foo=bar&baz=qux&baz=quux&corge=' */
五、Zlib:官方文檔: http://nodejs.org/api/zlib.html
zlib
模塊提供了數據壓縮和解壓的功能。當咱們處理HTTP請求和響應時,可能須要用到這個模塊。
首先咱們看一個使用zlib
模塊壓縮HTTP響應體數據的例子。這個例子中,判斷了客戶端是否支持gzip,並在支持的狀況下使用zlib
模塊返回gzip以後的響應體數據。
http.createServer(function (request, response) { var i = 1024, data = ''; while (i--) { data += '.'; } if ((request.headers['accept-encoding'] || '').indexOf('gzip') !== -1) { zlib.gzip(data, function (err, data) { response.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Encoding': 'gzip' }); response.end(data); }); } else { response.writeHead(200, { 'Content-Type': 'text/plain' }); response.end(data); } }).listen(80);
接着咱們看一個使用zlib
模塊解壓HTTP響應體數據的例子。這個例子中,判斷了服務端響應是否使用gzip壓縮,並在壓縮的狀況下使用zlib
模塊解壓響應體數據。
var options = { hostname: 'www.example.com', port: 80, path: '/', method: 'GET', headers: { 'Accept-Encoding': 'gzip, deflate' } }; http.request(options, function (response) { var body = []; response.on('data', function (chunk) { body.push(chunk); }); response.on('end', function () { body = Buffer.concat(body); if (response.headers['content-encoding'] === 'gzip') { zlib.gunzip(body, function (err, data) { console.log(data.toString()); }); } else { console.log(data.toString()); } }); }).end();
六、Net:官方文檔: http://nodejs.org/api/net.html
net
模塊可用於建立Socket服務器或Socket客戶端。因爲Socket在前端領域的使用範圍還不是很廣,這裏先不涉及到WebSocket的介紹,僅僅簡單演示一下如何從Socket層面來實現HTTP請求和響應。
首先咱們來看一個使用Socket搭建一個很不嚴謹的HTTP服務器的例子。這個HTTP服務器無論收到啥請求,都固定返回相同的響應。
net.createServer(function (conn) { conn.on('data', function (data) { conn.write([ 'HTTP/1.1 200 OK', 'Content-Type: text/plain', 'Content-Length: 11', '', 'Hello World' ].join('\n')); }); }).listen(80);
接着咱們來看一個使用Socket發起HTTP客戶端請求的例子。這個例子中,Socket客戶端在創建鏈接後發送了一個HTTP GET請求,並經過data
事件監聽函數來獲取服務器響應。
var options = { port: 80, host: 'www.example.com' }; var client = net.connect(options, function () { client.write([ 'GET / HTTP/1.1', 'User-Agent: curl/7.26.0', 'Host: www.baidu.com', 'Accept: */*', '', '' ].join('\n')); }); client.on('data', function (data) { console.log(data.toString()); client.end(); });
2、小結
使用NodeJS操做網絡,特別是操做HTTP請求和響應時會遇到一些驚喜,這裏對一些常見問題作解答。
問: 爲何經過headers
對象訪問到的HTTP請求頭或響應頭字段不是駝峯的?
答: 從規範上講,HTTP請求頭和響應頭字段都應該是駝峯的。但現實是殘酷的,不是每一個HTTP服務端或客戶端程序都嚴格遵循規範,因此NodeJS在處理從別的客戶端或服務端收到的頭字段時,都統一地轉換爲了小寫字母格式,以便開發者能使用統一的方式來訪問頭字段,例如headers['content-length']
。
問: 爲何http
模塊建立的HTTP服務器返回的響應是chunked
傳輸方式的?
答: 由於默認狀況下,使用.writeHead
方法寫入響應頭後,容許使用.write
方法寫入任意長度的響應體數據,並使用.end
方法結束一個響應。因爲響應體數據長度不肯定,所以NodeJS自動在響應頭裏添加了Transfer-Encoding: chunked
字段,並採用chunked
傳輸方式。可是當響應體數據長度肯定時,可以使用.writeHead
方法在響應頭裏加上Content-Length
字段,這樣作以後NodeJS就不會自動添加Transfer-Encoding
字段和使用chunked
傳輸方式。
問: 爲何使用http
模塊發起HTTP客戶端請求時,有時候會發生socket hang up
錯誤?
答: 發起客戶端HTTP請求前須要先建立一個客戶端。http
模塊提供了一個全局客戶端http.globalAgent
,可讓咱們使用.request
或.get
方法時不用手動建立客戶端。可是全局客戶端默認只容許5個併發Socket鏈接,當某一個時刻HTTP客戶端請求建立過多,超過這個數字時,就會發生socket hang up
錯誤。解決方法也很簡單,經過http.globalAgent.maxSockets
屬性把這個數字改大些便可。另外,https
模塊遇到這個問題時也同樣經過https.globalAgent.maxSockets
屬性來處理。
http
和https
模塊支持服務端模式和客戶端模式兩種使用方式。
request
和response
對象除了用於讀寫頭數據外,均可以看成數據流來操做。
url.parse
方法加上request.url
屬性是處理HTTP請求時的固定搭配。
使用zlib
模塊能夠減小使用HTTP協議時的數據傳輸量。
經過net
模塊的Socket服務器與客戶端可對HTTP協議作底層操做。