• Archive by category "架构设计"
  • (Page 4)

Blog Archives

Nodejs基础中间件Connect

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:

http://blog.fens.me/nodejs-connect/

connect

前言

“中间件”在软件领域是一个非常广的概念,除操作系统的软件都可以称为中间件,比如,消息中间件,ESB中间件,日志中间件,数据库中间件等等。

Connect被定义为Node平台的中间件框架,从定位上看Connect一定是出众的,广泛兼容的,稳定的,基础的平台性框架。如果攻克Connect,会有助于我们更了解Node的世界。Express就是基于Connect开发的。

让我们开始探索Connect中间件。

目录

  1. Connect介绍
  2. Connect安装
  3. Connect内置中间件介绍
  4. logger
  5. cookieParser
  6. session
  7. cookieSession
  8. compress
  9. basicAuth
  10. bodyParser
  11. json
  12. urlencoded
  13. multipart
  14. timeout
  15. reponseTime
  16. methodOverride
  17. csrf
  18. static
  19. staticCache
  20. directory
  21. vhost
  22. favicon
  23. limit
  24. query
  25. errorHadnler

1. Connect介绍

Connect是一个node中间件(middleware)框架。如果把一个http处理过程比作是污水处理,中间件就像是一层层的过滤网。每个中间件在http处理过程中通过改写request或(和)response的数据、状态,实现了特定的功能。这些功能非常广泛,下图列出了connect所有内置中间件和部分第三方中间件。 这里能看到完整的中间件列表

下图根据中间件在整个http处理流程的位置,将中间件大致分为3类:

  • 1. Pre-Request 通常用来改写request的原始数据
  • 2. Request/Response 大部分中间件都在这里,功能各异
  • 3. Post-Response 全局异常处理,改写response数据等

connect-middleware

关于Connect介绍部分,摘自:http://www.cnblogs.com/luics/archive/2012/11/28/2775206.html

2. Connect安装

我的系统环境

  • win7 64bit
  • Nodejs:v0.10.5
  • Npm:1.2.19

通过nodejs安装Connect


~ D:\workspace\javascript>mkdir nodejs-connect && cd nodejs-connect
~ D:\workspace\javascript\nodejs-connect> npm install connect
connect@2.9.0 node_modules\connect
├── methods@0.0.1
├── uid2@0.0.2
├── pause@0.0.1
├── cookie-signature@1.0.1
├── fresh@0.2.0
├── qs@0.6.5
├── bytes@0.2.0
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── debug@0.7.2
├── send@0.1.4 (range-parser@0.0.4, mime@1.2.11)
└── multiparty@2.1.8 (stream-counter@0.1.0, readable-stream@1.0.17)

尝试做一个最简单的web服务器

增加一个文件:app.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

启动node


~ D:\workspace\javascript\nodejs-connect>node app.js
GET / 200 5ms
GET /favicon.ico 200 0ms

打开浏览器:http://localhost:3000/

connect-web

3. Connect内置中间件介绍

22个内置中间件列表

下面将分别介绍这22个中间件。

4. logger

描述:用来输出用户请求日志。

参数:options或者format字符串

options:

  • format:参考下面的tokens
  • stream:输出流,默认是stdout
  • buffer:缓冲时间,默认是1000ms
  • immediate:立刻打印日志

tokens: format格式

  • :req[header] ex: :req[Accept]
  • :res[header] ex: :res[Content-Length]
  • :http-version
  • :response-time
  • :remote-addr
  • :date
  • :method
  • :url
  • :referrer
  • :user-agent
  • :status

Formats:缩写

  • default ‘:remote-addr – – [:date] “:method :url HTTP/:http-version” :status :res[content-length] “:referrer” “:user-agent”‘
  • short ‘:remote-addr – :method :url HTTP/:http-version :status :res[content-length] – :response-time ms’
  • tiny ‘:method :url :status :res[content-length] – :response-time ms’
  • dev concise output colored by response status for development use

例子:新建logger.js


var connect = require('connect');
var app = connect()
    .use(connect.logger())
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

connect.logger()


127.0.0.1 - - [Mon, 23 Sep 2013 05:14:18 GMT] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKi
t/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
127.0.0.1 - - [Mon, 23 Sep 2013 05:14:18 GMT] "GET /favicon.ico HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 6.1; WOW64)
 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"

connect.logger(‘short’)


127.0.0.1 - GET / HTTP/1.1 200 - - 9 ms
127.0.0.1 - GET /favicon.ico HTTP/1.1 200 - - 1 ms

connect.logger(‘dev’)


GET / 200 5ms
GET /favicon.ico 200 1ms

connect.logger(function(tokens, req, res){ return ‘some format string’ })


some format string
some format string

所以在开发环境,我们日志设置成dev就好了。

5. cookieParser

描述:cookie解析中间件,解析Cookies的头通过req.cookies得到cookies。还可以通过req.secret加密cookies。

例子:新建cookieParser.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser('secret string'))
    .use(function (req, res, next) {
        req.cookies.website="blog.fens.me";
        res.end(JSON.stringify(req.cookies));
    })
    .listen(3000);

connect-cookiesParser

6. session

描述:会话管理中间件

依赖:cookieParser

参数:options

options:

  • key:Cookies名,默认值为connect.sid
  • store: session存储实例
  • secret: session的cookie加密
  • cookie: session的cookie配置,默认值为{path: ‘/’, httpOnly: true, maxAge: null}
  • proxy:安全cookie的反向代理,通过x-forwarded-proto实现

Cookie option:

cookie.maxAge: 默认值null,表示当浏览器关闭后cookie被删除。

例子:新建session.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.session({secret: '123', cookie: { maxAge: 60000 }}))
    .use(function (req, res, next) {
        if(req.session.pv){
            res.setHeader('Content-Type', 'text/html');
            res.write('views: ' + req.session.pv);
            req.session.pv++;
            res.end();
        }else{
            req.session.pv = 1;
            res.end('Refresh');
        }

    })
    .listen(3000);

connect-session

7. cookieSession

描述:基于cookies的会话中间件

参数:options:

options:

  • key:Cookies名,默认值为connect.sess
  • secret: 防止cookie窃取
  • cookie: session的cookie配置,默认值为{path: ‘/’, httpOnly: true, maxAge: null}
  • proxy:安全cookie的反向代理,通过x-forwarded-proto实现

Clearing sessions:

req.session = null;

例子:新建cookieSession.js


var connect = require('connect');
var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.cookieSession({ secret: 'adddaa!', cookie: { maxAge: 60 * 60 * 1000 }}))
    .use(function (req, res, next) {
        if(req.session.pv){
            res.setHeader('Content-Type', 'text/html');
            res.write('views: ' + req.session.pv );
            req.session.pv++;
            console.log(req.session.pv);
            res.end();
        }else{
            req.session.pv = 1;
            res.end('Refresh');
        }

    })
    .listen(3000);

