NodeJs 安装静态的文件服务

来源:互联网 发布:python宝典 pdf 编辑:程序博客网 时间:2024/05/21 22:50

学习NodeJS第七天:安装静态的文件服务器

 13141人阅读 评论(4) 收藏 举报
 分类:

目录(?)[+]

  一个最简单的 Web Server 之功能包含下列三个步骤:步骤一 : 接收浏览器所传来的网址;步骤二 : 取出相对应的文件;步骤三 : 将文件内容传回给浏览器。然而、在这个接收与传回的过程中,所有的资讯都必须遵照固定的格式,规范这个接收/传送格式的协议,称为超文字传送协议 (Hyper Text Transfer Protocol),简称为 HTTP 协议。HTTP 协议格式的基础,乃是建构在网址 URL 上的传输方式,早期只能用来传送简单的 HTML 档桉,后来经扩充后也可以传送 其他类型的档桉,包含 影像、动画、简报、Word 文件等。

在本文中,我们将先简介 HTTP 协议的讯息内容,然后在介绍如何以 Node 实现 HTTP 协议,以建立一个简单的 Web Server。

HTTP 协议

当你在浏览器上打上网址(URL)后,浏览器会传出一个 HTTP 头信息给对应的 Web Server,Web Server 再接收到这个内容后, 根据网址取出对应的文件,并将该文件以 HTTP 格式的内容传回给浏览器,以下是这个过程的一个范例。

某仁兄上网,在浏览器中打上 http://163.com,于是,浏览器传送下列内容给 163.com 这台电脑。

