Node.js 指南(HTTP事務的剖析)

HTTP事務的剖析

本指南的目的是讓你充分了解Node.js HTTP處理的過程,咱們假設你在通常意義上知道HTTP請求的工做方式,不管語言或編程環境如何,咱們還假設你對Node.js EventEmittersStreams有點熟悉,若是你對它們不太熟悉,那麼值得快速閱讀每一個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使用一些方便的對象調用請求處理函數來處理事務、requestresponse,咱們很快就會講到。npm

爲了實際處理請求,須要在server對象上調用listen方法,在大多數狀況下,你須要傳遞給listen的是你但願服務器監聽的端口號,還有一些其餘選項,請參閱API參考。編程

方法、URL和Headers

處理請求時,你可能要作的第一件事就是查看方法和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也可用。服務器

請求體

收到POSTPUT請求時,請求體可能對你的應用程序很重要,獲取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-streambody這樣的模塊能夠幫助隱藏一些邏輯,在走這條路以前,要很好地瞭解正在發生的事情,這就是爲何你在這裏!

關於錯誤的簡單介紹

因爲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狀態碼

若是不設置它,響應中的HTTP狀態碼始終爲200,固然,並不是每一個HTTP響應都保證這一點,而且在某些時候你確定但願發送不一樣的狀態碼,爲此,你能夠設置statusCode屬性。

response.statusCode = 404; // Tell the client that the resource wasn't found.

還有其餘一些快捷方式,咱們很快就會看到。

設置響應Headers

Headers是經過一個名爲setHeader的方便方法設置的。

response.setHeader('Content-Type', 'application/json');
response.setHeader('X-Powered-By', 'bacon');

在響應上設置headers時,大小寫對其名稱不敏感,若是重複設置標題,則設置的最後一個值是發送的值。

顯式發送Header數據

咱們已經討論過的設置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服務器示例

讓咱們簡化前面的示例來進行一個簡單的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
  • URL是/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對象是ReadableStreamresponse對象是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請求的大部分基礎知識,此時,你應該可以:

  • 使用請求處理程序函數實例化HTTP服務器,並讓它偵聽端口。
  • request對象中獲取headers、URL、方法和body數據。
  • 根據request對象中的URL和/或其餘數據作出路由決策。
  • 經過response對象發送headers、HTTP狀態碼和body數據。
  • request對象和response對象管道數據。
  • 處理requestresponse流中的流錯誤。

從這些基礎知識中,能夠構建用於許多典型用例的Node.js HTTP服務器,這些API提供了許多其餘功能,所以請務必閱讀有關EventEmittersStreamsHTTP的API文檔。


上一篇:Node.js中的定時器

下一篇:使用不一樣的文件系統

相關文章
相關標籤/搜索