您的位置:首页 > Web前端 > Node.js

Node.js读书笔记

2015-10-10 21:13 567 查看
七天学会Node.js   http://nqdeng.github.io/7-days-nodejs/#1.5.6 Node.js入门       http://www.nodebeginner.org/index-zh-cn.html Node.js资源汇总   https://cnodejs.org/topic/54128625351649283bcc5b30 1.Node.js   --  是一个可以让JavaScript运行在服务器端的开发平台,充分考虑了实时响应、超大规模数据要求下架构的可扩展性,
采用了单线程、异步式I/O、事件驱动的程序设计模型,带来了巨大的性能提升,减少了多线程程序设计的复杂性,提高了开发效率。
2.Node.js能做什么?
Node.js为网络而生,主要用于以下领域的开发
1.具有复杂逻辑的网站
2.基于社交网络的大规模Web应用
3.Web Socket服务器
4.TCP/UDP套接字应用程序
5.命令行工具和交互式终端程序
6.待遇图形用户界面的本地应用程序
说明:
Node.js内建了HTTP服务器支持,即可以轻而易举地实现一个网站和服务器的组合

3.异步式I/O与事件驱动架构设计代替多线程,提升性能     --- Node.js最大的特点
对于高并发解决方案:
传统的架构:多线程模型,为每个业务逻辑提供一个系统线程,通过系统线程切换来弥补同步式I/O调用时的时间开销
Node.js:   采用单线程模型,对于所有的I/O都采用异步式的请求方式,避免了频繁的上下文切换,
Node.js在执行过程中会维护一个事件队列,程序在执行时进入事件循环等待下一个事件到来,每个异步式I/O请求完成后会被推送到事件队列,等待程序进程进行处理
Node.js的异步机制是基于事件的,所有的磁盘IO,、网络通信、数据库查询都以非阻塞方式请求,返回的结果由事件循环来处理