connect-cookieSession

我们发现,这次不管刷新多少次页面,req.session.pv始终是1.

8. compress

描述:gzip压缩中间件,通过filter函数设置,需要压缩的文件类型。压缩算法为gzip/deflate。

filter函数


exports.filter = function(req, res){
  return /json|text|javascript|dart|image\/svg\+xml|application\/x-font-ttf|application\/vnd\.ms-opentype|application\/vnd\.ms-fontobject/.test(res.getHeader('Content-Type'));
};

Threshold压缩阈值:当响应请求大于阈值,则压缩响应请求。

参数:options

  • chunkSize (default: 16*1024)
  • windowBits
  • level: 0-9 where 0 is no compression, and 9 is slow but best compression
  • memLevel: 1-9 low is slower but uses less memory, high is fast but uses more
  • strategy: compression strategy

例子:新建compress.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.compress({level:9}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.write(res);
        res.end('hello world\n');
    })
    .listen(3000);

connect-compress

9. basicAuth

描述:basic认证中间件,实现简单的用户密码登陆,当用户和密码验证通过后,通过一个callback方法继续执行。

同步验证:


connect()
  .use(connect.basicAuth(function(user, pass){
    return 'tj' == user && 'wahoo' == pass;
  }))

异步验证:


connect()
  .use(connect.basicAuth(function(user, pass, fn){
    User.authenticate({ user: user, pass: pass }, fn);
  }))

例子:新建basicAuth.js


var connect = require('connect');
var app = connect();
app.use(connect.logger('dev'));

//  基本用法
//  app.use(connect.basicAuth('fens','fens'))

//  同步验证
app.use(connect.basicAuth(function (user, pass) {
    var isLogin = 'fens' == user && 'fens' == pass;
    console.log("Login:" + isLogin);
    return isLogin;
}))
app.use(function (req, res) {
    res.end('hello world\n');
})
app.listen(3000);

验证弹出框

connect-basicAuth1

正确输入用户和密码后,访问页面

connect-basicAuth2

10. bodyParser

描述:请求内容解析中间件,支持多种类型application/json,

application/x-www-form-urlencoded, multipart/form-data.

与其他的3个中间件相同:


app.use(connect.json());
app.use(connect.urlencoded());
app.use(connect.multipart());

例子:新建bodyParser.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.bodyParser())
    .use(function(req, res) {
        res.end('req.body=>' + JSON.stringify(req.body));
    })
    .listen(3000);

POST方法:


~ curl -d 'user[name]=tj' http://localhost:3000/
req.body=>{"'user":{"name":"tj'"}}

GET方法:


~ curl http://localhost:3000/?user=123
req.body=>{}

11. json

描述:JSON解析中间件,req.body传值

参数:options

  • strict: when false anything JSON.parse() accepts will be parsed
  • reviver: used as the second “reviver” argument for JSON.parse
  • limit: byte limit [1mb]

同bodyParser。

12. urlencoded

描述:application/x-www-form-urlencode请求解析中间件

参数:options

  • limit: byte limit [1mb]

同bodyParser。

13. multipart

描述:multipart/form-data请求解析中间件,解析req.body和req.files.

上传文件:uploadDir


app.use(connect.multipart({ uploadDir: path }))

参数:options

  • limit: byte limit defaulting to [100mb]
  • defer: 在不等待“结束”事件,通过调用req.form.next()函数,可以缓冲并处理多个表单对象,还可以绑定到“progress”或“events”的事件。

Temporary Files:临时文件

默认情况下,临时文件会被保存在os.tmpDir()目录,但不会自动回归,我们必须手动处理。如果没有使用defer选项时,你可以通过req.files来获得对象的使用。


req.files.images.forEach(function(file){
  console.log('  uploaded : %s %skb : %s', file.originalFilename, file.size / 1024 | 0, file.path);
});

Streaming:流式处理

当使用defer选项时,文件在上传过程中,你可以通过”part”事件和流控制访问文件。


req.form.on('part', function(part){
  // transfer to s3 etc
  console.log('upload %s %s', part.name, part.filename);
  var out = fs.createWriteStream('/tmp/' + part.filename);
  part.pipe(out);
});

req.form.on('close', function(){
  res.end('uploaded!');
});

例子:新建multipart.js


var connect = require('connect');
var app = connect()
.use(connect.logger('dev'))
.use(connect.multipart({ uploadDir: 'd:\\tmp' }))
.use(function (req, res) {
if(req.method=='POST'){
console.log(req.files);
res.end('Upload ==>'+ req.files.file.path);
}
res.setHeader('Content-Type', 'text/html');
res.write('<form enctype="multipart/form-data" method="POST"><input type="file" name="file">');
res.write('<input type="submit" value="submit"/>');
res.write('</form>');
res.end('hello world\n');
})
.listen(3000);

通过form表单选择文件

connect-multipart1

POST请求解析

connect-multipart2

14. timeout

描述:请求超时中间件,默认超时时间是5000ms,可以清除这个时间通过req.clearTimeout()函数。超时的错误,可以通过next()函数传递。我们也可以设置超时的响应错误状态码:.timeout=503

例子:新建timeout.js,模拟超时


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.timeout(1000))
    .use(function (req, res) {
        setTimeout(function(){
            res.end('hello world\n');
        },5000)
    })
    .listen(3000);

控制台输出:


Error: Response timeout
    at IncomingMessage. (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.j
s:39:17)
    at IncomingMessage.EventEmitter.emit (events.js:95:17)
    at null._onTimeout (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.js:34:11)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
GET / 503 1030ms - 389b
Error: Response timeout
    at IncomingMessage. (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.j
s:39:17)
    at IncomingMessage.EventEmitter.emit (events.js:95:17)
    at null._onTimeout (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\timeout.js:34:11)
    at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)
GET /favicon.ico 503 1006ms - 389b

15. reponseTime

描述:计算响应时间中间件,在response的header增加X-Response-Time,计算响应时间。

例子:新建reponseTime.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.responseTime())
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

connect-responseTime

16. methodOverride

无法模拟出效果,暂时先跳过

描述: 提供伪造HTTP中间件,检查一个method是否被重写,通过传递一个key,得到_method,原始的方法通过req.originalMethod获得。

例子:新建methodOverride.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.methodOverride('KEY'))
    .use(function (req, res) {
        res.end(JSON.stringify(req.headers));
    })
    .listen(3000);

17. csrf

描述:跨域请求csrf保护中间件,通过req.csrfToken()令牌函数绑定到请求的表单字段。这个令牌会对访客会话进行验证。

默认情况会检查通过bodyParser()产生的req.body,query()函数产生的req.query,和X-CSRF-Token的header。

依赖:session(), cookieParser()

参数:options:

  • value: 一个函数接受请求,返回的令牌

