上一篇文章,我們已經實現了客戶端向NodeJS服務器發出請求時,服務器從磁盤讀取文件內容后,向客戶端返回文件的數據。而對於愛蓮(iLinkIT)的1對n的場景,即將文件共享出來之后,讓多個用戶同時下載,如果每個用戶發起請求,我們都重新去磁盤讀一下文件,那樣豈不是效率低下?本文將重點改進一下效率和體驗的問題。
老規矩,先上一個圖:
因為對於一個具體的共享任務,文件是同一個的,我們可以只做一次讀取文件的操作,把讀取進來的文件數據先保存到緩沖區。當有客戶端發送請求時,就從緩沖區讀取數據響應對應的客戶端。
代碼如下:
1 var http = require( 'http' ); 2 var fs = require('fs'); 3 4 var file_path = "D:\\ilinkit_logo.png" ; 5 var file_stream ; 6 var buffer_box = [] ; 7 var file_length = 0 ; 8 9 fs.stat( file_path , function ( err , stat ){ 10 if (err) { 11 if ('ENOENT' == err.code) { 12 console.log( 'File does not exist...' ); 13 } else { 14 console.log( 'Read file exception...' ); 15 } 16 } else { 17 file_stream = fs.createReadStream( file_path ); 18 file_stream.on( 'data' , function( chunk ){ 19 buffer_box.push( chunk ) ; 20 file_length += chunk.length ; 21 } ); 22 file_stream.on( 'end' , function( ){ 23 console.log( "文件讀取完畢" ); 24 } ); 25 file_stream.on('error', function(err){ 26 console.log( "文件讀取失敗!" ); 27 }); 28 29 var server =http.createServer( function ( request ,response ){ 30 for( var buffer_index = 0 ; buffer_index<buffer_box.length ; buffer_index++ ) 31 { 32 response.write( buffer_box[buffer_index] ); 33 } 34 response.end(); 35 } ); 36 server.listen( 8000 ); 37 console.log( 'HTTP服務器啟動中,端口:8000.....' ); 38 39 }//end else,讀取文件沒有發生錯誤 40 });
先解釋一下整體的框架調整。從第9行~第40行,都是用fs.stat檢查文件的相關信息,如果文件存在不存在,或者其他異常(例如:當前用戶無權限讀取該文件等。),就什么也不做,退出程序。如果讀取文件正確,則創建一個HTTP服務器(第29行~第36行)。
關鍵代碼解析如下:
第6行和第7行,聲明保存文件數據的緩沖區的相關變量。
第19行和第20行,將文件的數據保存到緩沖區中。
第30行~第34行,當有客戶端有發送請求時,從緩沖區讀取數據,通過response對象向客戶端傳送數據。
驗證方式如下:
1. 啟動服務器:打開命令行,進入js腳本所在的位置,執行:node f_ilinkit_1.js。
2. 打開瀏覽器,輸入:http://localhost:8000,顯示如下:
改進1:
建立了緩沖區,客戶端提交請求之后,直接從緩沖區返回數據,貌似整個過程已經很完美了,但是,其實,咱們的程序還存在致命的問題。我們在向客戶端響應數據的時候,是非常簡單地調用 response.write( buffer_box[buffer_index] ); 來響應,但是,知道HTTP協議的同學都知道,如果用Web服務器的標准來看,客戶端和服務器之間的響應,是還需要進行“響應頭”的設置的,這樣,客戶端才知道接收到的數據是什么類型?應該如何處理?
因為我們之前用來測試的文件是D:\ilinkit_logo.png,客戶端(瀏覽器)接收到一個圖片的文件之后,就把它顯示在瀏覽器中了,如果我們共享的文件是zip文件,是rar文件,會發生什么呢?所以,服務器在向客戶端發送數據時,應該設置“響應頭”的內容,讓客戶端把當前的數據當作一個附件來處理,這也符合我們愛蓮(iLinkIT)的業務場景。改進后的代碼如下:
1 var http = require( 'http' ); 2 var fs = require('fs'); 3 4 var file_path = "D:\\ilinkit_logo.rar" ; 5 var file_stream ; 6 var buffer_box = [] ; 7 var file_length = 0 ; 8 9 var file_name = file_path.substr( file_path.lastIndexOf('\\')+1 ); 10 11 fs.stat( file_path , function ( err , stat ){ 12 if (err) { 13 if ('ENOENT' == err.code) { 14 console.log( 'File does not exist...' ); 15 } else { 16 console.log( 'Read file exception...' ); 17 } 18 } else { 19 file_stream = fs.createReadStream( file_path ); 20 file_stream.on( 'data' , function( chunk ){ 21 buffer_box.push( chunk ) ; 22 file_length += chunk.length ; 23 } ); 24 file_stream.on( 'end' , function( ){ 25 console.log( "文件讀取完畢" ); 26 } ); 27 file_stream.on('error', function(err){ 28 console.log( "文件讀取失敗!" ); 29 }); 30 31 var server =http.createServer( function ( request ,response ){ 32 response.setHeader( 'Content-Type' , 'application/octet-stream' ); 33 response.setHeader( 'Content-Disposition' , 'attachment; filename=' + encodeURIComponent(file_name) ); 34 35 for( var buffer_index = 0 ; buffer_index<buffer_box.length ; buffer_index++ ) 36 { 37 response.write( buffer_box[buffer_index] ); 38 } 39 response.end(); 40 } ); 41 server.listen( 8000 ); 42 console.log( 'HTTP服務器啟動中,端口:8000.....' ); 43 44 }//end else,讀取文件沒有發生錯誤 45 });
關鍵的改進點說明如下:
第4行,共享的文件,我們把ilinkit_logo.png修改為ilinkit_logo.rar。當然,在D:下應該放一個ilinkit_logo.rar的文件。
第9行,我們從共享的文件路徑中,解析出文件名(例子中就是:ilinkit_logo.rar),用於向客戶端響應時,告知當前附件的文件名。
第32行和第33行,在向客戶端提供文件數據之前,先設置響應的內容的類型(Content-Type)和內容特點(Content-Disposition),告訴客戶端,要將接收到數據當附件處理,文件名為ilinkit_logo.rar。
驗證方式如下:
1. 先將ilinkit_logo.png壓縮成ilinkit_logo.rar,壓縮后的文件依然放到D:下面。
2. 啟動服務器:打開命令行,進入js腳本所在的位置,執行:node f_ilinkit_2.js。
3. 打開瀏覽器,輸入:http://localhost:8000,顯示如下:
我們服務器里提供的共享文件是ilinkit_logo.rar,所以,用客戶端訪問,服務器就向客戶端響應一個附件文件,其實,上面的代碼中,我們如果把ilinkit_logo.rar修改為ilinkit_logo.png,瀏覽器收到文件數據之后,仍然會把它當“附件”處理,而不會顯示在瀏覽器中,因為我們設置了響應頭的內容。
共享文件修改為ilinkit_logo.png的時候,瀏覽器下載效果如下:
【要點回顧】
今天的解說就到這里,我們一起來回顧一下要點:
1. 改進了響應數據的方式,將要共享的文件先讀取到緩沖區,提高響應效率。
2. 通過設置“響應頭”,告知客戶端要將接收到的數據,當“附件”處理。
感謝諸位捧場,歡迎多提寶貴建議,謝謝^_^~~
-----------------------愛蓮(iLinkIT)系列文章------------------------------------------