4.安装Node.js
1.Windows下直接下载安装包即可,会自动安装node.js和npm
2.Linux下 需要分别安装node.js和npm包管理器
sudo apt-get install node
sudo apt-get install npm
5.运行Node.js程序
1.node命令进入perl命令模式,在里面执行程序
2.创建xx.js,进入到该目录,运行命令  node xx.js 运行程序
3.node -e "console.log('hello node.js')" 通过 -e参数,直接运行参数后的程序段
6.建立HTTP服务器
1.创建app.js文件
2.编写代码
var http = require('http');
http.createServer(function(req,res){
res.writeHead(200,{'Content-type':'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
}).listen(3000);
console.log("HTTP Server is listening at port 3000");
3.运行程序
进入app.js目录,输入命令 :  node app.js
说明:
1.该程序调用了Node.js提供的http模块,对所有Http请求答复同样的内容并监听3000端口
2.浏览器中访问  http://127.0.0.1:3000 3.程序一直等待,直到Ctrl+C结束程序运行
4.listen函数中创建了事件监听器,使得node.js进程不会退出事件循环

7.使用supervisor方便代码调试
Node.js运行时,修改代码只有重启服务后才能生效,使用supervisor可以让你在修改代码后,不手动重启(supervisor帮你在代码变动时自动重启)即可看到变化
1.安装:
npm install  -g supervisor
2.使用supervisor命令启动app.js
supervisor app.js
 Node的小基友supervisor  
  每次修改代码后会自动重启。懒程序员就指望这种省事省力的工具活着了:)
  安装:npm install -g supervisor
  执行:supervisor app.js

8.几组常见概念:
同步:发出一个请求调用时,在没有得到结果之前,该调用就不返回        ---  即:请求一定要得到回应才返回
异步:当异步过程调用发出后,调用者不会立即得到结果,实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用  --- 即:请求发出后,继续执行其他任务,服务器处理完后,通过回调将结果发送给调用者
阻塞:  阻塞调用    ---  调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才回返回
非阻塞:非阻塞调用  ---  调用结果返回之前,函数不会阻塞当前线程,而会立刻返回

说明:
同步与异步  --- 关注的时消息通信机制

同步   ---  就是在发出一个 调用 时,在没有得到结果前,该 调用 就不返回,但一旦调用返回,就得到返回值了(执行完毕了)  ---  调用者 主动等待这个 调用的结果
异步   ---  调用  发出后,这个调用就直接返回,没有返回结果
---  当一个异步过程调用发出后,调用者不会立刻得到结果,而是在 调用 发出后,被调用者 通过状态、通知来通知调用者,或通过回调函数处理这个调用
eg:
典型的异步编程模型比如Node.js

举个通俗的例子:
你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。
而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调。

阻塞与非阻塞  --- 关注的是程序在等待调用结果(消息、返回值)时的状态

阻塞调用    ---  指调用之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回
非阻塞调用  ---  指在不能立刻得到结果之前,该调用不会阻塞当前线程
eg:
你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。
在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关。

9.异步式非阻塞I/O与事件式编程
Node.js最大的特点就是异步式IO与事件紧密结合的编程模式,控制流很大程度上要靠事件和回调函数来组织,一个逻辑要拆分为若干个单元
阻塞与线程:
1.阻塞:  线程在执行中如果遇到磁盘读写或网络通信等IO操作,通常要耗费很长时间,这是操作系统会剥夺这个线程的CPU控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞。
当I/O操作完毕时,操作系统架构这个线程的阻塞状态解除,恢复其对CPU的控制权,令其继续执行,这么IO模式称为同步式I/O或阻塞式IO
2.异步式IO或非阻塞式IO: 当线程遇到IO操作时,不会以阻塞方式等待IO操作的完成或数据的返回,而只是将IO请求发送给操作系统,继续执行下一条语句,
当操作系统完成IO操作时,以事件形式通知执行IO操作的线程,线程会在特定时候处理这个事件,为了处理异步IO,线程必须有事件循环,不断地检查有没有未处理的事件,依次予以处理

10.回调函数
eg:以异步方式读取文件为例  --- readfile.js
var fs = require('fs');
fs.readFile('file.txt','utf-8',function(err,data){
if(err){
console.error(err);
}else{
console.log(data);
}
});
说明:这里使用了匿名回调函数,也可将匿名函数单独剥离出来
function readFileCallBack(err, data) {
if (err) {
console.error(err);
} else {
console.log(data);
}
}
var fs = require('fs');
fs.readFile('file.txt', 'utf-8', readFileCallBack);
console.log('end.');

11.事件
Node.js所有的异步IO操作在完成时都会发送一个事件到事件队列,事件由EventEmitter对象提供。
eg:
上例中的fs.readFile和http.createServer的回调函数都是通过EventEmitter来实现的

EventEmitter的用法:
运行代码后,1秒后控制台输出了 some_event occured。
原理:
event对象注册了事件some_event的一个监听器,然后我们通过setTimeout在1000ms后向event对象发送事件some_event,此时会调用some_event的监听器

var EventEmitter = require('events').EventEmitter;
var event = new EventEmitter();

event.on('some_event',function(){
console.log('some_event occured');
});

setTimeout(function(){
event.emit('some_event1');
},1000);

12.Node.js的事件循环机制
Node.js程序由事件循环开始,到事件循环结束,所有的逻辑都是事件的回调函数,所以Node.js始终在事件循环中,程序入口就是事件循环第一个事件的回调函数。
事件的回调函数在执行的过程中,可能会发出IO请求或直接发射(emit)事件,执行完毕后在返回事件循环,事件循环会检查事件队列中有没有未处理的事件,直到程序结束。
Node.js的事件循环对开发中不可见,有libev库实现,libev支持多种类型的事件,在Node.js中均被EventEmitter封装,libev事件循环的每一次迭代,在Node.js中就是一次Tick

13.模块和包
模块(Module)和包(Package)是Node.js最重要的支柱,开发大规模程序通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的
Node.js提供了require函数来调用其他模块,而且模块都是基于文件,机制简单
模块
1.模块时Node.js程序的基本组成部分,文件和模块一一对应,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或编译过的C/C++扩展
eg:
var http = require('http')
其中http是Node.js的一个核心模块,其内部由C++实现,外部用JavaScript封装,通过require函数获取这个模块,然后使用其中的对象
创建及加载模块
1.创建模块   ---         Node.js中一个文件就是一个模块
Node.js提供了exports和require两个函数对象
exports   ---  模块公开的接口
require   ---  用于从外部获取一个模块的接口,即获取模块的exports对象

eg:
1.创建一个模块    module.js
var name;
exports.setName = function(thyName){
name =thyName;
}
exports.sayHello = function(){
console.log('Hello '+name);
}

2.同一目录下创建 getModule.js 调用模块
var myModule = require('./module');
myModule.setName('Jay');
myModule.sayHello();

3.运行结果
Hello Jay

4.程序说明
module.js通过exports对象把setName和sayHello作为模块的访问接口,在getModule.js中通过require('./module')加载这个模块,
然后就可直接访问module.js中exports对象的成员函数了,npm提供了的上万个模块都是通过这种简单的方式搭建起来的

2.单次加载   ---   require不会重复加载模块,即:无论调用多少次require,获得的模块都是同一个
eg: loadmodule.js
var hello1 = require('./module');
hello1.setName("aa");
var hello2 = require('./module');
hello2.setName("bb");
hell01.sayHello();
结果:
Hello bb

原因: 变量hello1和hello2指向的时同一个实例,因日hello1.setName的结构被hello2.setName覆盖,最终输出结果由后者决定

3.覆盖exports
只想把一个对象封装到模块中      ----  特别注意:通过覆盖exports方式,防止因单次加载而覆盖变量的值
eg:
1.定义hello.js模块

function Hello(){
var name;
this.setName = function(thyName){
name = thyName;
}
this.sayHello = function(){
console.log("Hello "+name);
}
};

推荐方式1:
module.exports = Hello;
原始方式2:
exports.Hello = Hello;

2.调用hello.js模块
方式1获取Hello模块对象:
var Hello = require('./hello');
//方式2获取Hello模块对象:
//var Hello = require("./hello").Hello;
hello = new Hello();
hello.setName("aa");
hello.sayHello();

特别说明:
1.模块接口的唯一变化是使用module.exports=Hello代替了exports.Hello = Hello,
外部引用该模块时,其接口对象就是要输出的Hello对象本事,而是通过原始方式输出的exports
2.事实上,exports本身仅仅是一个普通的空对象,即:{},它专门用来声明接口,本质上是通过它
为模块闭包的内部建立了一个有限的访问接口,因为它没有任何特殊的地方,所有可用其他东西来代替

14.创建包
1.包是在模块基础上更深一步的抽象,Node.js中的包类似Java中的类库,它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制
2.包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库,通过定制package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。
1.作为文件夹模块
最简单的包,就是一个作为文件夹的的模块
eg:
1.创建一个somepackage文件夹
2.在该文件夹下创建index.js
exports.hello = function(){
console.log("Hello from package");
}
3.在somepackage同级目录下创建getpackage.js文件,调用somepackage包下的模块
var somePackage = require("./somepackage");
somePackage.hello();
4.输出结果
Hello

2.package.json
1.在上例中的somepackage文件夹下,创建一个package.json文件,内容如下
{
"main":"./lib/interface.js"
}
2.将index.js重命名为interface.js并放到lib子文件夹下,以同样的方式调用这个包,依然可正常使用
3.说明:
Node.js在调用某个包时,会首先检查包中的package.json文件的main字段,将其作为包的接口模块,
如果package.json或main字段不存在,会尝试寻找index.js或index.node作为包的接口,
main字段指明了接口模块文件的位置

15.Node.js包管理器 npm 是Node.js官方提供的包管理工具,它已成为Node.js包的标准发布平台,用于Node.js包的发布、传播、依赖控制
npm 提供了命令行工具,可以方便的下载、安装、升级、删除包,也可让你作为开发者发布并维护包
1.获取一个包
格式:
npm [install/i] [package_name]
eg:  安装express
$ npm install express
或
$ npm i express

说明:
安装信息显示完后,express就按照成功了,并放置在当前目录的node_modules子目录下,
npm在获取express时还将自动解析其依赖,并获取express依赖的mime、mkdirp、qs和connect
2.本地模式和全局模式

npm安装包时两种模式: 本地模式和全局模式,
本地模式   --- 默认    即:把包安装到当前目录的node_modules子目录下,本地模式不会注册PATH环境变量
全局模式   --- npm [install/i] -g [package_name],使用全局模式安装的包不能直接在js文件中require获得,全局模式会注册PATH环境变量
对比
模式                   可使用require使用            注册PATH
本地模式                 是                          否
全局模式                 否                          是

总结:
当我们是要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装

说明:
1.npm默认情况下会从http://npmjs.org搜索和下载包,将包安装到当前目录的node_modules子目录下
2.Node.js的require在加载模块时会尝试搜寻node_modules子目录,因此使用npm本地模式安装的包可直接被引用

3.创建全局链接       npm link命令   ---  在本地包和全局包之间创建符号链接,使得require可以间接使用全局模式安装的包   --- 仅支持Linux,不支持Windows
eg:
1.全局模式安装express
npm install -g express
2.创建全局链接
npm link express ./node_modules/express -> /usr/local/lib/node_modules/express

4.包的发布      npm init    ---  根据交互式问答产生一个符合标准的package.json

16.Node.js核心模块
1.全局对象   JavaScript中的一个特殊对象,它及其所有属性都可在程序的任何地方访问,即全局变量。
浏览器JS中,window是全局底线
Node.js中的全局对象是global,所有全局变量都是global对象的属性
在Node.js中能够直接访问到对象通常都是global的属性,如:console、process等
2.全局对象与全局变量
global最根本的作用是作为全局变量的宿主
全局变量  --  在最外层定义的变量,全局对象的属性,隐式定义的变量(未定义直接赋值的变量)
Node.js中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的,而模块本身不是最外层上下文
特别注意:
永远使用var定义变量,以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险

3.两个常用全局变量   process 和  console
1.process  --- 是一个全局变量,即global对象的属性。它用于描述当前的Node.js进程状态,提供了一个与操作系统的简单接口
process对象的一些最常用的成员方法
process.argv     ---  命令行参数数组
第一个元素是 node, 第二个元素是  脚本文件名,  第三个元素开始每个元素是一个运行参数
process.stdout   ---  标准输出流,通常我们使用console.log()打印字符,而process.stdout.write()函数提供了更底层的接口
process.stdin    ---  标准输入流,初始时它是暂停的,要想从标准输入流读取数据,必须回复流,并手动编写流的事件响应函数
eg:
process.stdin.resume();
process.stdin.on('data',function(data){
process.stdout.write('read from console: '+data.toString());
});
process.nextTick(callback)   ---  功能是为事件循环设置一项任务,Node.js会在下次事件循环响应时调用callback
eg:把耗时操作拆分为两个事件,减少每个事件的执行时间,提供事件响应速度
function doSomething(args, callback){
somethingComplicated(args);
process.nextTick(callback());
}

doSomething(function onEnd(){
compute();
})

2.console   ---  用于提供控制台标准输出
console对象常用方法
console.log()    ---  向标准输出流打印字符并以换行符结束
console.error()  ---  向标准错误流输出
console.trace()  ---  向标准错误流输出当前的调用栈

4.常用工具util
util是一个Node.js核心模块,提供常用函数的集合
1.util.inherits     ---  util.inherits(constructor, superConstructor)是一个实现对象间原型继承的函数
eg:
var util = require('util');

function Base(){
this.name = 'base';
this.base = 1991;

this.sayHello(){
console.log("Hello "+this.name);
};
}

Base.prototype.showName = function(){
console.log(this.name);
}

function Sub(){
this.name = 'Sub';
}

util.inherits(Sub,Base);

var objBase = new Base();
objBase.showName();
objBase.sayHello();
console.log(objBase);

var objSub = new Sub();
objSub.showName();
//objSub.sayHello();
console.log(objSub);

结果:
base
Hello base
{ name: 'base', base: 1990, sayHello: [Function] }
sub
{ name: 'sub' }

分析:
我们定义了一个基础对象 Base 和一个继承自 Base 的 Sub,Base 有三个在构造函数
内定义的属性和一个原型中定义的函数,通过 util.inherits 实现继承。运行结果如下:
base
Hello base
{ name: 'base', base: 1991, sayHello: [Function] }
sub
{ name: 'sub' }
注意,Sub 仅仅继承了 Base 在原型中定义的函数,而构造函数内部创造的 base 属
性和 sayHello 函数都没有被 Sub 继承。同时,在原型中定义的属性不会被 console.log 作
为对象的属性输出。如果我们去掉 objSub.sayHello(); 这行的注释,将会看到:
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Object #<Sub> has no method 'sayHello'
at Object.<anonymous> (/home/byvoid/utilinherits.js:29:8)
at Module._compile (module.js:441:26)
at Object..js (module.js:459:10)
at Module.load (module.js:348:31)
at Function._load (module.js:308:12)
at Array.0 (module.js:479:10)
at EventEmitter._tickCallback (node.js:192:40)

2.util.inspect   --- util.inspect(object, [showHidden], [depth], [colors]) 是一个将任意对象转换为字符串的方法,通常用于调试和错误输出
它只是接受一个参数Object,即:要转换的对象
showHidden    ---  可选参数,如果是true,建辉输出更多隐藏信息
depth         ---  表示最大递归层数
color         ---  true用于在终端显示更漂亮效果

5.事件驱动 events    ---   Node.js最重要的模块,它提供了唯一接口,是Node.js事件编程的基石
1.事件发射器
events模块只提供了一个对象: events.EventEmitter
EventEmitter核心就是事件发射与事件监听器功能的封装,EventEmitter的每个事件由一个事件名和若干个参数组成,
对于每个事件,EventEmitter支持若干个事件监听器,当事件发射时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递

eg:
var events = require("events");
var emitter = new events.EventEmitter();

//为emitter添加事件监听
emitter.on('someEvent',function(arg1, arg2){
console.log("listener1",arg1,arg2);
});

//为emitter再添加一个事件监听
emitter.on('someEvent',function(arg1, arg2){
console.log("listener2",arg1,arg2);
});

//生成一个someEvent事件
emitter.emit("someEvent","Jay",1990);

结果:
listener1 Jay 1990
listener2 Jay 1990

分析:
emitter为事件someEvent注册了两个事件监听器,然后发射了一个someEvent事件,两个事件监听器回调函数被先后调用。

2.EventEmitter常用的API         ---   特别注意

EventEmitter.on(event,listener)
---  为指定事件注册一个监听器,接受一个字符串event事件名 和 一个回调函数listener
EventEmitter.emit(event,[arg1],[arg2],[...])
---  发射名为event的事件,传递若干可选参数到事件监听器的参数表
EventEmitter.once(event,listener)
---  为指定事件注册一个单次监听器,即:监听器最多只会触发一次,触发后立刻解除该监听器
EventEmitter.removeListener(event,listener)
---   移除指定事件的某个监听器,listener必须是该事件已经注册过的监听器
EventEmitter.removeAllListeners([event])
---   移除所有事件的所有监听器,如果指定event,则移除指定事件的所有监听器

3.error事件
遇到异常时通常会发射error事件,当error被发射时,EventEmitter规定:如果没有相应的监听器,Node.js会把它当做异常,退出程序并打印调用栈。
一般要为会发射error事件的对象设置监听器,避免遇到错误后整个程序崩溃
eg:
var events = require("events");
var emitter = new events.EventEmitter();
//为error事件注册监听器
emitter.on("error",function(){
console.log("程序出现了error");
})
//发射error事件
emitter.emit("error");

4.继承EventEmitter      ---  大部分时候不会直接使用EventEmitter,而是在对象中继承它,包括:fs、net、http在内的,只要是支持事件响应的核心模块都是EventEmitter的子类

6.文件系统  fs     ---  fs模块时文件操作的封装,它提供了文件的读取、写入、更名、删除、遍历目录、链接等文件系统操作
fs模块中所有的操作都提供了异步和同步的两个版本
eg:
fs.readFile()      ---    异步读取文件内容
fs.readFileSync()  ---    同步读取文件内容

1.fs.readFile(filename,[encoding],[callback(err,data)])    ---  异步读取文件
err   ---  表示有没有错误发生,如果有错误发送,err将会是Error对象
data  ---  文件内容,如果指定了encoding,data是一个解析后的字符串,否则data将会是以Buffer形式表示的二进制数据
eg:
var fs = require("fs");
fs.readFile("context.txt","utf-8",function(err,data)){
if(err){
console.log(err);
}else{
console.log(data);
}
}

2.fs.open(path,flags,[model],[callback(err,fd)])
与 C 语言标准库中的 fopen 函数类似。它接受两个必选参数,path 为文件的路径,
flags 可以是以下值。
 r :以读取模式打开文件。
 r+ :以读写模式打开文件。
 w :以写入模式打开文件,如果文件不存在则创建。
 w+ :以读写模式打开文件,如果文件不存在则创建。
 a :以追加模式打开文件,如果文件不存在则创建。
 a+ :以读取追加模式打开文件,如果文件不存在则创建。
mode 参数用于创建文件时给文件指定权限,默认是 0666①。回调函数将会传递一个文件描述符 fd②。

3.fs.read(fs,buffer,offset,length,position,[callback(err,bytesRead,buffer)])
---  从指定的文件描述符fs中读取数据并写入buffer指向的缓冲区对象
offset   ---  是buffer的写入偏移量
length   ---  是要从文件中读取的字节数
position ---  是文件读取的起始位置,如果position未null,则会从当前文件指针的位置读取
callback ---  回调函数传递bytesRead和buffer,分布表示读取的字节数和缓冲区对象
eg:
var fs = require("fs");
fs.open("content.txt",'r',function(err,fd)){
if(err){
console.error(err);
return;
}

var buf = new Buffer(8);
fs.read(fd,buf,0,8,null,function(err,bytesRead,buffer){
if(err){
console.error(err);
return;
}

console.log("bytesRead: "+bytesRead);
console.log(buffer);
});
}

7.HTTP服务器与客户端
Node.js标准库提供了http模块,其中封装了一个高效的HTTP服务器和一个简易的HTTP客户端
http.Server是一个基于事件的HTTP服务器,它的核心由Node.js下层C++部分实现,而接口由JavaScript封装,兼顾了高性能与简易性
http.request是一个HTTP客户端工具,用于向HTTP服务器发起请求

1.HTTP服务器
http.Server是http模块中的HTTP服务器对象,用Node.js做的所有基于HTTP协议的系统,eg:网站、社交应用甚至服务器,都是基于http.Server实现的。
它提供了一套封装级别很低的API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现。

eg:使用http实现一个服务器,通过 127.0.0.1:30000来访问

var http = require("http");
http.createServer(function(req,res){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
}).listener(30000);

console.log("Http Server is listening at port 30000");

解析:
http.createServer创建了一个http.Server实例,将一个函数作为HTTP请求处理函数。
函数的两个参数:
req    ---  请求对象
res    ---  响应对象

1.http.Server事件
http.Server是一个基于事件的HTTP服务器,所有的请求都被封装为独立的事件,开发者只需对它的事件编写响应函数即可实现HTTP服务器的所有功能,
它继承自EventEmitter,提供了以下几个事件
request:  当客户端请求到来时,该事件被触发,提供两个参数req和res,分别是http.ServerRequest和http.ServerResponse的实例,表示请求和响应信息
connection : 当TCP连接建立时,该事件被触发,提供一个参数socket,为net.Socket的实例
connection事件的粒度要大于request,因为客户端在Keep-Alive模式下可能会在同一个连接内发送多次请求
close:    当服务器关闭时,该事件被触发。忒别注意:不是在用户连接断开时

2.requets: 最常用的事件 :
http提供了 http.createServer([requestListener])         --- 创建一个HTTP服务器并将requestListener作为request事件的监听函数
eg:
var http = require("http");
var server = new http.Server();
server.on("request",function(req,res)){
......
}
server.listen(8888);
<==等价于==>
var http = require("http");
http.createServer(function(rep,res){
......
}).listen(8888);

3.http.ServerRequest
1.http.ServerRequest是HTTP请求的信息,是后端开发者最关注的内容,它一般由http.Server的request事件发送,作为第一个参数传递
2.ServerRequest提供的一些属性:
HTTP请求分为两部分:请求头(Request Header)和请求体(Request Body).
请求体长度较长,需要一定的时间传输,因此

http.ServerRequest提供了3个事件用于控制请求体传输

data     ---   当请求体数据到来时,该事件被触发。该事件提供一个参数chuk,表示接收到的数据。
如果该事件没有被监听,那么请求体将会被抛弃,该事件可能会被调用多次。
end      ---   当请求体数据传输完成时,该事件被触发,伺候将不会再有数据到来
close    ---   用户当前请求结束时,该事件被触发。不同于end,如果用户强制终止了传输,也还是调用close

ServerRequest 的属性
名 称                含 义
complete           客户端请求是否已经发送完成
httpVersion        HTTP 协议版本,通常是 1.0 或 1.1
method HTTP        请求方法,如 GET、POST、PUT、DELETE 等
url                原始的请求路径,例如 /static/image/x.jpg 或 /user?name=byvoid
headers            HTTP 请求头
trailers           HTTP 请求尾(不常见)
connection         当前 HTTP 连接套接字,为 net.Socket 的实例
socket             connection 属性的别名
client             client 属性的别名
4.获取GET请求内容
Node.js的url模块中的parse函数,提供了获取GET请求内容的功能
说明:
由于GET请求直接被嵌入到路径中,URL是完整的请求路径,包括了?后面的部分,因此可手动解析URL中的参数,或通过url模块解析请求参数
eg:解析URL的GET路径和参数
httpserverrequestget.js
//1.引入所需模块
var http = require("http");
var url = require("url");
var util = require("util");

//2.创建HTTP服务器,并通过url默解析URL和参数
http.createServer(function(req, res){
req.writeHead(200,{"Content-type":"text/plain"});
//3.将解析后的URL参数输出到网页
req.end(util.inspect(url.parse(req.url,true)));
})

结果:http://192.168.1.246:8888/user?name=byvoid&email=byvoid@byvoid.com
{ protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?name=byvoid&email=byvoid@byvoid.com',
query: { name: 'byvoid', email: 'byvoid@byvoid.com' },
pathname: '/user',
path: '/user?name=byvoid&email=byvoid@byvoid.com',
href: '/user?name=byvoid&email=byvoid@byvoid.com' }

分析:
通过url.parse,原始的path被解析为一个对象,其中
query   ---  对应GET请求的参数内容
path    ---  表示请求路径

5.获取POST请求内容   ---             POST请求的内容全部在请求体中
http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作,
譬如上传文件。而很多时候我们可能并不需要理会请求体的内容,恶意的 POST请求会大大消耗服务器的资源。
所以 Node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做

eg:一个简单的获取POST参数的例子,仅做演示:不能在生产环境中使用!!!
var http = require("http");
var querystring = require("querystring");
var util = require("util");

http.createServer(function(req, res){
var post = "";

req.on("data",function(chunk){
post += chunk;
});

req.on("end",function(){
post = querystring.parse(post);
res.end(util.inspect(post));
});
}).listen(8888);

特别说明:
在node js中常用的web server是express。
在express中,可以通过req.param('key')来获取post回来的参数。

6.http.ServerResponse
这是返回个客户端的信息,决定了用户最终能看到的结果,它由http.Server的request事件发送,作为第二个参数传递
3个主要函数:
response.writeHead(statusCode,[headers])
--- 向请求的客户端发送响应头,该函数在一个请求中最多调用一次,若不调用,则自动生成一个响应头
statusCode   : HTTP状态码 eg: 200,400(未找到)
headers      : 响应头的属性
response.write(data,[encoding])
--- 向请求的客户端发送响应内容,在response.end调用之前,response.write可调用多次
data     :  一个Buffer或字符串,表示要发送的内容,如果data是字符串,需要制定encoding来说明它的编码方式,默认是 utf-8
说明:也可发送二进制文件
eg:
response.writeHead(200,{"Content-type":"image/jpeg"});
response.write(file,"binary");
response.end([data],[encoding])           --- 结束响应,告知客户端所有发送已完成
当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可选参数,
意义和 response.write 相同。如果不调用该函数,客户端将永远处于等待状态。

7.HTTP客户端
http模块提供了http.request和http.get两个函数,作为客户端向HTTP服务器发送请求
http.request和http.get函数    --  都返回一个http.ClientRequest的实例

1. http.request(options, callback)  --- 发起HTTP请求
options  是一个类似关联数组的对象,表示请求的参数,常用参数如下
host:请求网站的域名或IP地址
port:请求网站的端口,默认:80
method:请求方法,默认GET
path  :请求相对于根的路径,默认 / ,GET请求时包括查询参数QueryString  eg: /search?query=jay
headers :请求头内容

eg:下面是一个通过 http.request 发送 POST 请求的代码:
//httprequest.js
var http = require('http');
var querystring = require('querystring');
var contents = querystring.stringify({
name: 'byvoid',
email: 'byvoid@byvoid.com',
address: 'Zijing 2#, Tsinghua University',
});
var options = {
host: 'www.byvoid.com',
path: '/application/node/post.php',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length' : contents.length
}
};
var req = http.request(options, function(res) {
res.setEncoding('utf8');
res.on('data', function (data) {
console.log(data);
});
});
req.write(contents);
req.end();
运行后结果如下:
array(3) {
["name"]=>
string(6) "byvoid"
["email"]=>
string(17) "byvoid@byvoid.com"
["address"]=>
string(30) "Zijing 2#, Tsinghua University"
}