例子:新建csrf.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.cookieParser())
    .use(connect.session({secret: '123', cookie: { maxAge: 60000 }}))
    .use(connect.csrf({value:'admin'}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.end('hello world\n');
        console.log(req.csrfToken());
    })
    .listen(3000);

生成的csrf Token:


1YZ72JuGRTOh/mzqByktPoyz2C+Dk1E5wXyj0=
GET / 200 356ms
bItSjAAXydK4jkYYLDnc1c0+7AGDFwGX6r8ns=
GET /favicon.ico 200 3ms

18. static

描述: 静态文件处理中间件,设置root路径作为静态文件服务器

参数:options:

options:

  • maxAge:浏览器缓存存活时间(毫秒),默认值0
  • hidden:是否允许传递隐藏类型的文件,默认值false
  • redirect:是否允许当访问名是一个目录,结尾增加”/”,默认值true
  • index:设置默认的文件名,默认值index.html

例子:新建static.js


var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.static(__dirname+'/static',{maxAge:60*60*1000,hidden:false}))
    .use(function (req, res) {
        res.setHeader('Content-Type', 'text/html');
        res.write('static:');
        res.write('');
        res.write('hidden:');
        res.end('');
    })
    .listen(3000);

隐藏类型的文件,没有被显示出来。

connect-static

19. staticCache

描述: 静态文件缓存中间件,最大可以缓存128个对象,每个对象最大256K,总加起来32mb。

缓存算法采用LRU(最近最少使用)算法,让最活跃的对象保存在缓存中,从而增加命中。

Benchmarks:性能测试

  • static(): 2700 rps
  • node-static: 5300 rps
  • static() + staticCache(): 7500 rps

依赖:static()

参数:options:

  • maxObjects:最多能缓存的对象,默认值128个
  • maxLength:最大缓存的对象,默认值256kb

例子:新建staticCache.js


var connect = require('connect');
var app = connect()
.use(connect.logger('dev'))
.use(connect.static(__dirname+'/static',{maxAge:60*60*1000,hidden:false}))
.use(connect.staticCache())
.use(function (req, res) {
res.setHeader('Content-Type', 'text/html');
res.write('static:');
res.write('<img src="static.png" width="100px"/>');
res.write('hidden:');
res.end('<img src=".png" width="100px"/>');
})
.listen(3000);

控制台日志:


connect.staticCache() is deprecated and will be removed in 3.0
use varnish or similar reverse proxy caches.
GET / 200 11ms
GET /.png 200 0ms

建议用varnish或专门的缓存工具,来代替staticCache()。

20. directory

描述: 目录列表中间件,列出指定目录下的文件

参数:options:

  • hidden:是否显示隐藏文件,默认值false.
  • icons:是否显示网站图标,默认值false.
  • filter:是否过滤文件,默认值false.

例子:新建directory.js

var connect = require('connect');
var app = connect()
    .use(connect.logger('dev'))
    .use(connect.directory(__dirname+'/static',{hidden:true}))
    .use(function (req, res) {
        res.end();
    })
    .listen(3000);

connect-directory

21. vhost

无法模拟出效果,暂时先跳过

描述: 虚拟二级域名映射中间件,设置hostname和server。

例子:新建vhost.js


var connect = require('connect');
var app = connect();
app.use(connect.logger('dev'));
app.use(function (req, res) {
    res.end(JSON.stringify(req.headers.host));
});

var fooApp = connect();
fooApp.use(connect.logger('dev'));
fooApp.use(function (req, res) {
    res.end('hello fooApp\n');
});

var barApp = connect();
barApp.use(connect.logger('dev'));
barApp.use(function (req, res) {
    res.end('hello barApp\n');
});

app.use(connect.vhost('foo.com', fooApp));
app.use(connect.vhost('bar.com', barApp));

app.listen(3000);

22. favicon

描述:网页图标中间件,指定favicon的路径

参数:options:

  • maxAge:缓存存活时间(毫秒),默认值1天

例子:新建favicon.js


var connect = require('connect');
var app = connect()
    .use(connect.favicon('static/favicon.ico'))
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end('hello world\n');
    })
    .listen(3000);

网站图标

connect-favicon

23. limit

描述: 请求内容大小限制中间件,可以用mb,kb,gb表示单位。

例子:新建limit.js


var connect = require('connect');
var app = connect()
.use(connect.logger('dev'))
.use(connect.limit('1mb'))
.use(connect.multipart({ uploadDir: 'd:\\tmp' }))
.use(function (req, res) {
if(req.method=='POST'){
console.log(req.files);
res.end('Upload ==>'+ req.files.file.path);
}
res.setHeader('Content-Type', 'text/html');
res.write('<form enctype="multipart/form-data" method="POST"><input type="file" name="file">');
res.write('<input type="submit" value="submit"/>');
res.write('</form>');
res.end('hello world\n');
})
.listen(3000);

控制台日志,上传大于1mb的文件。


GET / 200 8ms
Error: Request Entity Too Large
    at Object.exports.error (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\utils.js:62:13)
    at Object.limit [as handle] (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\limit.js:46:
47)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Object.logger (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\logger.js:156:5)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Function.app.handle (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:198:3)
    at Server.app (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\connect.js:65:37)
    at Server.EventEmitter.emit (events.js:98:17)
    at HTTPParser.parser.onIncoming (http.js:2027:12)
    at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:119:23)
POST / 413 8ms - 961b

24. query

描述:URL解析中间件,自动解析URL查询参数req.query

参数:options,qs.parse函数

例子:新建query.js


var connect = require('connect');
var app = connect()
    .use(connect.query())
    .use(connect.logger('dev'))
    .use(function (req, res) {
        res.end(JSON.stringify(req.query));
    })
    .listen(3000);

通过CURL测试:


curl -d '{name:'aad'}' http://localhost:3000?pass=did
{"pass":"did"}

req.query会自动解析URL的查询参数,不解析POST的数据。

25. errorHadnler

无法模拟出效果,暂时先跳过

描述:错误处理中间件,对于开发过程中的错误,提供栈跟踪和错误响应,接受3种类型text,html,json。

Text: text/plain是默认类型,返回一个简单的栈跟踪和错误消息

JSON:application/json,返回{‘error’:error}对象

HTML: 返回一个HTML错误页面

参数:options:

  • showStack:返回错误信息和错误栈.默认值false
  • showMessage,只返回错误信息,默认值false
  • dumpExceptions, 输出异常日志,默认值false
  • logErrors,输出错误日志到文件,默认值false

例子:新建errorHadnler.js


var connect = require('connect');
var app = connect()
    .use(connect.logger())
    .use(connect.errorHandler({ dumpExceptions: true, showStack: true }))
    .use(function (req, res) {
        req.headers.accept='html';
        res.write(JSON.stringify(req.headers.accept));
        throw new Error('my errorHandler!!!');
        res.end('Hello');
    })
    .listen(3000);

控制台输出


