本指南的目的是讓你充分了解Node.js HTTP處理的過程,咱們假設你在通常意義上知道HTTP請求的工做方式,不管語言或編程環境如何,咱們還假設你對Node.js EventEmitters
和Streams
有點熟悉,若是你對它們不太熟悉,那麼值得快速閱讀每一個API文檔。html
任何節點Web服務器應用程序在某些時候都必須建立Web服務器對象,這是經過使用createServer
完成的。node
const http = require('http'); const server = http.createServer((request, response) => { // magic happens here! });
傳遞給createServer
的函數對於針對該服務器發出的每一個HTTP請求都會調用一次,所以它被稱爲請求處理程序,實際上,createServer
返回的Server
對象是一個EventEmitter
,咱們這裏只是建立server
對象的簡寫,而後稍後添加監聽器。express
const server = http.createServer(); server.on('request', (request, response) => { // the same kind of magic happens here! });
當HTTP請求命中服務器時,node使用一些方便的對象調用請求處理函數來處理事務、request
和response
,咱們很快就會講到。npm
爲了實際處理請求,須要在server
對象上調用listen
方法,在大多數狀況下,你須要傳遞給listen
的是你但願服務器監聽的端口號,還有一些其餘選項,請參閱API參考。編程
處理請求時,你可能要作的第一件事就是查看方法和URL,以便採起適當的措施,Node經過將方便的屬性放在request
對象上來使這相對輕鬆。json
const { method, url } = request;
注意:request
對象是IncomingMessage
的一個實例。
這裏的method
將始終是普通的HTTP方法/動做,url
是沒有服務器、協議或端口的完整URL,對於典型的URL,這意味着包括第三個正斜槓後的全部內容。segmentfault
Headers也不遠,它們在本身的request
對象中,被稱爲headers
。數組
const { headers } = request; const userAgent = headers['user-agent'];
這裏須要注意的是,不管客戶端實際發送它們的方式如何,全部headers都僅以小寫字母表示,這簡化了爲任何目的解析headers的任務。瀏覽器
若是重複某些headers,則它們的值將被覆蓋或以逗號分隔的字符串鏈接在一塊兒,具體取決於header,在某些狀況下,這可能會有問題,所以rawHeaders
也可用。服務器
收到POST
或PUT
請求時,請求體可能對你的應用程序很重要,獲取body數據比訪問請求headers更復雜一點,傳遞給處理程序的request
對象實現了ReadableStream
接口,就像任何其餘流同樣,能夠在其餘地方監聽或傳輸此流,咱們能夠經過監聽流的'data'
和'end'
事件來直接從流中獲取數據。
每一個'data'
事件中發出的塊是一個Buffer
,若是你知道它將是字符串數據,那麼最好的方法是在數組中收集數據,而後在'end'
,鏈接並對其進行字符串化。
let body = []; request.on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); // at this point, `body` has the entire request body stored in it as a string });
注意:這看起來有點單調乏味,並且在不少狀況下確實如此,幸運的是,在 npm上有像 concat-stream和 body這樣的模塊能夠幫助隱藏一些邏輯,在走這條路以前,要很好地瞭解正在發生的事情,這就是爲何你在這裏!
因爲request
對象是一個ReadableStream
,它也是一個EventEmitter
,發生錯誤時的行爲與此相似。
request
流中的錯誤經過在流上發出'error'
事件來呈現,若是你沒有該事件的偵聽器,則會拋出錯誤,這可能會致使Node.js程序崩潰。所以,你應該在請求流上添加'error'
偵聽器,即便你只是記錄它並繼續前進(雖然最好發送某種HTTP錯誤響應,稍後會詳細介紹)。
request.on('error', (err) => { // This prints the error message and stack trace to `stderr`. console.error(err.stack); });
還有其餘方法能夠處理這些錯誤,例如其餘抽象和工具,但始終要注意錯誤可能而且確實會發生,而且你將不得不處理它們。
此時,咱們已經介紹瞭如何建立服務器,並從請求中獲取方法、URL、headers和body,當咱們將它們放在一塊兒時,它可能看起來像這樣:
const http = require('http'); http.createServer((request, response) => { const { headers, method, url } = request; let body = []; request.on('error', (err) => { console.error(err); }).on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); // At this point, we have the headers, method, url and body, and can now // do whatever we need to in order to respond to this request. }); }).listen(8080); // Activates this server, listening on port 8080.
若是咱們運行此示例,咱們將可以接收請求,但不會響應它們,實際上,若是你在Web瀏覽器中請求此示例,則你的請求將超時,由於沒有任何內容被髮送回客戶端。
到目前爲止,咱們尚未涉及響應對象,它是ServerResponse
的一個實例,它是一個WritableStream
,它包含許多用於將數據發送回客戶端的有用方法,接下來咱們將介紹。
若是不設置它,響應中的HTTP狀態碼始終爲200
,固然,並不是每一個HTTP響應都保證這一點,而且在某些時候你確定但願發送不一樣的狀態碼,爲此,你能夠設置statusCode
屬性。
response.statusCode = 404; // Tell the client that the resource wasn't found.
還有其餘一些快捷方式,咱們很快就會看到。
Headers是經過一個名爲setHeader
的方便方法設置的。
response.setHeader('Content-Type', 'application/json'); response.setHeader('X-Powered-By', 'bacon');
在響應上設置headers時,大小寫對其名稱不敏感,若是重複設置標題,則設置的最後一個值是發送的值。
咱們已經討論過的設置headers和狀態碼的方法假設你正在使用「隱式headers」,這意味着在開始發送body數據以前,你須要依賴node在正確的時間爲你發送headers。
若是須要,能夠將headers顯式寫入響應流,爲此,有一個名爲writeHead
的方法,它將狀態碼和headers寫入流。
response.writeHead(200, { 'Content-Type': 'application/json', 'X-Powered-By': 'bacon' });
一旦設置了headers(隱式或顯式),你就能夠開始發送響應數據了。
因爲response
對象是WritableStream
,所以將響應體寫入客戶端只需使用經常使用的流方法便可。
response.write('<html>'); response.write('<body>'); response.write('<h1>Hello, World!</h1>'); response.write('</body>'); response.write('</html>'); response.end();
流上的end
函數也能夠接收一些可選數據做爲流上的最後一位數據發送,所以咱們能夠以下簡化上面的示例。
response.end('<html><body><h1>Hello, World!</h1></body></html>');
注意:在開始向body寫入數據塊以前設置狀態和headers很重要,這是有道理的,由於headers在HTTP響應中位於body以前。
response
流也能夠發出'error'
事件,在某些時候你也必須處理它,全部關於request
流錯誤的建議仍然適用於此處。
如今咱們已經瞭解瞭如何進行HTTP響應,讓咱們把它們放在一塊兒,在前面的示例的基礎上,咱們將建立一個服務器,用於發回用戶發送給咱們的全部數據,咱們將使用JSON.stringify
將該數據格式化爲JSON。
const http = require('http'); http.createServer((request, response) => { const { headers, method, url } = request; let body = []; request.on('error', (err) => { console.error(err); }).on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); // BEGINNING OF NEW STUFF response.on('error', (err) => { console.error(err); }); response.statusCode = 200; response.setHeader('Content-Type', 'application/json'); // Note: the 2 lines above could be replaced with this next one: // response.writeHead(200, {'Content-Type': 'application/json'}) const responseBody = { headers, method, url, body }; response.write(JSON.stringify(responseBody)); response.end(); // Note: the 2 lines above could be replaced with this next one: // response.end(JSON.stringify(responseBody)) // END OF NEW STUFF }); }).listen(8080);
讓咱們簡化前面的示例來進行一個簡單的echo服務器,它只是在響應中發送請求中收到的任何數據,咱們須要作的就是從請求流中獲取數據並將該數據寫入響應流,相似於咱們以前所作的。
const http = require('http'); http.createServer((request, response) => { let body = []; request.on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); response.end(body); }); }).listen(8080);
如今讓咱們調整一下,咱們只想在如下條件下發送echo:
POST
。/echo
。在任何其餘狀況下,咱們只想響應404
。
const http = require('http'); http.createServer((request, response) => { if (request.method === 'POST' && request.url === '/echo') { let body = []; request.on('data', (chunk) => { body.push(chunk); }).on('end', () => { body = Buffer.concat(body).toString(); response.end(body); }); } else { response.statusCode = 404; response.end(); } }).listen(8080);
注意:經過這種方式檢查URL,咱們正在作一種「路由」的形式,其餘形式的路由能夠像
switch
語句同樣簡單,也能夠像
express這樣的整個框架同樣複雜,若是你正在尋找能夠進行路由的東西,請嘗試使用
router。
如今讓咱們來簡化一下吧,請記住,request
對象是ReadableStream
,response
對象是WritableStream
,這意味着咱們可使用pipe
將數據從一個引導到另外一個,這正是咱們想要的echo服務器!
const http = require('http'); http.createServer((request, response) => { if (request.method === 'POST' && request.url === '/echo') { request.pipe(response); } else { response.statusCode = 404; response.end(); } }).listen(8080);
咱們尚未完成,正如本指南中屢次提到的,錯誤能夠並且確實會發生,咱們須要處理它們。
爲了處理請求流上的錯誤,咱們將錯誤記錄到stderr
併發送400
狀態碼以指示Bad Request
,可是,在實際應用程序中,咱們須要檢查錯誤以肯定正確的狀態碼和消息是什麼,與一般的錯誤同樣,你應該查閱錯誤文檔。
在響應中,咱們只是將錯誤記錄到stderr
。
const http = require('http'); http.createServer((request, response) => { request.on('error', (err) => { console.error(err); response.statusCode = 400; response.end(); }); response.on('error', (err) => { console.error(err); }); if (request.method === 'POST' && request.url === '/echo') { request.pipe(response); } else { response.statusCode = 404; response.end(); } }).listen(8080);
咱們如今已經介紹了處理HTTP請求的大部分基礎知識,此時,你應該可以:
request
對象中獲取headers、URL、方法和body數據。request
對象中的URL和/或其餘數據作出路由決策。response
對象發送headers、HTTP狀態碼和body數據。request
對象和response
對象管道數據。request
和response
流中的流錯誤。從這些基礎知識中,能夠構建用於許多典型用例的Node.js HTTP服務器,這些API提供了許多其餘功能,所以請務必閱讀有關EventEmitters
、Streams
和HTTP
的API文檔。