callback 是回调函数,传递一个http.ClientResponse实例的参数

2. http.get(options, callback)   ---  http.request的简化版,自动将请求方法设置为了GET请求,同时不需要手动调用req.end()
eg:
var http = require("http");
var req = http.get({host:"www.baidu.com"});
req.on("data",function(res){
res.setEncoding("utf8");
res.on("data",function(data){
console.log(data);
});
});

两个对象:
http.ClientRequest
---  由http.request或http.get返回产生的对象,表示一个已经产生而且正在进行中的HTTP请求。
它提供了一个response事件,即http.request或http.get第二个参数指定的回调函数的绑定对象参数。

http.ClientResponse
---  提供了三个事件data、end、close,分别在数据到达、传输结束和连接结束时触发。
data事件传递一个参数chunk,表示接收到的数据
常见属性:
statusCode                  HTTP状态码,200,、400、500
httpVersion                 HTTP协议版本  1.0或1.1
headers                     HTTP请求头
常见函数:
response.setEncoding([encoding])  -- 设置默认编码,当data事件被触发时,数据会以encoding编码,默认是null,不编码,以Buffer形式存储
response.pause()                  -- 暂停接收数据和发送事件,方便下载功能
response.resume()                 -- 从暂停的状态中恢复

8.Linux下完整版利用formidable上传图片并显示     ---- windows下只需要改变requestHandlers.js中的相关存取路径即可