Error: my errorHandler!!!
    at Object.handle (D:\workspace\javascript\nodejs-connect\errorHadnler.js:8:15)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:192:9)
    at Object.logger (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\middleware\logger.js:156:5)
    at next (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:190:15)
    at Function.app.handle (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\proto.js:198:3)
    at Server.app (D:\workspace\javascript\nodejs-connect\node_modules\connect\lib\connect.js:65:37)
    at Server.EventEmitter.emit (events.js:98:17)
    at HTTPParser.parser.onIncoming (http.js:2027:12)
    at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:119:23)

转载请注明出处:
http://blog.fens.me/nodejs-connect/

打赏作者

RequireJS异步模块加载

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-requirejs/

requirejs

前言

随着项目越来越大,依赖越来越多,代码的耦合度也越来越高。我们需要从架构的层面设计和优化代码的组织结构。RequireJS遵循AMD(异步模块定义)规范,从架构层抽象出“模块化”开发方案,并以标准化了模块化开发,同时和其他的开发框架保持兼容。

按照RequireJS的规范,我们能够更容易地实现更复杂,更强大的JS的富客户端程序。

目录

  1. RequireJS介绍
  2. RequireJS安装
  3. RequireJS基本使用
  4. nodejs构建简易的web服务器
  5. RequireJS模块化
  6. 多路径配置: baseUrl,paths
  7. 编译Requirejs模块

1. RequireJS介绍

RequireJS是一个Javascript的模块加载器,倡导的是一种模块化开发理念,核心价值是让 JavaScript 的模块化开发变得更简单自然。RequireJS 遵循的是 AMD(异步模块定义)规范,帮助用户异步按需的加载 JavaScript 代码,并解决 JavaScript 模块间的依赖关系,提升了前端代码的整体质量和性能。

2. RequireJS安装

我的系统环境

  • win7 64bit
  • Nodejs:v0.10.5
  • Npm:1.2.19

通过nodejs安装RequireJS


~ D:\workspace\javascript>mkdir nodejs-require && cd nodejs-require
~ D:\workspace\javascript\nodejs-require>npm install requirejs
npm http GET https://registry.npmjs.org/requirejs
npm http 200 https://registry.npmjs.org/requirejs
requirejs@2.1.8 node_modules\requirejs

全局安装requirejs:使用r.js工具。


~ D:\workspace\javascript\nodejs-require>npm install requirejs -g
npm http GET https://registry.npmjs.org/requirejs
npm http 304 https://registry.npmjs.org/requirejs
D:\toolkit\nodejs\r.js -> D:\toolkit\nodejs\node_modules\requirejs\bin\r.js
npm ERR! peerinvalid The package generator-karma does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer generator-angular@0.4.0 wants generator-karma@~0.5.0

npm ERR! System Windows_NT 6.1.7601
npm ERR! command "D:\\toolkit\\nodejs\\\\node.exe" "D:\\toolkit\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "
requirejs" "-g"
npm ERR! cwd D:\workspace\javascript\nodejs-require
npm ERR! node -v v0.10.5
npm ERR! npm -v 1.2.19
npm ERR! code EPEERINVALID
npm ERR!
npm ERR! Additional logging details can be found in:
npm ERR!     D:\workspace\javascript\nodejs-require\npm-debug.log
npm ERR! not ok code 0

运行r.js命令失败


~ D:\workspace\javascript\nodejs-require>r.js

requirejs-rjs

我的win7环境中有错误,所以我只能在当前项目中运行r.js命令.(Linux下面可以全局安装。)


~ D:\workspace\javascript\nodejs-require>node node_modules\requirejs\bin\r.js -h
See https://github.com/jrburke/r.js for usage.

3. RequireJS基本使用

创建项目文件:


~ D:\workspace\javascript\nodejs-require>ls -l
-rwx------  1 4294967295 mkpasswd  65 Sep 19 13:32 a.js
-rwx------  1 4294967295 mkpasswd  59 Sep 18 20:33 b.js
-rwx------  1 4294967295 mkpasswd  82 Sep 18 20:33 c.js
-rwx------  1 4294967295 mkpasswd  69 Sep 18 20:33 d.js
-rwx------  1 4294967295 mkpasswd 206 Sep 19 13:32 index.html
-rwx------  1 4294967295 mkpasswd  48 Sep 19 13:31 main.js
drwx------+ 1 4294967295 mkpasswd   0 Sep 18 20:32 node_modules

对文件的定义:

  • index.html: 用于加载javascript文件,这里只加载RequireJS库
  • main.js: RequireJS的模块组,用来封装JS模块,通过js来加载其他的js文件
  • a.js: 一个JS的功能文件,不依赖其他库
  • b.js: 一个JS的功能文件,不依赖其他库
  • c.js: 一个JS的功能文件,依赖a.js和b.js
  • d.js: 一个JS的功能文件,依赖c.js

新增文件:index.html


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>RequireJS</title>
<script data-main="main" src="node_modules/requirejs/require.js"></script>
</head>
<h1>RequireJS Testing</h1>
<body>
</body>
</html>

新增文件:main.js


require(['a'],function(){
     a();
})

新增文件:a.js


function a(){
    console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaa");
}

新增文件:b.js


function b(){
    console.log("bbbbbbbbbbbbbbbbbbbbb");
}

新增文件:c.js


function c(){
    a();
    b();
    console.log("cccccccccccccccccccccccc");
}

新增文件:d.js


function d(){
    c();
    console.log("ddddddddddddddddddddd");
}

打开浏览器:file:///D:/workspace/javascript/nodejs-require/index.html

requirejs-testing1

我们发现a.js被加载了,但我们并没有在index.html中定义script加载a.js。

  • 1. a.js的加载是RequireJS帮我们做的。
  • 2. 通过在HTML中定义data-main属性,注册一个模块main.js。
  • 3. 在main.js中,通过声明式定义,引入a.js文件
  • 4. 实现了对a.js加载的控制

通过require函数完全成了依赖管理的功能

require(dependencies, callback)
  • dependencies: 是我们要载入的js,使用相对路径。
  • callback: 是封装有程序逻辑。

所以,我们可以发现了RequireJS可以帮我们做,js依赖管理。我们可以再也不用在HTML中,以很土的加载顺序的方式管理JS文件了。这种优雅的注入可以帮我很好的管理全局变量。

4. nodejs构建简易的web服务器

在进行下一步之前,我们要用构建一个简易的web服务器,RequireJS的模块化加载需要web server的支持,通过本地文件的方式是不行的。

安装connect依赖


~ D:\workspace\javascript\nodejs-require>npm install connect
connect@2.9.0 node_modules\connect
├── uid2@0.0.2
├── methods@0.0.1
├── cookie-signature@1.0.1
├── pause@0.0.1
├── fresh@0.2.0
├── bytes@0.2.0
├── qs@0.6.5
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── debug@0.7.2
├── send@0.1.4 (range-parser@0.0.4, mime@1.2.11)
└── multiparty@2.1.8 (stream-counter@0.1.0, readable-stream@1.0.17)

