日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

node.js整理 07例子

發布時間:2025/3/15 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 node.js整理 07例子 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

需求

  • 一個簡單的靜態文件合并服務器,該服務器需要支持類似以下格式的JS或CSS文件合并請求。
http://assets.example.com/foo/??bar.js,baz.js
  • 在以上URL中,??是一個分隔符,之前是需要合并的多個文件的URL的公共部分,之后是使用,分隔的差異部分。因此服務器處理這個URL時,返回的是以下兩個文件按順序合并后的內容。
/foo/bar.js /foo/baz.js
  • 另外,服務器也需要能支持類似以下格式的普通的JS或CSS文件請求。
http://assets.example.com/foo/bar.js

第一次迭代

  • 設計方案
+---------+ +-----------+ +----------+ request -->| parse |-->| combine |-->| output |--> response+---------+ +-----------+ +----------+//服務器會首先分析URL,得到請求的文件的路徑和類型(MIME)。然后,服務器會讀取請求的文件,并按順序合并文件內容。最后,服務器返回響應,完成對一次請求的處理;另外,服務器在讀取文件時需要有個根目錄,并且服務器監聽的HTTP端口最好也不要寫死在代碼里,因此服務器需要是可配置的。
  • 實現
var fs = require('fs'),path = require('path'),http = require('http');var MIME = {'.css': 'text/css','.js': 'application/javascript' };function combineFiles(pathnames, callback) {var output = [];(function next(i, len) {if (i < len) {fs.readFile(pathnames[i], function (err, data) {if (err) {callback(err);} else {output.push(data);next(i + 1, len);}});} else {callback(null, Buffer.concat(output));}}(0, pathnames.length)); }function main(argv) {var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),root = config.root || '.',port = config.port || 80;http.createServer(function (request, response) {var urlInfo = parseURL(root, request.url);combineFiles(urlInfo.pathnames, function (err, data) {if (err) {response.writeHead(404);response.end(err.message);} else {response.writeHead(200, {'Content-Type': urlInfo.mime});response.end(data);}});}).listen(port); }function parseURL(root, url) {var base, pathnames, parts;if (url.indexOf('??') === -1) {url = url.replace('/', '/??');}parts = url.split('??');base = parts[0];pathnames = parts[1].split(',').map(function (value) {return path.join(root, base, value);});return {mime: MIME[path.extname(pathnames[0])] || 'text/plain',pathnames: pathnames}; }main(process.argv.slice(2));
  • 注意

    • 使用命令行參數傳遞JSON配置文件路徑,入口函數負責讀取配置并創建服務器。

    • 入口函數完整描述了程序的運行邏輯,其中解析URL和合并文件的具體實現封裝在其它兩個函數里。

    • 解析URL時先將普通URL轉換為了文件合并URL,使得兩種URL的處理方式可以一致。

    • 合并文件時使用異步API讀取文件,避免服務器因等待磁盤IO而發生阻塞。

  • 把以上代碼保存為server.js,之后就可以通過node server.js config.json命令啟動程序

第二次迭代

  • 第一次迭代之后,已經有了一個可工作的版本,滿足了功能需求。接下來從性能的角度出發,看看代碼還有哪些改進余地。

  • 把map方法換成for循環或許會更快一些,但第一版代碼最大的性能問題存在于從讀取文件到輸出響應的過程當中。以處理/??a.js,b.js,c.js這個請求為例,看看整個處理過程中耗時在哪兒。

發送請求 等待服務端響應 接收響應 ---------+----------------------+------------->-- 解析請求------ 讀取a.js------ 讀取b.js------ 讀取c.js-- 合并數據-- 輸出響應
  • 第一版代碼依次把請求的文件讀取到內存中之后,再合并數據和輸出響應。這會導致以下兩個問題:
    • 當請求的文件比較多比較大時,串行讀取文件會比較耗時,從而拉長了服務端響應等待時間。
    • 由于每次響應輸出的數據都需要先完整地緩存在內存里,當服務器請求并發數較大時,會有較大的內存開銷。
  • 對于第一個問題,很容易想到把讀取文件的方式從串行改為并行。但是別這樣做,因為對于機械磁盤而言,因為只有一個磁頭,嘗試并行讀取文件只會造成磁頭頻繁抖動,反而降低IO效率。而對于固態硬盤,雖然的確存在多個并行IO通道,但是對于服務器并行處理的多個請求而言,硬盤已經在做并行IO了,對單個請求采用并行IO無異于拆東墻補西墻。因此,正確的做法不是改用并行IO,而是一邊讀取文件一邊輸出響應,把響應輸出時機提前至讀取第一個文件的時刻。這樣調整后,整個請求處理過程變成下邊這樣。
發送請求 等待服務端響應 接收響應 ---------+----+------------------------------->-- 解析請求-- 檢查文件是否存在-- 輸出響應頭------ 讀取和輸出a.js------ 讀取和輸出b.js------ 讀取和輸出c.js
  • 實現