index.js      ---  程序入口
route.js      ---  url路由规则模块
server        ---  服务器模块
requestHandlers.js   --- 不同url的响应处理模块

1.index.js
//程序入口,调用其他3个模块
var server = require("./server");
router = require("./route");
requestHandlers = require("./requestHandlers");

//定义handle数组,即各个url请求索要启动的模块函数方法
var handle = {};
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
handle["/show"] = requestHandlers.show;

//启动服务器
server.start(router.route,handle);
2.route.js
//在路由模块中定义url访问规则
function route(handle,pathname,response,request){
console.log("About to route a request for "+pathname);
if(typeof handle[pathname]=="function"){
handle[pathname](response,request);
}else{
console.log("No request handler found for "+pathname);
response.writeHead(404,{"Content-type":"text/html"});
response.write("404 not found");
response.end();
}
}

//导出route模块
exports.route = route;
3.server.js
//引入生成页面需要的http和url模块
var http = require("http");
url = require("url");
function start(route,handle){
function onRequest(request,response){
//通过url模块提供的方法,获取url路径
var pathname = url.parse(request.url).pathname;
console.log("Request for "+pathname+" received");
//调用路由模块制定的访问规则
route(handle,pathname,response,request);
}
//创建并启动服务器
http.createServer(onRequest).listen(8888);
console.log("Server has started");
}