新建文件:app.js


~ vi app.js

var connect = require('connect');
connect.createServer(
    connect.static(__dirname)
).listen(3000);

开发模式启动服务器:

supervisor app.js

打开浏览器:http://localhost:3000
requirejs-testing2

5. RequireJS模块化

虽然我们可以把所有的程序都写在main.js的callback中,但是程序会变得复杂,关系也不清楚。接下来,我们要做模块化的编程。RequireJS也实现了AMD定义的define的API。

define(id?, dependencies?, factory)
  • id:字符串,模块名称,可选。
  • dependencies: 是我们要载入的js,使用相对路径。
  • factory: 工厂方法,返回一个模块函数

新建ab.js,实现模块ab:


~ vi ab.js

define(function(){
    return {
        a: function(){
            a();
            console.log("ab.a()");
        },
        b:function(){
            b();
            console.log("ab.b()");
        }
    };
});

修改main.js


~ vi main.js

require(['a','b','ab'],function(a,b,ab){
    ab.b();
})

刷新页面:http://localhost:3000/

requirejs-testing3

调用流程:

  • 1. 通过main.js加载了a.js,b.js,ab.js文件
  • 2. ab.js构建一个ab的模块
  • 3. 在ab的模块中,调用b.js的b()函数

通过模块化的封装后,我们可以更优化我们的程序,程序结构更清晰!

6. 多路径配置: baseUrl,paths

如果我们需要加载多个js文件时,文件在不同的目录下面,我们可以通过baseUrl和paths来设置路径的默认位置和路径别名,方便我们定位文件。


#新建目录
~ mkdir folder
~ mkdir folder/f1
~ mkdir folder/f2

#把c.js移动到f1
~ mv c.js folder/f1

#把d.js移动到f2
~ mv d.js folder/f2

修改main.js


~ vi main.js
require.config({
    baseUrl:'./',
    paths:{
        f1:'folder/f1',
        f2:'folder/f2'
    }
});

require([
    'b',
    'a',
    'ab',
    'f1/c',
    'f2/d'
],function(a,b,ab,c){
    d();
})

刷新浏览器:

requirejs-testing4

我们看到c.js, d.js都被正确的加载了。

7. 编译Requirejs模块

最后要讲的是,通过r.js命令,对模块进行翻译优化。

在RequireJS安装部分,我们没有实现全局安装,因些只能在项目中通过相对路径使用r.js的命令。


~ D:\workspace\javascript\nodejs-require>node node_modules\requirejs\bin\r.js -o name=main out=main-build.js baseUrl=. pat
hs.f1="folder/f1" paths.f2="folder/f2"

Tracing dependencies for: main
Uglifying file: D:/workspace/javascript/nodejs-require/main-build.js

D:/workspace/javascript/nodejs-require/main-build.js
----------------
D:/workspace/javascript/nodejs-require/b.js
D:/workspace/javascript/nodejs-require/a.js
D:/workspace/javascript/nodejs-require/ab.js
D:/workspace/javascript/nodejs-require/folder/f1/c.js
D:/workspace/javascript/nodejs-require/folder/f2/d.js
D:/workspace/javascript/nodejs-require/main.js

我们会发现,新生成一个文件main-build.js


~ cat main-build.js

function b(){console.log("bbbbbbbbbbbbbbbbbbbbb")}function a(){console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaa")}function c(){a(),b(),console.log("cccccccccccccccccccccccc")}function d(){c(),console.log("ddddddddddddddddddddd")}define("b",function(){}),define("a",function(){}),define("ab",[],function(){return{a:function(){a(),console.log("ab.a()")},b:function(){b(),console.log("ab.b()")}}}),define("f1/c",function(){}),define("f2/d",function(){}),require.config({baseUrl:"./",paths:{f1:"folder/f1",f2:"folder/f2"}}),require(["b","a","ab","f1/c","f2/d"],function(e,t,n,r){d()}),define("main",function(){});

新生成的main-build.js可以用来做为发布的功能模块。

本文简单的介绍了RequireJS的使用,如果我们的程序按照AMD的模块化思路去构建,我相信JS的代码,也是健壮的,可维护的,可传递的。

转载请注明出处:
http://blog.fens.me/nodejs-requirejs/

打赏作者

Nodejs异步流程控制Async

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-async/

nodejs-async

前言

Nodejs框架类库很多,功能相近的框架,本来只打算学一种写一种。之前写过流程控制框架windjs文章,本来是想着要支持一下“国人框架”。无奈啊,作者竟然放弃了维护,国人真的不靠谱啊!

“流程控制”本来是件比较简单的事,但是由于Nodejs的异步架构的实现方法,对于需要同步的业务逻辑,实现起来就比较麻烦。嵌套3-4层,代码就会变得的支离破碎了!

今天就遇到了一个业务逻辑,连续对数据库操作,前后有依赖。让我们看看Async是如何解决问题的。

不用不知道,一用真强大!!

目录

  1. Async介绍
  2. Async安装
  3. Async函数介绍
  4. async_demo使用介绍
  5. 场景:对数据库的连续操作
  6. async_demo我的分支

1. Async介绍

Async是一个流程控制工具包,提供了直接而强大的异步功能。基于Javascript为Node.js设计,同时也可以直接在浏览器中使用。

Async提供了大约20个函数,包括常用的 map, reduce, filter, forEach 等,异步流程控制模式包括,串行(series),并行(parallel),瀑布(waterfall)等。

项目地址:https://github.com/caolan/async

2. Async安装

我的系统环境

  • win7 64bit
  • Nodejs:v0.10.5
  • Npm:1.2.19
  • MySQL:Server version: 5.6.11 MySQL Community Server (GPL)

我们做实验时,安装async有两个方式:

  • 1. 独立安装async
  • 2. 下载async demo代码安装

我建议大家用第二种方式安装,这样子实例的代码就都有了。

1). 独立安装async


~ D:\workspace\javascript>mkdir nodejs-async && cd nodejs-async
~ D:\workspace\javascript\nodejs-async>npm install async
npm http GET https://registry.npmjs.org/async
npm http 304 https://registry.npmjs.org/async
async@0.2.9 node_modules\async

打开网页,参照示例学习:https://github.com/bsspirit/async_demo

2). 下载async demo代码安装


~ D:\workspace\javascript>git clone git@github.com:bsspirit/async_demo.git nodejs-async
~ D:\workspace\javascript>cd nodejs-async
~ D:\workspace\javascript\nodejs-async>npm install
npm http GET https://registry.npmjs.org/moment
npm http GET https://registry.npmjs.org/async
npm http 304 https://registry.npmjs.org/moment
npm http 304 https://registry.npmjs.org/async
async@0.2.9 node_modules\async
moment@2.1.0 node_modules\moment

