NodeJSの勉強7日目:静的なファイルサーバのインストール

9318 ワード

一番簡単なWeb Serverの機能は以下の三つのステップを含みます.ステップ2:対応するファイルを取り出す.ステップ3:ファイルの内容をブラウザに転送します.しかし、この受信と転送の過程では、すべての情報は固定されたフォーマットに従って、この受信/転送フォーマットのプロトコルを規範化しなければならず、ハイパーテキスト転送プロトコル(Hyper Text Transfer Protocol)と呼ばれ、HTTPプロトコルと略称される.HTTPプロトコルフォーマットの基本は、URL上に構築された伝送方式で、簡単なHTMLファイルユーカリを初期に転送することができます.その後、拡張された後、他の種類のユーカリも転送できます.映像、動画、速報、Wordファイルなどが含まれます.本稿ではまずHTTPプロトコルの情報内容を紹介し、NodeでHTTPプロトコルを実現する方法を紹介し、簡単なWeb Serverを構築する.
HTTPプロトコル
ブラウザでURL(URL)をクリックすると、対応するWeb ServerにHTTPヘッダ情報が流れます.Web Serverはこのコンテンツを受信し、URLから対応するファイルを取り出し、HTTP形式のコンテンツをブラウザに転送します.以下はこのプロセスの一例です.ある仁兄はインターネットを利用して、ブラウザの中で打ちます.http://163.comそこで、ブラウザから次の内容を163.comというパソコンに送ります.GET/index.htm HTTP/1.0 Accept:image/gif、image/jpeg、aplication/msword、*/Accept-Language:zziga/4.0 Content-Length:Host:163.com Cachece-Contront:maxage 9200上のコンテンツを指定します.そして、予め設定されたウェブルートディレクトリ(c:\web\)に基づいて、c:\web\index.htmの絶対パスを合成して、ハードディスクからこのファイルを取り出して、次の内容をその仁兄のブラウザに転送します.HTTP/1.0 200 OK Content-Type:text/html Conteet Length:438<httml>  .... &ボルトhttml>最初の行はHTTP/1.0 200 OKです.このページが正常に転送されたことを表しています.第二行はContent-Type:text/htmlを表しています.HTMLファイルのサイズは438バイトです.
時間を延ばして読む
P.S:諸般の事情により、弟は久しぶりにNodeJSに接触しました.実は私はNodeJSに対してとても興味があります.そこで今日は時間があるうちに、IIS/IIS Express、またはAppacheに対して「蓄積された感情」を持って、NodeJSに基づく静的なサーバーを作ろうと決心しました.
ははは、NodeJSの静的なサーバーといえば、先輩たちはすでに多くの実践を持っています.そして、みんなと共有する文章を書きました.次のように列挙してみます.
  • 「Node.js静的ファイルサーバ実戦」朴霊の大きなNode文章は、非常に詳細であり、言ってはいけない.http://www.infoq.com/cn/news/2011/11/tyq-nodejs-static-file-server
  • 「静止サーバを完成します.Node.js石シリーズの4」、他にもnodeに関する文章があります.http://www.cnblogs.com/hsxixi/archive/2011/12/23/2298507.html
  • 『node.js入門--静的ファイルサーバ』はGZIPを実現しました.http://www.jiangkunlun.com/2012/09/nodejs_%E 9%9 D%99%E 6%80%81_%E 6%9 C%8 D%E 5%8 A%A 1%E 5%99%A 8/
  • Nodeの上に静止サーバを実現するのは難しいことではないはずです.上の3つのリンクは、Web Serverに慣れていない、またはNodeについてよく知らない友達にこれらの原理の知識を理解してもらいたいだけです.もちろんGoogle/Baiduにも深く入ることができます.もしあなたが私と同じように、ソースを読むことで、Node静的サーバとはどういうものかを知ることが好きでしたら、私とソースコードの旅に出てください.本文はWinXP+Node 0.0.0.6221を通して、サーバーのソースコードはAndy Greenのオープンソースプロジェクトを選択し、主なリソースリンクは以下の通りです.
  • nodejsをインストールする:http://nodejs.org/dist/v0.6.21/node.msi
  • ダウンロードサーバーのソースコード:https://github.com/andygrn/Node.js-File-Server
  • サーバファイルServer.jsにコメントを付けます.
    /*
    
    	Node.js File Server
    	Andy Green
    	http://andygrn.co.uk
    	November 2011
    
    */
    
    'use strict';
    
    //     。       ,       !
    var CONFIG = {
    
    	'host': '127.0.0.1',			//      
    	'port': 80,				//   
    	
    	'site_base': './site', 			//    ,        
    	
    	'file_expiry_time': 480, 		//      HTTP cache expiry time, minutes
    	
    	'directory_listing': true 		//           
    
    };
    
    //           ,       。
    var MIME_TYPES = {
    
    	'.txt': 'text/plain',
    	'.md': 'text/plain',
    	'': 'text/plain',
    	'.html': 'text/html',
    	'.css': 'text/css',
    	'.js': 'application/javascript',
    	'.json': 'application/json',
    	'.jpg': 'image/jpeg',
    	'.png': 'image/png',
    	'.gif': 'image/gif'
    
    };
    
    //       
    var EXPIRY_TIME = (CONFIG.file_expiry_time * 60).toString();
    
    //     ,   CUSTARD        ,   NODE      。 
    var HTTP = require('http');
    var PATH = require('path');
    var FS = require('fs');
    var CRYPTO = require('crypto');
    var CUSTARD = require('./custard');
    	
    var template_directory = FS.readFileSync('./templates/blocks/listing.js');
    
    
    //      An object representing a server response
    
    function ResponseObject( metadata ){
    
    	this.status = metadata.status || 200;
    	this.data = metadata.data || false;
    	this.type = metadata.type || false;
    
    }
    
    //    HTTP Meta   Etag。     md5     
    ResponseObject.prototype.getEtag = function (){
    	var hash = CRYPTO.createHash( 'md5' );
    	hash.update( this.data );
    	return hash.digest( 'hex' );
    };
    
    
    // Filter server requests by type
    
    function handleRequest( url, callback ){
    	//    url        ,     
    	if ( PATH.extname( url ) === '' ){
    		getDirectoryResponse( url, function ( response_object ){
    			callback( response_object );
    		} );
    	}
    	else {
    	//    url      +      ,       
    		getFileResponse( url, function ( response_object ){
    			callback( response_object );
    		} );
    	}
    
    }
    
    
    //         Creates a ResponseObject from a local file path
    
    function getFileResponse( path, callback ){
    
    	var path = CONFIG.site_base + path;
    
    	PATH.exists( path, function ( path_exists ){
    		if ( path_exists ){
    			FS.readFile( path, function ( error, data ){
    				if ( error ){
    //					Internal error
    					callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );
    				}
    				else {
    					//          Response 
    					callback( new ResponseObject({
    							 'data': new Buffer( data )
    							,'type': MIME_TYPES[PATH.extname(path)]
    						}) 
    					);
    				}
    			} );
    		}
    		else {
    //			Not found
    			callback( new ResponseObject( {'status': 404} ) );
    		}
    	} );
    
    }
    
    
    //         Creates a ResponseObject from a local directory path
    
    function getDirectoryResponse( path, callback ){
    
    	var full_path = CONFIG.site_base + path;	//     
    	var template;
    	var i;
    
    	if ( CONFIG.directory_listing ){
    		PATH.exists( full_path, function ( path_exists ){
    			if ( path_exists ){
    				FS.readdir( full_path, function ( error, files ){
    					if ( error ){
    //						Internal error
    						callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );
    					}
    					else {
    						//     
    //						Custard template
    						template = new CUSTARD;
    						
    						template.addTagSet( 'h', require('./templates/tags/html') );
    						template.addTagSet( 'c', {
    							'title': 'Index of ' + path,
    							'file_list': function ( h ){
    								var items = [];
    								var stats;
    								for ( i = 0; i < files.length; i += 1 ){
    									stats = FS.statSync( full_path + files[i] );
    									if ( stats.isDirectory() ){
    										files[i] += '/';
    									}
    									items.push( h.el( 'li', [
    										h.el( 'a', {'href': path + files[i]}, files[i] )
    									] ) );
    								}
    								return items;
    							}
    						} );
    						
    						template.render( template_directory, function ( error, html ){
    							if ( error ){
    //								Internal error
    								callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );
    							}
    							else {
    								callback( new ResponseObject( {'data': new Buffer( html ), 'type': 'text/html'} ) );
    							}
    						} );
    					}
    				} );
    			}
    			else {
    				//       ,   404
    //				Not found
    				callback( new ResponseObject( {'status': 404} ) );
    			}
    		} );
    	} else {
    		//        ,   403
    //		Forbidden
    		callback( new ResponseObject( {'status': 403} ) );
    	}
    
    }
    
    
    //       Start server
    
    HTTP.createServer( function ( request, response ){
    
    	var headers;
    	var etag;
    	
    	if ( request.method === 'GET' ){ //           HTTP GET    
    //		Get response object
    		handleRequest( request.url, function ( response_object ){
    			if ( response_object.data && response_object.data.length > 0 ){
    				etag = response_object.getEtag();
    				//     ,   304
    				if ( request.headers.hasOwnProperty('if-none-match') && request.headers['if-none-match'] === etag ){
    //					Not Modified
    					response.writeHead( 304 );
    					response.end();
    				}
    				//   
    				else {
    					headers = {
    						'Content-Type': response_object.type,
    						'Content-Length' : response_object.data.length,
    						'Cache-Control' : 'max-age=' + EXPIRY_TIME,
    						'ETag' : etag
    					};
    					response.writeHead( response_object.status, headers );
    					response.end( response_object.data );
    				}
    			}
    			else {
    				response.writeHead( response_object.status );
    				response.end();
    			}
    		} );
    	}
    	else {
    //		Forbidden
    		response.writeHead( 403 );
    		response.end();
    	}
    
    } ).listen( CONFIG.port, CONFIG.host ); //     
    
    console.log( 'Site Online : http://' + CONFIG.host + ':' + CONFIG.port.toString() + '/' );
    
    ソースをざっと見た後、まず読みやすいと感じました.機能上の議論から、ディレクトリ読み取り、MIMEタイプ、404ページ、HTTPキャッシュの機能を実現しました.また、個人的にはHTTPプロトコルの内容をもう一度復習しました.つまり、この小さな静的文書サーバーの例ですが、250行は「雀は小さいが、五臓がそろっている」と言えます.
    ps:GZip実装の方法(上記のurlを参照して、最後の一つ):
    var zlib = require('zlib');
    
    ...
    
    //   /  /  
    function readFile(req, res, realPath, header, type){
      var raw = fs.createReadStream(realPath), cFun;
      //  gzip
      if(setting.compress && setting.compress.match
          && type.match(setting.compress.match) && req.headers['accept-encoding']){
        if(req.headers['accept-encoding'].match(/\bgzip\b/)){
          header['Content-Encoding'] = 'gzip';
          cFun = 'createGzip';
        }else if(req.headers['accept-encoding'].match(/\bdeflate\b/)){
          header['Content-Encoding'] = 'deflate';
          cFun = 'createDeflate';
        }
      }
      res.writeHead(200, header);
      if(cFun){
        raw.pipe(zlib[cFun]()).pipe(res);
      }else{
        raw.pipe(res);
      }
    }
    2012-11-02: 朴霊によると、「実際の作業で静的なファイルサーバが必要なら、npm install anywhere-gを任意のディレクトリの下でAnywhereコマンドを実行すれば、このディレクトリを静的なファイルサーバのルートディレクトリに変更できますよ~」