//导出模块
exports.start = start;
4.requestHanlder.js
//定义每个request请求的处理,即不同url请求返回不同的页面
var querystring = require("querystring");
fs = require("fs");
formidable = require("formidable");

//start请求对应的响应
function start(response){
console.log("Request handler start was called");
//定义请求响应的的页面
var body = '<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html; '+
'charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" enctype="multipart/form-data" '+
'method="post">'+
'<input type="file" name="upload" multiple="multiple">'+
'<input type="submit" value="Upload file" />'+
'</form>'+
'</body>'+
'</html>';

response.writeHead(200, {"Content-Type": "text/html"});
response.write(body);
response.end();
}

//upload请求对应的响应  --- 接收form表单上传的图片
function upload(response,request){
console.log("Request handler upload was called");
//调用formidable对form表单进行解析和处理
var form = formidable.IncomingForm();
//定义上传路径
form.uploadDir = "/home/develop/tmp/node/test";
//处理表单内容
form.parse(request,function(error,fields,files){
console.log("about to parse form ");
console.log(files.upload.path);
fs.renameSync(files.upload.path,"/home/develop/tmp/node/test/test.jpg");
response.writeHead(200, {"Content-Type": "text/html"});
response.write("received image:<br/>");
response.write("<img src='/show' />");
response.end();
});
}