这套demo示例,比较全面的介绍了async的使用,有中文注释。 感谢github社区原创作者freewind,代码更新的贡献者alsotang。

当然,我的分支中也修改了一部分代码。在本文最后,我会写到changelog中!

3. Async函数介绍

基于async的0.2.9版本。

async主要实现了三个部分的流程控制功能:

  • 集合: Collections
  • 流程控制: Control Flow
  • 工具类: Utils

1). 集合: Collections

  • each: 如果想对同一个集合中的所有元素都执行同一个异步操作。
  • map: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。
  • filter: 使用异步操作对集合中的元素进行筛选, 需要注意的是,iterator的callback只有一个参数,只能接收true或false。
  • reject: reject跟filter正好相反,当测试为true时则抛弃
  • reduce: 可以让我们给定一个初始值,用它与集合中的每一个元素做运算,最后得到一个值。reduce从左向右来遍历元素,如果想从右向左,可使用reduceRight。
  • detect: 用于取得集合中满足条件的第一个元素。
  • sortBy: 对集合内的元素进行排序,依据每个元素进行某异步操作后产生的值,从小到大排序。
  • some: 当集合中是否有至少一个元素满足条件时,最终callback得到的值为true,否则为false.
  • every: 如果集合里每一个元素都满足条件,则传给最终回调的result为true,否则为false
  • concat: 将多个异步操作的结果合并为一个数组。

2). 流程控制: Control Flow

  • series: 串行执行,一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。
  • parallel: 并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。
  • whilst: 相当于while,但其中的异步调用将在完成后才会进行下一次循环。
  • doWhilst: 相当于do…while, doWhilst交换了fn,test的参数位置,先执行一次循环,再做test判断。
  • until: until与whilst正好相反,当test为false时循环,与true时跳出。其它特性一致。
  • doUntil: doUntil与doWhilst正好相反,当test为false时循环,与true时跳出。其它特性一致。
  • forever: 无论条件循环执行,如果不出错,callback永远不被执行。
  • waterfall: 按顺序依次执行一组函数。每个函数产生的值,都将传给下一个。
  • compose: 创建一个包括一组异步函数的函数集合,每个函数会消费上一次函数的返回值。把f(),g(),h()异步函数,组合成f(g(h()))的形式,通过callback得到返回值。
  • applyEach: 实现给一数组中每个函数传相同参数,通过callback返回。如果只传第一个参数,将返回一个函数对象,我可以传参调用。
  • queue: 是一个串行的消息队列,通过限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。
  • cargo: 一个串行的消息队列,类似于queue,通过限制了worker数量,不再一次性全部执行。不同之处在于,cargo每次会加载满额的任务做为任务单元,只有任务单元中全部执行完成后,才会加载新的任务单元。
  • auto: 用来处理有依赖关系的多个任务的执行。
  • iterator: 将一组函数包装成为一个iterator,初次调用此iterator时,会执行定义中的第一个函数并返回第二个函数以供调用。
  • apply: 可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码。
  • nextTick: 与nodejs的nextTick一样,再最后调用函数。
  • times: 异步运行,times可以指定调用几次,并把结果合并到数组中返回
  • timesSeries: 与time类似,唯一不同的是同步执行

3). 工具类: Utils

  • memoize: 让某一个函数在内存中缓存它的计算结果。对于相同的参数,只计算一次,下次就直接拿到之前算好的结果。
  • unmemoize: 让已经被缓存的函数,返回不缓存的函数引用。
  • log: 执行某异步函数,并记录它的返回值,日志输出。
  • dir: 与log类似,不同之处在于,会调用浏览器的console.dir()函数,显示为DOM视图。
  • noConflict: 如果之前已经在全局域中定义了async变量,当导入本async.js时,会先把之前的async变量保存起来,然后覆盖它。仅仅用于浏览器端,在nodejs中没用,这里无法演示。

4. async_demo使用介绍

详细使用请参考github源代码:https://github.com/bsspirit/async_demo

每个函数的用法,有非常详细的实例!!

5. 场景:对数据库的连续操作

这个场景进背景情况,请参考文章:用Nodejs连接MySQL

原场景中,对数据串行操作,增删改查(CRUD),代码如下:


var mysql = require('mysql');
var conn = mysql.createConnection({
    host: 'localhost',
    user: 'nodejs',
    password: 'nodejs',
    database: 'nodejs',
    port: 3306
});
conn.connect();

var insertSQL = 'insert into t_user(name) values("conan"),("fens.me")';
var selectSQL = 'select * from t_user limit 10';
var deleteSQL = 'delete from t_user';
var updateSQL = 'update t_user set name="conan update"  where name="conan"';

//delete
conn.query(deleteSQL, function (err0, res0) {
    if (err0) console.log(err0);
    console.log("DELETE Return ==> ");
    console.log(res0);

    //insert
    conn.query(insertSQL, function (err1, res1) {
        if (err1) console.log(err1);
        console.log("INSERT Return ==> ");
        console.log(res1);

        //query
        conn.query(selectSQL, function (err2, rows) {
            if (err2) console.log(err2);

            console.log("SELECT ==> ");
            for (var i in rows) {
                console.log(rows[i]);
            }

            //update
            conn.query(updateSQL, function (err3, res3) {
                if (err3) console.log(err3);
                console.log("UPDATE Return ==> ");
                console.log(res3);

                //query
                conn.query(selectSQL, function (err4, rows2) {
                    if (err4) console.log(err4);

                    console.log("SELECT ==> ");
                    for (var i in rows2) {
                        console.log(rows2[i]);
                    }
                });
            });
        });
    });
});

//conn.end();

为了实现了串行操作,所有的调用都是在callback中实现的,5层嵌套结构。这种代码已经变得不可以维护了。所以,需要用async库,对上面的代码结构进行重写!

修改后的代码


var mysql = require('mysql');
var async = require('async');

var conn = mysql.createConnection({
    host: 'localhost',
    user: 'nodejs',
    password: 'nodejs',
    database: 'nodejs',
    port: 3306
});

var sqls = {
    'insertSQL': 'insert into t_user(name) values("conan"),("fens.me")',
    'selectSQL': 'select * from t_user limit 10',
    'deleteSQL': 'delete from t_user',
    'updateSQL': 'update t_user set name="conan update"  where name="conan"'
};

var tasks = ['deleteSQL', 'insertSQL', 'selectSQL', 'updateSQL', 'selectSQL'];
async.eachSeries(tasks, function (item, callback) {
    console.log(item + " ==> " + sqls[item]);
    conn.query(sqls[item], function (err, res) {
        console.log(res);
        callback(err, res);
    });
}, function (err) {
    console.log("err: " + err);
});

控制台输出


