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

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

服务器文件 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 协议内容,对于深入理解 缓存 也就是 Etag 的使用很有帮助。总之,这个小小的静态文件服务器例子,不过 250 行,可谓“麻雀虽小,五脏俱全”,呵呵,这还得拜强大的 NodeJS 所赐!

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命令就可以把这个目录变成一个静态文件服务器的根目录哦~。”,十分方便的说~

时间: 2024-09-20 00:57:21

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

学习NodeJS第三天:打造Nodejs的调试环境(下)

Nodejs我了解的情况不多.知之不多,就胡言乱语,不仅轻浮,而且有误导的嫌疑,罪莫大焉.能做的是整理一些相关资源,供大家参考讨论. 安装静态的文件服务器 https://github.com/andygrn/Node.js-File-Server 下面以 windows 用户为例, 安装 nodejs http://nodejs.org/dist/v0.6.21/node.msi 安装 python http://www.python.org/getit/ 安装 VS 2010(需要msbui

nodejs express-关于nodejs中的express安装之后启动app.js

问题描述 关于nodejs中的express安装之后启动app.js 在学习nodejs时,安装了一个express,所有的模块安装完成之后,在命令行里输入启动服务的代码node app.js,然后在浏览器输入127.0.0.1:3000,结果链接打不开,上网查了好久,原来新版本的express,用node start就可以启动服务了,

学习NodeJS第三天:打造Nodejs的调试环境(中)

2012-12-07 因追加<学习 NodeJS 第三天:打造 Nodejs 的调试环境(下)>的缘故,特此将原来的<下>篇改为<中>篇,如标题所示. 上一期我们为大家介绍了安装 Eclipse 调试插件的情况,这对于还不熟悉 Eclipse 开发平台的用户是至关重要的,希望可以通过一步步的图片加文字说明,把 Nodejs 困难的地方变简单和清晰.友好和轻松. 现在正式进入要调试程序肯定要有调试代码.下面就是我们第一个测试的代码,很小的行数: var sys = req

phpmyadmin: linux学习篇-使用apt-get方式安装LAMP包括phpmyadmin

linux学习篇-使用apt-get方式安装LAMP包括phpmyadmin    对于想学linux的朋友来说,在虚拟机上装上linux系统是不二选择.至于哪种linux好用,萝卜白菜,各有所爱.我喜欢ubuntu,虚拟机上安装的是最新的ubuntu11.10    搭建LAMP环境有两种方式可以选择,一是用apt-get方式安装,这个适合初学者.有点是安装简单,不需要做什么配置就可以使用.缺点是自主性太差,安装目录啊啥的都是默认的,不方便自己管理,也不敢轻易的移动位置.二是用编译安装的方法.

WPF and Silverlight学习笔记(七)

WPF and Silverlight学习笔记(七):WPF布局管理之StackPanel.WrapPanel.DockPanel 一.StackPanel StackPanel是以堆叠的方式显示其中的控件 1 .可以使用Orientation属性更改堆叠的顺序 Orientation="Vertical" 默认,由上到下显示各控件 .控件在未定义的前提下,宽度为StackPanel的宽度,高度自动适应控件中内容 的高度 1: <StackPanel Orientation=&q

VSTO学习笔记(七)基于WPF的Excel分析、转换小程序

原文:VSTO学习笔记(七)基于WPF的Excel分析.转换小程序 近期因为工作的需要,要批量处理Excel文件,于是写了一个小程序,来提升工作效率. 小程序的功能是对Excel进行一些分析.验证,然后进行转换.   概述 小程序主界面如下: 首先选择一个日期和类别,从命名上对待分析的Excel文件进行过滤.点击[浏览]选择待分析的Excel文件所在的目录, 程序中会获取所有子目录.然后点击[执行分析]就会按照左边CheckBox中的选择进行分析,分析结果显示在每一行中间.[修改配置]可以对分析

我的MYSQL学习心得(七) 查询

在这个<我的MYSQL学习心得>系列里面,我一直都把MYSQL跟SQLSERVER进行比较,相互进行比较是学习一样东西比较好的方法 比较出大家的异同点,从而加深记忆 这一篇<我的MYSQL学习心得(七)>也是一样,相同的地方略略带过,不同的地方我会给出例子,谢谢大家的支持o(∩_∩)o      这一节主要介绍MYSQL里的基本查询(MYSQL官方参考手册) MySQL中select的基本语法形式: select 属性列表 from 表名和视图列表 [where 条件表达式] [g

我的MYSQL学习心得(七)

原文:我的MYSQL学习心得(七) 我的MYSQL学习心得(七) 我的MYSQL学习心得(一) 我的MYSQL学习心得(二) 我的MYSQL学习心得(三) 我的MYSQL学习心得(四) 我的MYSQL学习心得(五) 我的MYSQL学习心得(六)   在这个<我的MYSQL学习心得>系列里面,我一直都把MYSQL跟SQLSERVER进行比较,相互进行比较是学习一样东西比较好的方法 比较出大家的异同点,从而加深记忆 这一篇<我的MYSQL学习心得(七)>也是一样,相同的地方略略带过,不

Akka学习笔记(七):配置

Akka学习笔记(七):配置 使用Akka可以不用任何配置,Akka提供了明智的默认配置.为了适应特别的运行环境,修改默认行为,你可能需要修改: log level and logger backend enable remoting 消息系列化 路由设置 调度器调优 Akka使用Typesafe Config Library,纯java实现的配置库.之前博客有介绍过here 从哪里读取配置 Akka的所有配置信息装在 ActorSystem的实例中, 或者换个说法, 从外界看来, ActorS