GET /index.htm HTTP/1.0
Accept: image/gif, image/jpeg, application/msword, */*
Accept-Language: zh-ch
User-Agent: Mozilla/4.0
Content-Length: 
Host: 163.com 
Cache-Control: max-age=259200
Connection: keep-alive

当 163.com 电脑上的 Web Server 程序收到上述内容后,会取出指定的路径 /index.htm ,然后根据预设的网页根目录 (假设为 c:\web\),合成一个 c:\web\index.htm 的绝对路径,接着从硬盘中取出该文件,并传回下列内容给那位仁兄的浏览器。

HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 438
<html>
  ....
</html>

其中第一行 HTTP/1.0 200 OK 代表该网页被成功传回,第二行 Content-Type: text/html 代表传回文件为 HTML 文件, Content-Length: 438 代表该 HTML 文件的大小为 438 位字节。

延时阅读

P.S:由于诸多原因的关系,小弟已经很久没怎么接触 NodeJS 了。其实我对 NodeJS 不但非常感兴趣,而且还十分看好。于是今天趁有时间,并挟持着对 IIS / IIS Express、又或者 Apache 它们“累积已久的情绪”,决心打造一个基于 NodeJS 的静态服务器!

哈哈,要说 NodeJS 的静态服务器,前辈们已有诸多实践,并都付之笔墨与大家共享,尝试列举如下:

  • 《Node.js静态文件服务器实战》朴灵大大的 Node 文章,非常详细,不可不说
    http://www.infoq.com/cn/news/2011/11/tyq-nodejs-static-file-server
  • 《完成静态服务器——Node.js摸石头系列之四》,还有其他关于 node 的文章
    http://www.cnblogs.com/hsxixi/archive/2011/12/23/2298507.html
  • 《node.js入门—-静态文件服务器》,实现了 GZIP,这点要学习,,
    http://www.jiangkunlun.com/2012/09/nodejs_%E9%9D%99%E6%80%81_%E6%9C%8D%E5%8A%A1%E5%99%A8/

在 Node 上面实现一个静态服务器应该不是一件很难的事情。以上三个链接只是打算给尚不熟悉 Web Server 或者对 Node 不太了解的朋友去了解一下那些原理的知识,当然还可以深入地 Google/Baidu 之。如果你和我一样,喜欢通过阅读源码来了解 Node 静态服务器是怎么一回事的话,那请您和我走一趟源码之旅(附注释)。本文在 WinXP + Node 0.6.21下通过,服务器源码选用 Andy Green 的开源项目,主要的资源链接如下:

  • 安装 nodejs: http://nodejs.org/dist/v0.6.21/node.msi
  • 下载服务器源码: https://github.com/andygrn/Node.js-File-Server
服务器文件 Server.js,加上注释:

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  
  3.     Node.js File Server 
  4.     Andy Green 
  5.     http://andygrn.co.uk 
  6.     November 2011 
  7.  
  8. */  
  9.   
  10. 'use strict';  
  11.   
  12. // 配置对象。使用对象来配置,不错的编程方法!  
  13. var CONFIG = {  
  14.   
  15.     'host''127.0.0.1',            // 服务器地址  
  16.     'port': 80,             // 端口  
  17.       
  18.     'site_base''./site',          // 根目录,虚拟目录的根目录  
  19.       
  20.     'file_expiry_time': 480,        // 缓存期限 HTTP cache expiry time, minutes  
  21.       
  22.     'directory_listing'true       // 是否打开 文件 列表  
  23.   
  24. };  
  25.   
  26. // 当前支持的 文件类型,你可以不断扩充。  
  27. var MIME_TYPES = {  
  28.   
  29.     '.txt''text/plain',  
  30.     '.md''text/plain',  
  31.     '''text/plain',  
  32.     '.html''text/html',  
  33.     '.css''text/css',  
  34.     '.js''application/javascript',  
  35.     '.json''application/json',  
  36.     '.jpg''image/jpeg',  
  37.     '.png''image/png',  
  38.     '.gif''image/gif'  
  39.   
  40. };  
  41.   
  42. // 缓存过期时限  
  43. var EXPIRY_TIME = (CONFIG.file_expiry_time * 60).toString();  
  44.   
  45. // 依赖模块,注意 CUSTARD 是自定义的模块,不是 NODE 类库自带的。   
  46. var HTTP = require('http');  
  47. var PATH = require('path');  
  48. var FS = require('fs');  
  49. var CRYPTO = require('crypto');  
  50. var CUSTARD = require('./custard');  
  51.       
  52. var template_directory = FS.readFileSync('./templates/blocks/listing.js');  
  53.   
  54.   
  55. // 响应对象 An object representing a server response  
  56.   
  57. function ResponseObject( metadata ){  
  58.   
  59.     this.status = metadata.status || 200;  
  60.     this.data = metadata.data || false;  
  61.     this.type = metadata.type || false;  
  62.   
  63. }  
  64.   
  65. // 返回 HTTP Meta 的 Etag。可以了解 md5 加密方法  
  66. ResponseObject.prototype.getEtag = function (){  
  67.     var hash = CRYPTO.createHash( 'md5' );  
  68.     hash.update( this.data );  
  69.     return hash.digest( 'hex' );  
  70. };  
  71.   
  72.   
  73. // Filter server requests by type  
  74.   
  75. function handleRequest( url, callback ){  
  76.     // 如果 url 只是 目录 的,则列出目录  
  77.     if ( PATH.extname( url ) === '' ){  
  78.         getDirectoryResponse( url, function ( response_object ){  
  79.             callback( response_object );  
  80.         } );  
  81.     }  
  82.     else {  
  83.     // 如果 url 是 目录 + 文件名 的,则返回那个文件  
  84.         getFileResponse( url, function ( response_object ){  
  85.             callback( response_object );  
  86.         } );  
  87.     }  
  88.   
  89. }  
  90.   
  91.   
  92. // 处理文件的函数 Creates a ResponseObject from a local file path  
  93.   
  94. function getFileResponse( path, callback ){  
  95.   
  96.     var path = CONFIG.site_base + path;  
  97.   
  98.     PATH.exists( path, function ( path_exists ){  
  99.         if ( path_exists ){  
  100.             FS.readFile( path, function ( error, data ){  
  101.                 if ( error ){  
  102. //                  Internal error  
  103.                     callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );  
  104.                 }  
  105.                 else {  
  106.                     // 读取 文件 返回 Response   
  107.                     callback( new ResponseObject({  
  108.                              'data'new Buffer( data )  
  109.                             ,'type': MIME_TYPES[PATH.extname(path)]  
  110.                         })   
  111.                     );  
  112.                 }  
  113.             } );  
  114.         }  
  115.         else {  
  116. //          Not found  
  117.             callback( new ResponseObject( {'status': 404} ) );  
  118.         }  
  119.     } );  
  120.   
  121. }  
  122.   
  123.   
  124. // 处理目录的方法 Creates a ResponseObject from a local directory path  
  125.   
  126. function getDirectoryResponse( path, callback ){  
  127.   
  128.     var full_path = CONFIG.site_base + path;    // 完整路径  
  129.     var template;  
  130.     var i;  
  131.   
  132.     if ( CONFIG.directory_listing ){  
  133.         PATH.exists( full_path, function ( path_exists ){  
  134.             if ( path_exists ){  
  135.                 FS.readdir( full_path, function ( error, files ){  
  136.                     if ( error ){  
  137. //                      Internal error  
  138.                         callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );  
  139.                     }  
  140.                     else {  
  141.                         // 列出结果  
  142. //                      Custard template  
  143.                         template = new CUSTARD;  
  144.                           
  145.                         template.addTagSet( 'h', require('./templates/tags/html') );  
  146.                         template.addTagSet( 'c', {  
  147.                             'title''Index of ' + path,  
  148.                             'file_list'function ( h ){  
  149.                                 var items = [];  
  150.                                 var stats;  
  151.                                 for ( i = 0; i < files.length; i += 1 ){  
  152.                                     stats = FS.statSync( full_path + files[i] );  
  153.                                     if ( stats.isDirectory() ){  
  154.                                         files[i] += '/';  
  155.                                     }  
  156.                                     items.push( h.el( 'li', [  
  157.                                         h.el( 'a', {'href': path + files[i]}, files[i] )  
  158.                                     ] ) );  
  159.                                 }  
  160.                                 return items;  
  161.                             }  
  162.                         } );  
  163.                           
  164.                         template.render( template_directory, function ( error, html ){  
  165.                             if ( error ){  
  166. //                              Internal error  
  167.                                 callback( new ResponseObject( {'data': error.stack, 'status': 500} ) );  
  168.                             }  
  169.                             else {  
  170.                                 callback( new ResponseObject( {'data'new Buffer( html ), 'type''text/html'} ) );  
  171.                             }  
  172.                         } );  
  173.                     }  
  174.                 } );  
  175.             }  
  176.             else {  
  177.                 // 找不到 文件,就是 404  
  178. //              Not found  
  179.                 callback( new ResponseObject( {'status': 404} ) );  
  180.             }  
  181.         } );  
  182.     } else {  
  183.         // 禁止 目录浏览,返回 403  
  184. //      Forbidden  
  185.         callback( new ResponseObject( {'status': 403} ) );  
  186.     }  
  187.   
  188. }  
  189.   
  190.   
  191. // 启动服务器 Start server  
  192.   
  193. HTTP.createServer( function ( request, response ){  
  194.   
  195.     var headers;  
  196.     var etag;  
  197.       
  198.     if ( request.method === 'GET' ){ // 静态服务服务器都是 HTTP GET 方法的  
  199. //      Get response object  
  200.         handleRequest( request.url, function ( response_object ){  
  201.             if ( response_object.data && response_object.data.length > 0 ){  
  202.                 etag = response_object.getEtag();  
  203.                 // 命中缓存,返回 304  
  204.                 if ( request.headers.hasOwnProperty('if-none-match') && request.headers['if-none-match'] === etag ){  
  205. //                  Not Modified  
  206.                     response.writeHead( 304 );  
  207.                     response.end();  
  208.                 }  
  209.                 // 请求  
  210.                 else {  
  211.                     headers = {  
  212.                         'Content-Type': response_object.type,  
  213.                         'Content-Length' : response_object.data.length,  
  214.                         'Cache-Control' : 'max-age=' + EXPIRY_TIME,  
  215.                         'ETag' : etag  
  216.                     };  
  217.                     response.writeHead( response_object.status, headers );  
  218.                     response.end( response_object.data );  
  219.                 }  
  220.             }  
  221.             else {  
  222.                 response.writeHead( response_object.status );  
  223.                 response.end();  
  224.             }  
  225.         } );  
  226.     }  
  227.     else {  
  228. //      Forbidden  
  229.         response.writeHead( 403 );  
  230.         response.end();  
  231.     }  
  232.   
  233. } ).listen( CONFIG.port, CONFIG.host ); // 读取配置  
  234.   
  235. console.log( 'Site Online : http://' + CONFIG.host + ':' + CONFIG.port.toString() + '/' );  
粗略浏览源码后,首先感觉清晰可读,再则从功能上议,它已经实现了 目录读取、MIME 类型、404 页面还有HTTP 缓存的功能,另外于我个人而言,又再一次温习了 HTTP 协议内容,对于深入理解 缓存 也就是 Etag 的使用很有帮助。总之,这个小小的静态文件服务器例子,不过 250 行,可谓“麻雀虽小,五脏俱全”,呵呵,这还得拜强大的 NodeJS 所赐!

ps: GZip 实现的方法(参考上述提到的 url,最后一个):

[javascript] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. var zlib = require('zlib');  
  2.   
  3. ...  
  4.   
  5. //读文件/压缩/输出  
  6. function readFile(req, res, realPath, header, type){  
  7.   var raw = fs.createReadStream(realPath), cFun;  
  8.   //是否gzip  
  9.   if(setting.compress && setting.compress.match  
  10.       && type.match(setting.compress.match) && req.headers['accept-encoding']){  
  11.     if(req.headers['accept-encoding'].match(/\bgzip\b/)){  
  12.       header['Content-Encoding'] = 'gzip';  
  13.       cFun = 'createGzip';  
  14.     }else if(req.headers['accept-encoding'].match(/\bdeflate\b/)){  
  15.       header['Content-Encoding'] = 'deflate';  
  16.       cFun = 'createDeflate';  
  17.     }  
  18.   }  
  19.   res.writeHead(200, header);  
  20.   if(cFun){  
  21.     raw.pipe(zlib[cFun]()).pipe(res);  
  22.   }else{  
  23.     raw.pipe(res);  
  24.   }  
  25. }  

2012-11-02: 经朴灵大大指点,可以“如果在实际工作中需要用到静态文件服务器的话,npm install anywhere -g 然后在你的任意目录下执行anywhere命令就可以把这个目录变成一个静态文件服务器的根目录哦~。”,十分方便的说~

0 0
原创粉丝点击