deleteSQL ==> delete from t_user
{ fieldCount: 0,
  affectedRows: 0,
  insertId: 0,
  serverStatus: 34,
  warningCount: 0,
  message: '',
  protocol41: true,
  changedRows: 0 }
insertSQL ==> insert into t_user(name) values("conan"),("fens.me")
{ fieldCount: 0,
  affectedRows: 2,
  insertId: 45,
  serverStatus: 2,
  warningCount: 0,
  message: '&Records: 2  Duplicates: 0  Warnings: 0',
  protocol41: true,
  changedRows: 0 }
selectSQL ==> select * from t_user limit 10
[ { id: 45,
    name: 'conan',
    create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中国标准时间) },
  { id: 46,
    name: 'fens.me',
    create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中国标准时间) } ]
updateSQL ==> update t_user set name="conan update"  where name="conan"
{ fieldCount: 0,
  affectedRows: 1,
  insertId: 0,
  serverStatus: 2,
  warningCount: 0,
  message: '(Rows matched: 1  Changed: 1  Warnings: 0',
  protocol41: true,
  changedRows: 1 }
selectSQL ==> select * from t_user limit 10
[ { id: 45,
    name: 'conan update',
    create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中国标准时间) },
  { id: 46,
    name: 'fens.me',
    create_date: Fri Sep 13 2013 12:24:51 GMT+0800 (中国标准时间) } ]
err: null

代码一下读性就增强了许多倍,这就是高效的开发。

不用不知道,一用真强大!!!

当然还有其他的工作流框架来完成这件事情step,then.js,windjs。
windjs请参考:wind.js助力异步编程

6. async_demo我的分支

https://github.com/bsspirit/async_demo

  • 1. forEach.js改名为each.js
  • 2. each.js文件中的async.forEach,改为async.each
  • 3. map.js增加mapLimit函数
  • 4. filter_reject.js对注释补充
  • 5. filter_reject.js增加rejectSeries函数
  • 6. reduce.js增加注释
  • 7. detect.js增加注释
  • 8. sortBy.js增加注释
  • 9. some.js对注释补充
  • 10. every.js对注释补充和修改
  • 11. series.js调整注释格式
  • 12. parallel.js调整注释格式
  • 13. parallel.js增加parallelLimit函数
  • 14. whilist_until.js调整注释格式
  • 15. whilist_until.js增加doWhilst函数
  • 16. whilist_until.js增加doUntil函数
  • 17. whilist_until.js增加forever函数
  • 18. waterfall.js调整注释格式
  • 19. 增加compose.js文件
  • 20. apply.js补充注释并增加一个实例
  • 21. 修改nextTick.js实例
  • 22. 增加times.js文件,包括times和timesSeries函数
  • 23. 修改iterator.js实例
  • 24. 修正auto.js关于第二个实例的错误解释
  • 25. 修改queue.js实例和注释
  • 26. 修改cargo.js文件
  • 27. 增加applyEach.js文件
  • 28. 修改utils.js实例和注释

转载请注明出处:
http://blog.fens.me/nodejs-async/

打赏作者

AngularJS路由和模板

AngularJS体验式编程系列文章,将介绍如何用angularjs构建一个强大的web前端系统。angularjs是由Google团队开发的一款非常优秀web前端框架。在当前如此多的web框架下,angularjs能脱颖而出,从架构设计上就高人一等,双向数据绑定,依赖注入,指令,MVC,模板。Angular.js创新地把后台技术融入前端开发,扫去jQuery一度的光芒。用angularjs就像写后台代码,更规范,更结构化,更可控。

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/angularjs-route-template/

AngularJS-route

前言

如果想开发一款类似gmail的web应用,我们怎么做呢?

以jQuery的思路,做响应式的架构设计时,我们要监听所有点击事件,通过事件函数触发我们加载数据,提交,弹框,验证等的功能;以 AngularJS的思路,做声明式的架构设计时,我们通过指令和路由先设定好,什么样的操作干什么事情,等事件发生时,程序就会知道该干什么了。

今天说一下,AngularJS是如何实现前端路由功能的!

目录

  1. AngularJS路由介绍
  2. 路由的代码实现
  3. 实现效果截图

1. AngularJS路由介绍

AngularJS路由功能是一个纯前端的解决方案,与我们熟悉的后台路由不太一样。后台路由,通过不同的URL会路由到不同的控制器上(controller),再渲染(render)到页面(HTML)。AngularJS的前端路由,需求提前对指定的(ng-app),定义路由规则(routeProvider),然后通过不同的URL,告诉(ng-app)加载哪个页面(HTML),再渲染到(ng-app)视图(ng-view)中。

AngularJS的前端路由,虽然URL输入不一样,页面展示不一样,其实完成的单页(ng-app)视图(ng-view)的局部刷新。这样来看,AngularJS做单页应用就有点标配的感觉了。

从这个角度想想,要实现一个gmail的应用,真的就不难了。

2. 路由的代码实现

理论不多说了,直接上代码!! 还是基于我们之前用yeoman构建的项目

业务场景:论坛功能,帖子列表页(list.html) 和 帖子内容页(detail.html)。

代码文件:

  • 1. 增加:app/demo-route.html
  • 2. 增加:app/views/route/list.html
  • 3. 增加:app/views/route/detail.html
  • 4. 修改: app/scripts/app.js
  • 5. 修改: app/scripts/controllers/main.js

1). 增加:app/demo-route.html
这个文件是主页面(ng-app),包含视图(ng-view)

<!doctype html>
<head>
<meta charset="utf-8">
<title>route</title>
</head>
<body ng-app="routeApp">
<h1>Route Demo index</h1>

<div ng-view></div>

<script src="bower_components/angular/angular.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
</body>
</html>

2). 增加:app/views/route/list.html
这个页面是布局模板,是HTML的代码片段。包括了一组ID的列表,通过ID列表的链接,可以进入到ID的详细页面。


<hr/>
<h3>Route : List.html</h3>

<ul>
<li ng-repeat="id in [1, 2, 3 ]">
<a href="#/list/{{ id }}"> ID{{ id }}</a>
</li>
</ul>

3). 增加:app/views/route/detail.html
这个页面是布局模板,是HTML的代码片段。通过ID访问,包含ID号, (ID的文章内容)


<hr/>
<h3>Route <span style="color: red;">{{id}}</span>: detail.html </h3>

4). 修改: app/scripts/app.js
这个是ng-app文件的定义,我们在demo-route.html中定义了routeApp,在这里需要声明。


var routeApp = angular.module('routeApp',[]);
routeApp.config(['$routeProvider',function ($routeProvider) {
      $routeProvider
      .when('/list', {
        templateUrl: 'views/route/list.html',
        controller: 'RouteListCtl'
      })
      .when('/list/:id', {
          templateUrl: 'views/route/detail.html',
          controller: 'RouteDetailCtl'
      })
      .otherwise({
        redirectTo: '/list'
      });
}]);