//图片显示的请求   --- 将上传的图片显示到网页
function show(response){
console.log("Request handler show was called");
fs.readFile("/home/develop/tmp/node/test/test.jpg","binary",function(error,file){
if(error){
response.writeHead(500,{"Content-type":"text/plain"});
resonse.write(error+"\n");
response.end();
}else{
//如果没有异常,就将图片写到网页
response.writeHead(200,{"Content-type":"image/jpeg"});
response.write(file,"binary");
response.end();
}
});
}

//导出模块,供其他模块使用
exports.start = start;
exports.upload = upload;
exports.show = show;

5.访问   http://192.168.1.246:8888 
17.使用Node.js进行Web开发    ---  使用Node.js实现一个微博系统
功能:
路由控制、页面模板、数据库访问、用户注册、登录、用户会话等内容
技术:
Express框架、MVC设计模式、ejs或jade模板引擎、MongoDB数据库操作
MVC架构:
由模板引擎生成HTML页面,由控制器定义路径解析,由对象关系模型处理数据访问

1.使用http模块
Node.js提供了http模块,这是一个HTTP服务器内核的封装,你可用它做任何HTTP服务器能做的事,甚至实现一个HTTP代理服务器
eg:获取表单post的数据title和text
var http = require("http");
var querystring = require("querystring");

var server = http.createServer(function(req,res){
var post = "";
req.on("data",function(chunk){
post +=chunk;
});

req.on("end",function(){
post = querystring.parse(post);

res.write(post.title);
res.write(post.text);
res.end();
});
}).listen(3000);

2.Express框架      ---    最稳定和广泛的,Node.js官方推荐的Web开发框架