function main(argv) {var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),root = config.root || '.',port = config.port || 80;http.createServer(function (request, response) {var urlInfo = parseURL(root, request.url);validateFiles(urlInfo.pathnames, function (err, pathnames) {if (err) {response.writeHead(404);response.end(err.message);} else {response.writeHead(200, {'Content-Type': urlInfo.mime});outputFiles(pathnames, response);}});}).listen(port); }function outputFiles(pathnames, writer) {(function next(i, len) {if (i < len) {var reader = fs.createReadStream(pathnames[i]);reader.pipe(writer, { end: false });reader.on('end', function() {next(i + 1, len);});} else {writer.end();}}(0, pathnames.length)); }function validateFiles(pathnames, callback) {(function next(i, len) {if (i < len) {fs.stat(pathnames[i], function (err, stats) {if (err) {callback(err);} else if (!stats.isFile()) {callback(new Error());} else {next(i + 1, len);}});} else {callback(null, pathnames);}}(0, pathnames.length)); }
  • 第二版代碼在檢查了請求的所有文件是否有效之后,立即就輸出了響應頭,并接著一邊按順序讀取文件一邊輸出響應內容。并且,在讀取文件時,第二版代碼直接使用了只讀數據流來簡化代碼。

第三次迭代

  • 接下來需要從穩定性的角度重新審視一下代碼

  • 從工程角度上講,沒有絕對可靠的系統。即使第二次迭代的代碼經過反復檢查后能確保沒有bug,也很難說是否會因為NodeJS本身,或者是操作系統本身,甚至是硬件本身導致服務器程序在某一天掛掉。因此一般生產環境下的服務器程序都配有一個守護進程,在服務掛掉的時候立即重啟服務。一般守護進程的代碼會遠比服務進程的代碼簡單,從概率上可以保證守護進程更難掛掉。如果再做得嚴謹一些,甚至守護進程自身可以在自己掛掉時重啟自己,從而實現雙保險。

  • 因此在本次迭代時,先利用NodeJS的進程管理機制,將守護進程作為父進程,將服務器程序作為子進程,并讓父進程監控子進程的運行狀態,在其異常退出時重啟子進程。

var cp = require('child_process');var worker;function spawn(server, config) {worker = cp.spawn('node', [ server, config ]);worker.on('exit', function (code) {if (code !== 0) {spawn(server, config);}}); }function main(argv) {spawn('server.js', argv[0]);process.on('SIGTERM', function () {worker.kill();process.exit(0);}); }main(process.argv.slice(2));
  • 此外,服務器代碼本身的入口函數也要做以下調整。
function main(argv) {var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),root = config.root || '.',port = config.port || 80,server;server = http.createServer(function (request, response) {...}).listen(port);process.on('SIGTERM', function () {server.close(function () {process.exit(0);});}); }
  • 可以把守護進程的代碼保存為daemon.js,之后可以通過node daemon.js config.json啟動服務,而守護進程會進一步啟動和監控服務器進程。
  • 為了能夠正常終止服務,讓守護進程在接收到SIGTERM信號時終止服務器進程。而在服務器進程這一端,同樣在收到SIGTERM信號時先停掉HTTP服務再正常退出。至此,服務器程序就靠譜很多了。

第四次迭代

  • 解決了服務器本身的功能、性能和可靠性的問題后,接著需要考慮一下代碼部署的問題,以及服務器控制的問題。

  • 一般而言,程序在服務器上有一個固定的部署目錄,每次程序有更新后,都重新發布到部署目錄里。而一旦完成部署后,一般也可以通過固定的服務控制腳本啟動和停止服務。因此服務器程序部署目錄可以做如下設計。

- deploy/- bin/startws.shkillws.sh+ conf/config.json+ lib/daemon.jsserver.js
  • 在以上目錄結構中,分類存放了服務控制腳本、配置文件和服務器代碼。

  • 按以上目錄結構分別存放對應的文件之后,接下來看看控制腳本怎么寫。首先是start.sh。

#!/bin/sh if [ ! -f "pid" ] thennode ../lib/daemon.js ../conf/config.json &echo $! > pid fi
  • 然后是killws.sh。
#!/bin/sh if [ -f "pid" ] thenkill $(tr -d '\r\n' < pid)rm pid fi

后續迭代

  • 服務器程序正式上線工作后,接下來或許會發現還有很多可以改進的點。比如服務器程序在合并JS文件時可以自動在JS文件之間插入一個;來避免一些語法問題,比如服務器程序需要提供日志來統計訪問量,比如服務器程序需要能充分利用多核CPU,等等。

建議

  • 要熟悉官方API文檔。并不是說要熟悉到能記住每個API的名稱和用法,而是要熟悉NodeJS提供了哪些功能,一旦需要時知道查詢API文檔的哪塊地方。

  • 要先設計再實現。在開發一個程序前首先要有一個全局的設計,不一定要很周全,但要足夠能寫出一些代碼。

  • 要實現后再設計。在寫了一些代碼,有了一些具體的東西后,一定會發現一些之前忽略掉的細節。這時再反過來改進之前的設計,為第二輪迭代做準備。

  • 要充分利用三方包。NodeJS有一個龐大的生態圈,在寫代碼之前先看看有沒有現成的三方包能節省不少時間。

  • 不要迷信三方包。任何事情做過頭了就不好了,三方包也是一樣。三方包是一個黑盒,每多使用一個三方包,就為程序增加了一份潛在風險。并且三方包很難恰好只提供程序需要的功能,每多使用一個三方包,就讓程序更加臃腫一些。因此在決定使用某個三方包之前,最好三思而后行。

轉載于:https://www.cnblogs.com/jinkspeng/p/4396429.html

總結

以上是生活随笔為你收集整理的node.js整理 07例子的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。