在routeApp模块中,我们定义了路由和布局模板。routeApp的默认URL是/list,即http://localhost:9000/demo-route.html#/list。 跳转详细页的路由是/list/:id,id为参数。

同时,/list的布局模板是views/route/list.html,属于RouteListCtl的控制器管理空间。

5). 修改: app/scripts/controllers/main.js
这个文件定义控制器controller。


routeApp.controller('RouteListCtl',function($scope) {
});
routeApp.controller('RouteDetailCtl',function($scope, $routeParams) {
    $scope.id = $routeParams.id;
});

分别对应该路由中的两个控制器声明。

程序写好,我们打开浏览器看效果。

3. 实现效果截图

别忘了用下面命令,启动程序。

grunt server

浏览器被自动打开,默认出的是http://localhost:9000/demo-route.html#/list, “#/list”是被redirectTo转向的结果。

AngularJS-route1

点击ID2的链接。

AngularJS-route2

页面被刷新了,出了detil的页面。同时,我们注意观察,页面没有整个刷新,而在视图中(ng-view)做的局部刷新。因为,chrome的开发工具的监控中,只是看到detail.html被加载。

我们再浏览地址栏中,输入212

http://localhost:9000/demo-route.html#/list/212

AngularJS-route3

观察chrome的开发工具的监控中,没有任何的networking操作。

在浏览地址栏中,再输入原来list的地址

http://localhost:9000/demo-route.html#/list

观察chrome的开发工具的监控,确认没有任何变化!!

从这个实验,我们看到AngularJS纯前端路由的实现思路,配合视图的局部刷新,把业务功能切片后分散到HTML的模板页面中。非常容易地实现了widget。并且,这种widget可重用性会非常高,能大大减少前端代码量。

后端组件化开发思路,流畅的嵌入前端。爽死啦!!!

转载请注明出处:
http://blog.fens.me/angularjs-route-template/

打赏作者

websocket服务器监控

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 程序员Java,R,PHP,Javascript
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-websocket-monitor/

monitor

前言
Websocket技术的强大之处在于,打通了浏览器和服务器的双向通信的高速通道。有了如此神器,我们就可以在web上面“为所欲为”了。

在web上面,模拟一个Shell窗口,已经不稀奇了。我们再通过shell获得Linux系统(CPU,IO)信息,时时返回到web端,通过Highcharts生成系统监控图,是不是很高端!?

快动起来吧,你也可以做到的。

目录

  1. 系统架构设计
  2. 通过Shell获得CPU及IO信息
  3. 创建nodejs项目
  4. 实现websocket服务器端
  5. 实现websocket客户端
  6. 用Highcharts生成时时系统监控图

1. 系统架构设计

monitor-architect

我的系统环境:

  • Linux: Ubuntu 12.04.2 LTS 64bit deskop
  • Nodejs: npm 1.2.21, node v0.11.2

~ uname -a
Linux conan-deskop 3.5.0-23-generic #35~precise1-Ubuntu SMP Fri Jan 25 17:13:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
~ cat /etc/issue
Ubuntu 12.04.2 LTS \n \l

~ npm -v
1.2.21
~ node -v
v0.11.2

2. 通过Shell获得CPU及IO信息

安装软件包sysstat

sudo apt-get install sysstat

配置sysstat


~ sudo vi /etc/default/sysstat
ENABLED="true"

~ sudo /etc/init.d/sysstat restart

查看CPU的使用情况


~ sar  1
Linux 3.5.0-23-generic (conan-deskop)   2013年08月23日  _x86_64_        (2 CPU)
18时13分39秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
18时13分40秒     all      0.52      0.00      0.52      0.00      0.00     98.96
18时13分41秒     all      0.52      0.00      0.52      0.00      0.00     98.96
18时13分42秒     all      0.00      0.00      0.52      0.00      0.00     99.48
18时13分43秒     all      0.00      0.00      1.03      0.00      0.00     98.97
18时13分44秒     all      0.00      0.00      0.52      0.00      0.00     99.48

我们只关系idle的情况。公式:CPU时间 = 100 – idle

查看内存的情况


~ sar -r 1
Linux 3.5.0-23-generic (conan-deskop)   2013年08月23日  _x86_64_        (2 CPU)
18时14分30秒 kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit  kbactive   kbinact
18时14分31秒    749672   1301452     63.45    123460    643808   2205152     53.18    716128    440348
18时14分32秒    749672   1301452     63.45    123460    643808   2205152     53.18    716128    440348
18时14分33秒    749672   1301452     63.45    123460    643808   2205152     53.18    716128    440348
18时14分34秒    749672   1301452     63.45    123460    643808   2205152     53.18    716128    440348

我们只关系memused的情况。公式:内存用量 = 100 – memused

3. 创建nodejs项目

创建一个express3的项目, 还没有装好nodejs环境的同学,请参考:准备Nodejs开发环境Ubuntu


~ cd /home/conan/nodejs
~ express -e nodejs-alert
~ cd nodejs-alert
~ sudo npm install

实现websocket通信,我们还需要安装socket.io


~ sudo npm install socket.io

4. 实现websocket服务器端

修改app.js文件,webscoket的实现用到socket.io,读取系统进程用到child_process。


var express = require('express')
  , path = require('path')
  , app = express()
  , server = require('http').createServer(app)
  , io = require('socket.io').listen(server)
  , worker = require('child_process');

// 其他代码省略
....

5. 实现websocket客户端

1). 首先是view/alert.html


~ vi view/alert.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Socket.io - System Alert</title>
<script src="javascripts/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src="javascripts/sysAlert.js"></script>
</head>
<body>
<h1>Socket.io - System Alert</h1>
<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
<script src="javascripts/highcharts/highcharts.js"></script>
<script src="javascripts/highcharts/modules/exporting.js"></script>
</body>
</html>

2). 下载必须的jquery.min.js和highcharts的整个库
3). 增加 javascripts/sysAlert.js 文件,用于客户端的实现。

  • 调用socket.io实现和服务器的通信
  • 调用Highcharts生成动态图

    // highcharts部分代码
    Highcharts.setOptions({
        global: {
            useUTC: false
        }
    });

    $('#container').highcharts({
        chart: {
            type: 'spline',
            animation: Highcharts.svg,
            marginRight: 10
        },
    
    // 代码省略
    ....
    });

    //// socket.io部分代码
    var socket = io.connect('http://192.168.1.20');
    socket.on('system', function (data) {

    // 代码省略
    ...
    });

下面是生成的 系统监控动态图!

6. 用Highcharts生成时时系统监控图

alert

内存,占用情况大概为84%,保持稳定。
CPU,占用情况。每次CPU有变化时,我都在做矩阵计算。

R语言矩阵计算代码


a<-matrix(1:1000000,ncol=1000)->b
b %*% a

转载请注明出处:
http://blog.fens.me/nodejs-websocket-monitor/

打赏作者