提供的功能     ---  多数功能只是对HTTP协议中常用操作的封装,更多的功能需要插件或整合其他模块来完成
路由控制
模板解析支持
动态视图
用户会话
CSRF保护
静态文件服务
错误控制器
访问日志
缓存
插件支持

eg:实现上面用http模块实现的功能
var express = require("express");
var app = express.createServer();

app.use(express.bodyParse);
app.all("/",function(req,res){
res.send(req.body.title+req.body.text);
});
app.listen(3000);
解析:
不用手动编写req的事件监听器,只需加载express.bodyParse()就能直接通过req.body获取Post的数据了

3.快速开发
1.全局方式安装express
npm install -g express
npm install -g express-generator
查看node版本
node -v
查看express版本
express -V
2.使用express命令创建工程  --- 创建一个express+ejs项目
express -e microblog
说明:
express -e project_name     --- 使用ejs模块引擎创建express项目
express -J project_name     --- 使用jade模块引擎创建express项目
express的版本不是通常的 “-v” 来查看,而是 “-V”

3.进入创建的项目 microblog,运行
npm install
4.启动服务器  --- 进入microblog项目,运行命令
npm start
5.访问项目
IP:3000

6.Express项目结构:
1.aap.js               --   项目入口
2.node_modules         --   存放项目的依赖库
3.package.json         --   项目依赖配置及开发者信息
4.public               --   静态文件  css,js,img等
5.routes               --   路由文件,类似MVC中的Controller控制器
6.views                --   页面文件(ejs或jade的模板,默认是:jade)

7.常用文件解析
1.routes/index.js   --- 路由文件,相当于控制器,用于组织展示的内容。
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});

module.exports = router;

说明:
router.get('/',function(...))  将 / 路径映射到function函数下,
res.render('index',{title:'Express'}) 调用模板解析引擎,解析名为index的模板,
并传入一个对象作为参数,这个对象只有一个属性,即 title:'Express'

2.views/index.ejs    --- 模板文件,即  routes/index.js中调用的模板,用于显示res.render第二个参数传入的对象的属性
<h1><%= title %></h1>
<p>Welcome to <%= title %></p>

4.路由控制            ---  网站架构最核心的部分,即 MVC架构中的控制器
工作原理:
访问 http://localhost:3000,浏览器会向服务器发送以下请求: GET / HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142
Safari/535.19
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: UTF-8,*;q=0.5
其中第一行是请求的方法、路径和 HTTP 协议版本,后面若干行是 HTTP 请求头。

1.请求网页动态数据
app.js 中  app.use('/', routes);调用routes/index.js模块,
在routes/index.js模块中,router.get('/',function(...)) 作用是 对 / 路径的GET请求由function进行处理,
通过res.render('index',{title:'Express'}) 调用模板解析引擎,解析名为index的模板,传递title变量,最终视图模板生成HTML页面,返回给浏览器

2.请求静态数据
浏览器接收到内容后,分析发现需要获取/stylesheets/style.css,因此会再次向服务器发起请求。
app.js通过app.use(express.static(path.join(__dirname, 'public')));  --- 配置了静态文件服务器,
因此/stylesheets/style.css会定向到app.js所咋目录的子目录中的文件public/stylesheets/style.css,返回给浏览器

Express创建的网站架构图:
---------------------------------
|              浏览器           |
---------------------------------
|
|
---------------------------------
|            路由控制器         |
---------------------------------
|               |             |
|               |             |
----------    -----------   ------------
模板引擎       静态文件      对象模型
----------    -----------   ------------

典型的MVC架构,浏览器发起请求,由路由控制接受,根据不同的路径定向到不同控制器。
控制器处理用户具体请求,可能会访问数据库中对象(对象模型),控制器还有访问模板引擎,
生成视图的HTML,最后再由控制器返回给浏览器,完成一次请求。

路由规则:
在routes/index.js中添加相应的url处理的路由规则
eg:添加/hello的处理   --- 向页面输出当前时间
var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});

/*GET hello page*/
router.get('/hello',function(req, res){
res.send("The time is "+new Date().toString());
});

module.exports = router;

路径匹配:
Express支持更高级的路径匹配模式   --- 支持JavaScript正则表达式
eg:
想要展示一个用户的个人页面,路径为 /user/[username]
路由规则:
router.get('/user/:username', function(req,res){
//向请求页面输出信息
res.send("user: "+req.params.username);
});

REST风格的路由规则:
REST   ---  表征状态转移,它是一种基于HTTP协议的网络应用的接口风格,充分利用HTTP的方法实现统一风格接口的服务

常用的4个HTTP方式:
GET       :  获取
POST      :  新增
PUT       :  更新
DELETE    :  删除

Express对每种HTTP请求方法都设计了不同的路由绑定函数:
方式                       绑定函数
GET                     router.get(path,callback)
POST                    router.post(path,callback)
PUT                     router.put(path,callback)
DELETE                  router.delete(path,callback)
所有方式                router.all(path,callback)   --- 支持把所有的请求方式绑定到同一个响应函数

控制权转移:
Express支持同一路径绑定多个路由响应函数,提供了路由控制权转移方法,即:回调函数的第三个参数 next,
通过调用next(),会将路由控制权转移给后面的规则    --- 类似 J2EE中的过滤器放行功能
eg:router/index.js中

router.all("/user/:username",function(req, res, next){
console.log("all methods captured");
//该路由规则处理完后,将路由控制权交给下一个路由规则   --- 放行
next();
});

router.get("/user/:username",function(req, res){
res.send("user: "+req.params.username);
});

解析:
同一个/user/:username绑定了两个路由响应函数,通过all的第三个参数 next,
处理完后,通过next()方法 将控制权转移类下一个路由规则,get方式才能处理该url
注意:
get方法处理完后没有转移控制权(放行),即:后面如果还有其他的绑定函数,对/user/:username将不能处理

next控制权转移是一个非常有用的工具,可以让我轻易实现中间件,而且还能提高代码的复用程度
eg:
针对一个用户查询信息和修改信息的操作,分别对应了GET和PUT操作,而两者公有的步骤是检查用户是否存在,可用next()方法实现

var users = {
"jay":{
name:"Jay",
website:"http://www.feicool.com"
}
}

router.all("/user/:username", function(req, res, next){
//检查用户是否存在
if(users[req.params.username]){
//存在则交给下一个路由规则处理
next();
}else{
//不存在则抛出异常
next(new Error(req.params.username+" does not exist."));
}
});

//展示用户信息
router.get("/user/:username",function(req, res){
res.send(JSON.stringify(users[req.params.username]));
});

//修改用户信息
router.put("/user/:username", function(req, res){
res.send("Done !!!");
});

解析:
router.all 这个路由规则起到了中间件的作用,把相似请求的相同部分提取出来,有利于代码维护,降低了代码耦合度

5.模板引擎          ---  MVC架构中的视图,将页面模板和要显示的数据结合起来生成HTML页面,由服务器端解析成HTML后传递给客户端。
1.在Node.js的MVC架构中,模板引擎包含在服务器端,控制器得到用户请求后,从模型获取数据,调用模板引擎,
模板引擎数据和页面模板为输入,生成HTML页面,然后返回给控制器,由控制器交回客户端。

------------    -----------
页面模板          数据  ----------- |         |
------------    -----------          |         |
|              |                |         |
----------------------------         |         |
模板引擎                    | 控制器  |
----------------------------         |         |
|                         |         |
----------------------------         |         |
HTML页面        ----------- |         |
----------------------------

模板引擎在MVC架构中的位置
2.使用模板引擎
ejs和jade

1.在app.js中通过以下语句设置了模板引擎和页面模板位置
app.set('views',__dirname+'/views');
app.set('view engine', 'ejs');
说明:
要使用的模板引擎是ejs,页面模板在views子目录下

2.在routes/index.js的exports.index函数中调用模板引擎,并传递参数对象
res.render('index',{title:'Express'})
说明:
res.render   --- 功能是调用模板引擎,并将其产生的页面直接返回给客户端。
两个参数:
参数一:模板名称,即views目录下的模板文件名,不包含扩展名
参数二:传递给模板的数据对象,json格式,用于模板翻译

3.ejs只有3中标签
<% code %>     : JavaScript代码
<%= code %>    : 显示替换过HTML特殊字符的内容
<%- code %>    : 显示原始HTML内容

3.页面布局
layout.ejs页面布局模板,它描述了整个页面的框架结构,默认情况下每个单独的页面都继承自这个框架。替换掉<%- body %>这个部分
这个功能通常非常有用,因为一般为了保持整个网站的一致风格,HTML 页面的<head>部分以及页眉页脚中的大量内容是重复的,
因此我们可以把它们放在 layout.ejs 中
关闭这个功能,可在app.js中的app.configure中添加一下内容:
app.set('view options',{
layout:false
})

另一种情况是,一个网站可能需要不止一种页面布局,例如网站分前台展示和后台管理
系统,两者的页面结构有很大的区别,一套页面布局不能满足需求。这时我们可以在页面模
板翻译时指定页面布局,即设置 layout 属性,例如:
function(req, res) {
res.render('userlist', {
title: '用户列表后台管理系统',
layout: 'admin'
});
};
这段代码会在翻译 userlist 页面模板时套用 admin.ejs 作为页面布局。

4.片段视图     --- partials
它就是一个页面的片段,通常是重复的内容,用于迭代显示。
通过它你可以将相对独立的页面块分割出去,而且可以避免显式地使用 for 循环。
eg:在 app.js 中新增以下内容:
app.get('/list', function(req, res) {
res.render('list', {
title: 'List',
items: [1991, 'byvoid', 'express', 'Node.js']
});
});
在 views 目录下新建 list.ejs,内容是:
<ul><%- partial('listitem', items) %></ul>
同时新建 listitem.ejs,内容是:
<li><%= listitem %></li>
访问 http://localhost:3000/list,可以在源代码中看到以下内容: <!DOCTYPE html>
<html>
<head>
<title>List</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<ul><li>1991</li><li>byvoid</li><li>express</li><li>Node.js</li></ul>
</body>
</html>

解析:
partial是一个可在视图中使用的函数,接受两个参数
参数一:片段视图名称
参数二:对象或数组
如果是对象,则片段视图中上下文变量引用的就是这个对象
如果是数组,则其中每个元素依次迭代应用到片段视图
片段视图中的上下文变量就是视图文件名  ,上例中的 'listitem'

6.建立微博网站        --- 初级完整项目:参考附件
功能分析:
首先,微博应该以用户为中心,因此需要有用户的注册和登录功能。微博网站最核心的功能是信息的发表,这个功能涉及许多
方面,包括数据库访问、前端显示等。一个完整的微博系统应该支持信息的评论、转发、圈点用户等功能,
但出于演示目的,我们不能一一实现所有功能,只是实现一个微博社交网站的雏形.
路由规划:
根据功能设计划分路由url规则
 /:首页
 /u/[user]:用户的主页
 /post:发表信息
 /reg:用户注册
 /login:用户登录
 /logout:用户登出
根据用户状态细分
1.发表信息以及用户登出页面必须是已登录用户才能操作的功能。
2.用户注册和用户登入所面向的对象必须是未登入的用户。
3.首页和用户主页则针对已登入和未登入的用户显示不同的内容。

打开 routes/index.js添加路由规则

界面设计:
1. 从http://twitter.github.com/bootstrap/下载bootstrap.zip
2. 其中所有的 JavaScript 和 CSS 文件都提供了开发版和产品版,前者是原始的代码,后者经过压缩,
文件名中带有 min。将 img 目录复制到工程 public 目录下,将 bootstrap.css、bootstrap-responsive.css
复制到 public/stylesheets 中,将 bootstrap.js 复制到 public/javascripts 目录中,
3. 然后从http://jquery.com/下载一份最新版的 jquery.js 也放入 public/javascripts 目录中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: