有的时候我看的不是你一时的能力,而是你面对世界的态度

分享2小段创业里的感想

有的时候我看的不是你一时的能力,而是你面对世界的态度。

麻雀总是叽叽喳喳,乌鸦总是唉身叹气。只有鹰,一直高飞。

这是一条微商发的动态,我当真了。原因很简单,它是心灵鸡汤类的,但在我的经验世界里它确确实实是这样的。

有这样一些人,写写代码,遇到点问题,就唉声叹气,“草”。。。

我对这种行为极其反感,这是无能的表现。而对于一个心智健全的人来说,遇到的问题是好事儿,有明确的解决方向的,随着经验的升级,遇到的问题越多,解决的越多,人就会愈加淡定。这样的结果是以后遇到未知问题,你都可以勇敢的面对。

有的时候我看的不是你一时的能力,而是你面对世界的态度。

我们回到这句话上,麻雀总是叽叽喳喳,它实际上一种目标不明确和从众的表现,人在没有目标的情况下,各种彷徨和迟疑,时间就是这样流逝的。看别人也这样,自己就更加相信这是对的,可是你可曾想过:“别人就一定是对的么?”?

比如大学的时候,所有人都玩游戏,逃课,泡妞,我也想这样,可是毕业了我怎么办呢?人家要么有钱要么有权,我连个能帮我的人都没有,我从众的起么?知道自己几斤几两比什么都重要,坚持着去做,一定会有回报的。

所以后面会说“只有鹰,一直高飞”

做一个目标明确,胸怀远大的人吧,能造福世界就造福世界,造福不了更多人,至少让家人过得好一点。

现在是一个非常好的时代,很多技术壁垒都已经不复存在,只要你努力去学,一万个小时真的可以成为专家,就算成为不了专家,就算是一个高级技工也可以活的很好。在大时代的变革中,一定会找到属于自己的位置的。

内裤穿反了,只有自己知道

有很多时候,人生很无奈,比如有一个月,你的产品没有任何大的进展,对于一个创业公司是很严重的事儿,而你作为一个负责人,你会有意无意的注意到你的小伙伴的懈怠或者不尽力、不给力,我想大部分人都会很郁闷的,想不小心眼都很难。

第一,我不能去责怪他们,创业公司团队稳定是第一要务

第二,我要反思,为啥会这样呢?是因为产品计划按部就班,安排的计划让他们闭门造车么?

我心里也没有答案的,而我能做的就是先走下去,在变革求利好。团队里首先会有人沉不住气,各种消极悲观,在创业公司是一旦不成功就会失败,损失很大。而在大公司,大不了从头再来。大公司承担这点损失,和创业公司承担这些损失的差别真是天上地下。

而我能做的只有乐观的陪着大家走下去。

某天我观察到我们的用户不爱用我们的一个功能,他们宁可微信发给我,也不愿意用,这是一个很严重的问题,于是我们所有人开会 ,讨论这个问题,团队像炸了窝一样,各抒己见,异常活跃,最总给出了一个很好的解决方案,并且把0.8之后的发展思路也捋顺了。所有人都很激动,士气里面都上来了。

人生真是很奇妙,创业对我做的改造最多的是心态,承受着巨大的压力,就好比“内裤穿反了,只有自己知道”一样,剩下的事儿就是遇到什么问题解决什么问题,只要尽力不后悔就可以了。

作为一个技术负责人,一往无前的冲,把事情做好就可以给团队很大的勇气和动力,另外要做的就是和小伙伴们一起肩并肩互相扶持,创业不一定成功,坚持不下去是一定不会成功的。

写点东西,鼓励自己继续努力下去,也希望大家周末愉快,事业顺利。

好了,不扯了,干活了。。。。

全文完

欢迎关注我的公众号【node全栈】

sails vs meteor

看一下sails的特性

安装

npm install -g sails

很简单就可以安装了。

看一下它的帮助

sails --help

 Usage: sails [command]

 Commands:

   version               
   lift [options]        
   new [options] [path_to_new_app]
   generate              
   console               
   consle                
   consloe               
   c                     
   www                   
   debug                 
   configure             
   help                  
   *                     

 Options:

   -h, --help     output usage information
   -v, --version  output the version number
   --silent       
   --verbose      
   --silly  

这是它的cli的全部功能

  • new 是根据模板创建项目
  • generate 是教授叫生成
  • lift 是启动服务器

了解这3个基本上就可以了。

如果你想进一步了解,可以继续看help

sails lift --help

 Usage: lift [options]

 Options:

   -h, --help     output usage information
   --prod         
   --port [port]  

创建一个项目看看

sails new helloworld
cd helloworld 
sails lift

我们需要看一下它的目录结构

➜  helloworld  tree -L 2    
.
├── Gruntfile.js
├── README.md
├── api
│   ├── controllers
│   ├── models
│   ├── policies
│   ├── responses
│   └── services
├── app.js
├── assets
│   ├── favicon.ico
│   ├── images
│   ├── js
│   ├── robots.txt
│   ├── styles
│   └── templates
├── config
│   ├── blueprints.js
│   ├── bootstrap.js
│   ├── connections.js
│   ├── cors.js
│   ├── csrf.js
│   ├── env
│   ├── globals.js
│   ├── http.js
│   ├── i18n.js
│   ├── local.js
│   ├── locales
│   ├── log.js
│   ├── models.js
│   ├── policies.js
│   ├── routes.js
│   ├── session.js
│   ├── sockets.js
│   └── views.js
├── node_modules
│   ├── ejs -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/ejs
│   ├── grunt -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt
│   ├── grunt-contrib-clean -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-clean
│   ├── grunt-contrib-coffee -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-coffee
│   ├── grunt-contrib-concat -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-concat
│   ├── grunt-contrib-copy -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-copy
│   ├── grunt-contrib-cssmin -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-cssmin
│   ├── grunt-contrib-jst -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-jst
│   ├── grunt-contrib-less -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-less
│   ├── grunt-contrib-uglify -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-uglify
│   ├── grunt-contrib-watch -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-contrib-watch
│   ├── grunt-sails-linker -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-sails-linker
│   ├── grunt-sync -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/grunt-sync
│   ├── include-all -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/include-all
│   ├── rc -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/rc
│   ├── sails -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails
│   └── sails-disk -> /Users/sang/.nvm/v0.10.38/lib/node_modules/sails/node_modules/sails-disk
├── package.json
├── tasks
│   ├── README.md
│   ├── config
│   ├── pipeline.js
│   └── register
└── views
    ├── 403.ejs
    ├── 404.ejs
    ├── 500.ejs
    ├── homepage.ejs
    └── layout.ejs

36 directories, 29 files

目录说明

app.js是入口,和express类似,但你看不到任何express的影子,它把东西都抽象到module里了,一般人看起来是有点难于理解的。

自己的代码要写在api目录里

├── api
│   ├── controllers
│   ├── models
│   ├── policies
│   ├── responses
│   └── services

而rails是在app目录里。sails放到api里,可能是现在写api比较多,2点好处

  • 前后端分离
  • 为移动端提供api

这命名也是比较好理解的

  • controllers和models是mvc里的m和c
  • services 一般是多model相关的业务操作,它只在controller里调用
  • responses比较有意思,它实际上是给res服务器响应对象增加方法,比如定义个aaa方法,你就可以res.aaa()了,对于扩展res是有好处的,算一个小亮点
  • policies 是权限控制部分,说白了也还是中间件,config.policies.js里声明权限,相信会玩死很多人,简单的acl还不如自己写

至此,核心的特性已经从目录看的差不多了。

node_modules

一般项目里做模块依赖,很烦。

rails里的bundle install也很烦。。。

但是sails利用软连接,把依赖的模块放到自己的npm安装目录,然后创建软连接,这样就可以在sails new之后,里面启动服务器,无需安装任何模块,这一点还是值得借鉴的

views

目录和express一样,它的默认模板引擎是ejs

/**
 * View Engine Configuration
 * (sails.config.views)
 *
 * Server-sent views are a classic and effective way to get your app up
 * and running. Views are normally served from controllers.  Below, you can
 * configure your templating language/framework of choice and configure
 * Sails' layout support.
 *
 * For more information on views and layouts, check out:
 * http://sailsjs.org/#!/documentation/concepts/Views
 */

module.exports.views = {
  engine: 'ejs',
  layout: 'layout',
  partials: false
};

各取所需吧,爱用啥用啥,比如又要掀起一场口水打仗了。。。。

assets

asset pipeline其实最早也是rails里的概念,可以将JS和CSS合并和压缩

打开package.json

"grunt": "0.4.2",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.10.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.5.0",
"grunt-contrib-cssmin": "~0.9.0",
"grunt-contrib-jst": "~0.6.0",
"grunt-contrib-less": "0.11.1",
"grunt-contrib-uglify": "~0.4.0",
"grunt-contrib-watch": "~0.5.3",
"grunt-sails-linker": "~0.9.5",
"grunt-sync": "~0.0.4",

从这里基本可以看出它的功能

  • js支持coffeescript
  • css支持less
  • 常用grunt操作,clean是清理,concat是合并,copy是复制,cssmin是压缩css,uglify是压缩js,watch是检测文件变动,sync应该是类似livereload之类的模块

后来发现它的文档里也有

Here are a few things that the default Grunt configuration in Sails does to help you out:

  • Automatic LESS compilation
  • Automatic JST compilation
  • Automatic Coffeescript compilation
  • Optional automatic asset injection, minification, and concatenation
  • Creation of a web ready public directory
  • File watching and syncing
  • Optimization of assets in production

多了一个jst编译,是grunt-contrib-jst做的事儿,其他大致一样

默认它是没有grunt-cli模块的,需要自己安装

npm install -g grunt-cli

然后执行grunt命令

➜  helloworld  grunt       
Running "clean:dev" (clean) task
Cleaning .tmp/public...OK

Running "jst:dev" (jst) task
>> Destination not written because compiled files were empty.

Running "less:dev" (less) task
File .tmp/public/styles/importer.css created: 0 B → 619 B

Running "copy:dev" (copy) task
Copied 3 files

Running "coffee:dev" (coffee) task

Running "sails-linker:devJs" (sails-linker) task
padding length 4
File "views/layout.ejs" updated.

Running "sails-linker:devStyles" (sails-linker) task
padding length 4
File "views/layout.ejs" updated.

Running "sails-linker:devTpl" (sails-linker) task
padding length 4
File "views/layout.ejs" updated.

Running "sails-linker:devJsJade" (sails-linker) task

Running "sails-linker:devStylesJade" (sails-linker) task

Running "sails-linker:devTplJade" (sails-linker) task

Running "watch" task
Waiting...

这里就是grunt的tasks了,没有啥可说的,我不是很喜欢grunt,我更喜欢gulp,sails默认是grunt,但文档里有有gulp版本的,可以去试试

另外要说明的是上线的时候,静态资源处理

  • 要么cdn
  • 要么nginx反向代理

这东西的价值就非常好了。

config

太多了,蛋疼。。。。

console

sails console

是通过命令行来处理数据的,一般简单测试会用

测试模型以及orm方法还是很爽的

来个例子玩玩吧

生成controller玩玩

sails generate controller comment create destroy tag like

生成的代码

/**
 * CommentController
 *
 * @description :: Server-side logic for managing comments
 * @help        :: See http://sailsjs.org/#!/documentation/concepts/Controllers
 */

module.exports = {



  /**
   * `CommentController.create()`
   */
  create: function (req, res) {
    return res.json({
      todo: 'create() is not implemented yet!'
    });
  },


  /**
   * `CommentController.destroy()`
   */
  destroy: function (req, res) {
    return res.json({
      todo: 'destroy() is not implemented yet!'
    });
  },


  /**
   * `CommentController.tag()`
   */
  tag: function (req, res) {
    return res.json({
      todo: 'tag() is not implemented yet!'
    });
  },


  /**
   * `CommentController.like()`
   */
  like: function (req, res) {
    return res.json({
      todo: 'like() is not implemented yet!'
    });
  }
};

这种意义不是很大。。。

生成模型

sails generate model user name:string email:string password:string

基于Waterline的,除了可以适配多个db外,没看出啥特别的,写法一点也不好

如果直接生成api呢

➜  helloworld  sails generate api sssd sdf jklsdf werjk jlksd sd 
info: Created a new api!

擦,生成SssdController,每一个属性生成一个方法

Sssd model里只有属性对。。。。哭了

另外把路由配置在config.routes.js里,声明式做法也蛮蛋疼的,太麻烦了。。。。

另外说基于blueprints的restful api,恕我愚钝,没太看明白

难道

sails generate api dentist

所有的代码都隐掉,你无法做任何修改么?

orm

  • 强大的关联
  • console 调试

如果一个orm,在js的语法下,兼容Any database,你说能不恶心么?把nosql和rdbms搞到一起,Waterline也是醉了。。。

他们还自己吹牛

  • activerecord
  • hibernate

类的。。。

凑合用吧

总结

sails能做到这样已经很不错了,有很多点都是和rails概念类似,也很用心

但出于js语法以及各种db等,还是有很多难受的地方

项目选型思考

  • 如果有熟悉rails思想的人可以考虑用
  • 如果有大牛可以用
  • 如果是新手或者无人解决框架问题,还是老老实实的express吧

全文完

欢迎关注我的公众号【node全栈】

centos7作为web服务器优化

centos7作为web服务器优化

1、关闭firewalld:

systemctl stop firewalld.service #停止firewall
systemctl disable firewalld.service #禁止firewall开机启动

2、安装iptables防火墙

yum install iptables-services #安装
  iptables -nvL   #查看信息

3、加大打开文件数的限制(open files)

查看

ulimit -n
  ulimit -a

  vi /etc/security/limits.conf

最后添加

* soft nofile 1024000 
* hard nofile 1024000 
hive   - nofile 1024000 
hive   - nproc  1024000 

用户进程限制

vi /etc/security/limits.d/20-nproc.conf

  #加大普通用户限制  也可以改为unlimited
  *          soft    nproc     40960
  root       soft    nproc     unlimited

此步骤需要重启机器生效,可以设置完后再重启

4、网络线程优化

vi /etc/sysctl.conf

加入下面几行

# Disable IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Determines how often to check for stale neighbor entries.
net.ipv4.neigh.default.gc_stale_time=120

# Using arp_announce/arp_ignore to solve the ARP Problem
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.all.arp_announce=2
vm.swappiness = 0
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
net.ipv4.conf.lo.arp_announce=2


net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 15

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30


执行下面命令生效
/sbin/sysctl -p

5.时间校对

cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

  ntpdate us.pool.ntp.org


  crontab -e
  0-59/10 * * * * /usr/sbin/ntpdate us.pool.ntp.org | logger -t NTP

  service crond restart

6.关闭root登录并修改ssh端口

http://linuxg.net/5-useradd-command-examples/

最常用的

sudo useradd -m -d /home/mike1 -s /bin/bash -c "the mike1 user" -U mike1

添加普通用户

groupadd test
  useradd -d /home/www/ -g test test1
  passwd test1

iptables 开启6666端口 此步骤须先于下面步骤,否则会造成ssh连不上的

vi /etc/sysconfig/iptables 

添加

-A INPUT -p tcp -m state --state NEW -m tcp --dport 6666 -j ACCEPT

修改ssh端口

vi /etc/ssh/sshd_config 

找到#Port 22一段,这里是标识默认使用22端口,修改为如下:

#Port 22 
Port 6666 

关闭root登录权限

PermitRootLogin yes

改为

PermitRootLogin no

保存退出

测试新用户能正常登录后方可以在iptables 禁用22端口了

7.禁止开启ping

禁止

echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all

开启

echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all

永久保存

vi /etc/rc.d/rc.local
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all

8.网络命令ifconfig

bash: ifconfig: 未找到命令
  yum -y install net-tools

9.nginx转发请求

location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_pass http://127.0.0.1:3002;
    proxy_redirect off;
}

10.资源

全文完

欢迎关注我的公众号【node全栈】

TAP & Tape, the awesome way to test JavaScript

TAP & Tape, the awesome way to test JavaScript

原文,如果有时间可以翻译一下,很不错的一篇介绍node测试相关的文章

node-tap & tape are simple, awesome testing tools for JavaScript. The JavaScript community has grown up with testing culture, and the vast majority of projects use larger tools like Mocha and Jasmine. Recently I’ve been switching lots of projects over to tap & tape, and want to share why.

substack, the creator of tape, has already written a bit about his process and thinking - here’s just a little more, written from the perspective of a former mocha user.

What it sorts down into is roughly three parts: the protocol, browserify, and magic.

The Test Anything Protocol

TAP, or the Test Anything Protocol is the definition of ‘tried & true’: it’s been around since 1987 and has been implemented in a ton of languages. It’s just a dead-simple way to format test results, like

TAP version 13
# equivalence
ok 1 these two numbers are equal

1..1
# tests 1
# pass  1

# ok

Having this format be super simple means that you can combine tools that have common expectations. For instance, faucet is a node module that gives pretty, summarized results for your tests, but it doesn’t directly plug into tape or node-tap - all it cares about is the TAP protocol, so you can pipe results into it. You could even pipe a TAP producer in a different language into it, and it’ll work just the same.

Browserify

browserify is another awesome tool written by substack. With it, you can use node-style require() calls in code you’re going to run in browsers, and then use the browserify command to stitch them all together and make something you can throw in a script tag.

Browserify has been huge for writing cross-platform libraries, and it’s been huge for building things. Mapbox.com and Mapbox.js are both constructed this way - individual libraries on npm, package.json just like nodejs code, and then browserify to bring it to the web.

Long story short, browserify ‘just works’ with tape. While mocha has something of an unusual browser story - it’s hard to get at the files you’d want to run tests in a browser, and then when you do, if you want to run tests in both browsers and node, it’s not straightforward to conditionally require things sometimes.

So, to run browserify tests in a browser, you can just run

$ npm install -g browserify testling
$ browserify test/test.js | testling

Simple as that. Since tape is just a node module and browserify turns the whole thing into just JavaScript, it’s easy to run it anywhere you want - embedded in a webpage, in a browser, or wherever. tape uses console.log to write its results, which is super easy to pull out of a headless browser.

Concepts

A quick review: TAP is a standard for test output. node-tap and tape are two node modules that let you write tests that output results in the TAP protocol.

So, we’ve discussed TAP a little bit, and you might notice that mocha supports TAP too. So why not just use mocha to write tests? Let’s talk about that.

Magic

Mocha does a little magic. With only few exceptions, nodejs has the assumption that any variables on a page will come from obvious places:

// this comes from a module
var module = require('module');

In the interest of simplicity, mocha doesn’t follow this rule. Your mocha test files have assumptions:

var assert = require("assert")
describe('truth', function(){
  it('should find the truth', function(){
    assert.equal(1, 1);
  })
})

Keen eyes will notice that assert entered the stage by a require() call, but describe and it didn’t - they appear magically.

On the other hand, a basic tape test:

var test = require('tape').test;
test('equivalence', function(t) {
    t.equal(1, 1, 'these two numbers are equal');
    t.end();
});

test comes from require(), and t comes from the closure. Simple enough. This lack of abstraction has two awesome advantages:

1.It’s easier to form a mental model of what’s going on, so it’s easier to hack with it and know what’ll happen. What if I call a function with t and then call functions off of t? It’ll work.
2.Tests are code. So, you can run tests as modules with node, just like you’d run other code: node test/test.js, and it works. As opposed to needing a ‘test runner’ binary that contains some of the code the test really needs.

Testling & Travis-CI

Continuous Integration, where every commit to GitHub is automatically tested, has become a necessity. Setting the green ‘this works’ badge on projects means something, and we’ve found that running tests on remote hosts can give a better sanity check than just running them locally - the environment is constructed from scratch, there aren’t any stray files that make things work.

tape works with testling just like mocha - the same minimal .travis.yml file ‘just works’ as long as npm install and npm test do their things. But on top of that, you can use Testling-CI and testling, and test commits in browsers. Testling-CI works the same as Travis: set a webhook, tweak a few package.json properties, and you’ll get a page of your own with results. testling, on the other hand, runs headless tests locally, with your real browsers, not with phantomjs or another custom abstraction. In combination, this means that you can easily cross-browser test code. And if the abstractions break, it’s easy to just browserify the code and run the tests manually.

全文完

欢迎关注我的公众号【node全栈】

mongodb的ttl

TTL是 Time To Live的缩写,译为生存时间。
TTL Collection(淘汰过期数据)

MongoDB 2.2 引入一个新特性 —— TTL 集合,TTL 集合支持失效时间设置,当超过指定时间后,集合自动清除超时的文档,这用来保存一些诸如session会话信息的时候非常有用,或者存储缓存数据使用。

如果你想使用 TTL 集合,你要用到 expireAfterSeconds 选项:

db.ttl.ensureIndex({"Date": 1}, {expireAfterSeconds: 300})

限制

使用 TTL 集合时是有限制的:

  • 你不能创建 TTL 索引,如果要索引的字段已经在其他索引中使用
  • 索引不能包含多个字段
  • 索引的字段必须是一个日期的 bson 类型

如果你违反了上述三个规则,那么超时后文档不会被自动清除。

文档是怎么被删除的?

mongod 后台进程会实时跟踪过期的文档并删除,我们来对此进行检查测试:

首先我们创建一个索引并设置 10 秒钟后失效:

db.ttl_collection.ensureIndex( { "Date": 1 }, { expireAfterSeconds: 10 } )

然后插入文档:

db.ttl_collection.insert({"Date" : new Date()})

因为我们想象该文档会在 10 秒后删除,可是我在我的电脑上测试多次,结果却不太一样。

有些时候 mongod 在 18 秒后删除,有些时候是 40 秒。

为什么会这样呢?我们已经告诉 MongoDB 要在 10 秒后删除,可事实上却不是如此。

例如,这一次是 45 秒中后才删除:

因为 mongod 后台任务每分钟检查一次过期文档,因此在时间的处理上总有一定的差异,但这个差异不会超过 1 分钟,这也取决于 MongoDB 实例当前的负荷情况。

mongo session

{
  "_id": "L3b_elXZtbDBxOkwFICHyBvfn7etKiJP",
  "session": {
    "cookie": {
      "originalMaxAge": 1800000,
      "expires": "2015-06-19T09:16:49.469Z",
      "secure": false,
      "httpOnly": true,
      "path": "/"
    },
    "current_user": {
      "_id": "55783c18ad0e6c3e1bb03399",
      "username": "kezhi",
      "password": "111111",
      "__v": 0
    }
  },
  "expires": ISODate("2015-06-19T09:16:49.524Z")
}

好奇怪,我刚建立的session

db.sessions.stats()


{
    "ns" : "vsq.sessions",
    "count" : 1,
    "size" : 240,
    "avgObjSize" : 240,
    "numExtents" : 1,
    "storageSize" : 8192,
    "lastExtentSize" : 8192,
    "paddingFactor" : 1,
    "paddingFactorNote" : "paddingFactor is unused and unmaintained in 3.0. It remains hard coded to 1.0 for compatibility only.",
    "userFlags" : 1,
    "capped" : false,
    "nindexes" : 2,
    "indexDetails" : {},
    "totalIndexSize" : 16352,
    "indexSizes" : {
        "_id_" : 8176,
        "expires_1" : 8176
    },
    "ok" : 1
}

全文完

欢迎关注我的公众号【node全栈】

Koa 还是 Express?

群里很多人在问到底该用Koa还是express,本文会对比2个框架的各种细节,并给出指导意见,希望能够为大家解惑。

koa

koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

版本要求

Koa 目前需要 >=0.11.x版本的 node 环境。并需要在执行 node 的时候附带 –harmony 来引入 generators 。

express无所谓,目前0.10+都ok,甚至更低版本

中间件变化

Koa 应用是一个包含一系列中间件 generator 函数的对象。 这些中间件函数基于 request 请求以一个类似于栈的结构组成并依次执行。 Koa 类似于其他中间件系统(比如 Ruby’s Rack 、Connect 等), 然而 Koa 的核心设计思路是为中间件层提供高级语法糖封装,以增强其互用性和健壮性,并使得编写中间件变得相当有趣。

Koa 包含了像 content-negotiation(内容协商)、cache freshness(缓存刷新)、proxy support(代理支持)和 redirection(重定向)等常用任务方法。 与提供庞大的函数支持不同,Koa只包含很小的一部分,因为Koa并不绑定任何中间件。

和 express 基于的中间件Connect,差别并不大,思想都是一样的,它里面说的

增强其互用性和健壮性

我还没玩出太多感想,请大家指点

除了yield next;外,并无其他

yield要说一下,必须在处理的中间件里才会回调

比如

var koa = require('koa');
var app = koa();

//1 x-response-time

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  this.set('X-Response-Time', ms + 'ms');
});

//2 logger

app.use(function *(next){
  var start = new Date;
  yield next;
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});

//3 response

app.use(function *(){
  this.body = 'Hello World';
});

app.listen(3000);

在程序启动的时候,1和2是没有执行的,只有当执行到任意请求,比如3的时候,它才会调用1和2

错误处理

koa

app.on('error', function(err){
  log.error('server error', err);
});

而在新版的express里

server.on('error', onError);


/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

二者实际上没有啥大差别

Koa Context

将 node 的 request 和 response 对象封装在一个单独的对象里面,其为编写 web 应用和 API 提供了很多有用的方法。

这些操作在 HTTP 服务器开发中经常使用,因此其被添加在上下文这一层,而不是更高层框架中,因此将迫使中间件需要重新实现这些常用方法。

context 在每个 request 请求中被创建,在中间件中作为接收器(receiver)来引用,或者通过 this 标识符来引用:

app.use(function *(){
  this; // is the Context
  this.request; // is a koa Request
  this.response; // is a koa Response
});

比express里爽一些,express里中间件可变参数还是会比较恶心,而且性能也不好

对比一些api

  • req
  • res

基本上一模一样

二者比较总结

https://github.com/koajs/koa/blob/master/docs/koa-vs-express.md

Feature Koa Express Connect
Middleware Kernel
Routing
Templating
Sending Files
JSONP

Does Koa replace Express?

It’s more like Connect, but a lot of the Express goodies were moved to the middleware level in Koa to help form a stronger foundation. This makes middleware more enjoyable and less error-prone to write, for the entire stack, not just the end application code.

Typically many middleware would re-implement similar features, or even worse incorrectly implement them, when features like signed cookie secrets among others are typically application-specific, not middleware specific.

拿koa来比较express并不太合适,可以说它是介于connect和express中间的框架

  • 与connect类似都是调用栈思想,但修改了中间件模式,使用generator
  • 把express里的一些好的东西加进去,但剔除了路由,视图渲染等特性

总结

koa是一个比express更精简,使用node新特性的中间件框架,相比之前express就是一个庞大的框架

  • 如果你喜欢diy,很潮,可以考虑koa,它有足够的扩展和中间件,而且自己写很简单
  • 如果你想简单点,找一个框架啥都有,那么先express

koa是大势所趋,我很想用,但我目前没有选koa,我的考虑

  • 0.11下我目前没有搞定node-inspector,所以我啥时候搞定,啥时候迁移
  • 团队成本问题,如果他们连express都不会,上来就koa,学习曲线太逗,不合适
  • 目前基于express的快读开发框架需要一段时间迁移到koa

和es6的考虑是一样的,又爱又恨,先做技术储备,只要时机ok,毫不犹豫的搞起。

目前express由strongloop负责,它的下一步如何发展,还说不好,比如5.0、6.0是否会用koa作为中间件也不好说

koa代码很少,可以很容易读完

https://github.com/koajs/koa

另外值得提得一点是,核心开发者 @dead-horse 是阿里的员工,赞一下国内的开源。

全文完

欢迎关注我的公众号【node全栈】

Sysstat

Sysstat 是一个软件包,包含监测系统性能及效率的一组工具,这些工具对于我们收集系统性能数据,比如CPU使用率、硬盘和网络吞吐数据,这些数据的收集和分析,有利于我们判断系统是否正常运行,是提高系统运行效率、安全运行服务器的得力助手;

install

wget http://pagesperso-orange.fr/sebastien.godard/sysstat-11.1.5.tar.gz
cd sysstat-11.1.5
./configure
make
sudo make install

可用命令

Sysstat 软件包集成如下工具:

* iostat 工具提供CPU使用率及硬盘吞吐效率的数据;
* mpstat 工具提供单个处理器或多个处理器相关数据;
* sar 工具负责收集、报告并存储系统活跃的信息;
* sa1 工具负责收集并存储每天系统动态信息到一个二进制的文件中。它是通过计划任务工具cron来运行,
    是为sadc所设计的程序前端程序;
* sa2 工具负责把每天的系统活跃性息写入总结性的报告中。它是为sar所设计的前端 ,要通过cron来调用
* sadc 是系统动态数据收集工具,收集的数据被写一个二进制的文件中,它被用作sar工具的后端;
* sadf 显示被sar通过多种格式收集的数据;

用法说明

sar

man sar

sar - Collect, report, or save system activity information.

[deploy@iZ251uvtr2b ~]$ sar -u -o datafile 2 3
Linux 3.10.0-229.4.2.el7.x86_64 (iZ251uvtr2bZ)     06/15/2015     _x86_64_    (4 CPU)

11:34:42 AM     CPU     %user     %nice   %system   %iowait    %steal     %idle
11:34:44 AM     all      0.13      0.00      0.13      0.00      0.00     99.75
11:34:46 AM     all      0.13      0.00      0.00      0.00      0.00     99.87
11:34:48 AM     all      0.13      0.00      0.13      0.13      0.00     99.62
Average:        all      0.13      0.00      0.08      0.04      0.00     99.75

关于 Sysstat 计划任务

crontab即可,没啥特殊的

分享一些资源

ionic 分享到微信的例子,还不错

pixi 是 Super fast HTML 5 2D rendering engine that uses webGL with canvas fallback

h5游戏引擎

https://github.com/GoodBoyDigital/pixi.js

它比较有意思是的gulp的加载,https://github.com/GoodBoyDigital/pixi.js/tree/master/gulp/tasks

它使用的是require-dir来加载的,我也写了一个

示例

var gulp        = require('gulp');

// Require all tasks in vendor/tasks, including subfolders
require('mount-tasks')(__dirname + '/tasks')

// default task
gulp.task('default', ['clean', 'build']);

好的开源项目

https://github.com/chmontgomery/gulp-help

它很好的把cli的模式引入到gulp插件了

通过

gulp help

给对应的task增加说明描述信息,使其更具有可读性

https://github.com/nodeonly/Chart.js

演示http://www.chartjs.org/

基于canvas绘制的图表工具,天生h5,支持响应式,基本图表该有的都有

最让我吃惊的是它又将近15000个star。。。。

https://github.com/meteoric/meteor-ionic

Ionic components for Meteor. No Angular!
http://meteoric.github.io

整合2个怪兽,不过还是很好用的,这个栈很潮,如果你搞的定,可以试试

地区问题

东莞不支持子区域,比如厚街镇这样的地方是没有,需要手动添加

另外像太仓市,实际上是江苏苏州的地级市,但是客户并不清楚,所以级联选择地址是有很多弊病的。

还是需要更智能一点的组件。

状态问题

今天发现一些数据不对,是因为状态跳转,之前有状态0,但现在状态0和1合并为1,所以0成了孤儿。

那么查询的时候,如果没有状态 in (1,2,3),那么你肯定会把0查出来,这样就会导致数据问题,非常难差

所以防御式编程的时候,需要处理in这样的方式

全文完

欢迎关注我的公众号【node全栈】

Nodejs RESTFul架构实践之api

why token based auth?

此段摘自

英文原文

在讨论了关于基于 token 认证的一些基础知识后,我们接下来看一个实例。看一下下面的几点,然后我们会仔细的分析它:

  1. 多个终端,比如一个 web 应用,一个移动端等向 API 发送特定的请求。
  2. 类似 https://api.yourexampleapp.com 这样的请求发送到服务层。如果很多人使用了这个应用,需要多个服务器来响应这些请求操作。
  3. 这时,负载均衡被用于平衡请求,目的是达到最优化的后端应用服务。当你向 https://api.yourexampleapp.com 发送请求,最外层的负载均衡会处理这个请求,然后重定向到指定的服务器。
  4. 一个应用可能会被部署到多个服务器上(server-1, server-2, …, server-n)。当有请求发送到https://api.yourexampleapp.com 时,后端的应用会拦截这个请求头部并且从认证头部中提取到 token 信息。使用这个 token 查询数据库。如果这个 token 有效并且有请求终端数据所必须的许可时,请求会继续。如果无效,会返回 403 状态码(表明一个拒绝的状态)。

基于 token 的认证在解决棘手的问题时有几个优势:

  • Client Independent Services 。在基于 token 的认证,token 通过请求头传输,而不是把认证信息存储在 session 或者 cookie 中。这意味着无状态。你可以从任意一种可以发送 HTTP 请求的终端向服务器发送请求。
  • CDN 。在绝大多数现在的应用中,view 在后端渲染,HTML 内容被返回给浏览器。前端逻辑依赖后端代码。这中依赖真的没必要。而且,带来了几个问题。比如,你和一个设计机构合作,设计师帮你完成了前端的 HTML,CSS 和 JavaScript,你需要拿到前端代码并且把它移植到你的后端代码中,目的当然是为了渲染。修改几次后,你渲染的 HTML 内容可能和设计师完成的代码有了很大的不同。在基于 token 的认证中,你可以开发完全独立于后端代码的前端项目。后端代码会返回一个 JSON 而不是渲染 HTML,并且你可以把最小化,压缩过的代码放到 CDN 上。当你访问 web 页面,HTML 内容由 CDN 提供服务,并且页面内容是通过使用认证头部的 token 的 API 服务所填充。
  • No Cookie-Session (or No CSRF) 。CSRF 是当代 web 安全中一处痛点,因为它不会去检查一个请求来源是否可信。为了解决这个问题,一个 token 池被用在每次表单请求时发送相关的 token。在基于 token 的认证中,已经有一个 token 应用在认证头部,并且 CSRF 不包含那个信息。
  • Persistent Token Store 。当在应用中进行 session 的读,写或者删除操作时,会有一个文件操作发生在操作系统的temp 文件夹下,至少在第一次时。假设有多台服务器并且 session 在第一台服务上创建。当你再次发送请求并且这个请求落在另一台服务器上,session 信息并不存在并且会获得一个“未认证”的响应。我知道,你可以通过一个粘性 session 解决这个问题。然而,在基于 token 的认证中,这个问题很自然就被解决了。没有粘性 session 的问题,因为在每个发送到服务器的请求中这个请求的 token 都会被拦截。

这些就是基于 token 的认证和通信中最明显的优势。基于 token 认证的理论和架构就说到这里。下面上实例。

这段本来想自己写,不过自己写也这些内容,节省点时间

jwt加密和解密

JWT 代表 JSON Web Token ,它是一种用于认证头部的 token 格式。这个 token 帮你实现了在两个系统之间以一种安全的方式传递信息。出于教学目的,我们暂且把 JWT 作为“不记名 token”。一个不记名 token 包含了三部分:header,payload,signature。

header 是 token 的一部分,用来存放 token 的类型和编码方式,通常是使用 base-64 编码。

payload 包含了信息。你可以存放任一种信息,比如用户信息,产品信息等。它们都是使用 base-64 编码方式进行存储。
signature 包括了 header,payload 和密钥的混合体。密钥必须安全地保存储在服务端。

nodejs实现的jwt代码

http://github.com/auth0/node-jsonwebtoken

主要3个方法

  • jwt.sign
  • jwt.verify
  • jwt.decode

需要小心的密钥在多线程或集群下的处理。

加解密一个对象的时间,远远比查询数据库的代价小,唯一可能有的是token有效期的校验,代价极其小。

优雅之写法

授权获取token

在app/routes/api/index.js里

// auth
router.post('/auth', function(req, res, next) {
  User.one({username: req.body.username},function(err, user){
    if (err) throw err;
    console.log(user);

    if (!user) {
        res.json({ success: false, message: '认证失败,用户名找不到' });
    } else if (user) {

      // 检查密码
      if (user.password != req.body.password) {
          res.json({ success: false, message: '认证失败,密码错误' });
      } else {
        // 创建token
        var token = jwt.sign(user, 'app.get(superSecret)', {
            'expiresInMinutes': 1440 // 设置过期时间
        });

        // json格式返回token
        res.json({
            success: true,
            message: 'Enjoy your token!',
            token: token
        });
      }
    }
  });
});

测试

curl -d "username=sang&password=000000" http://127.0.0.1:3019/api/auth

返回

{"success":true,"message":"Enjoy your token!","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4"}%

路由处理

app/routes/api/groups.js

var express = require('express');
var router = express.Router();

var $ = require('../../controllers/groups_controller');
var $middlewares = require('mount-middlewares');

router.get('/list', $middlewares.check_api_token, $.api.list);

module.exports = router;

核心代码

router.get('/list', $middlewares.check_api_token, $.api.list);

说明

  • 使用了$middlewares.check_api_token中间件
  • 核心业务逻辑在$.api.list
  • 和其他的express路由用法一样,无他

中间件$middlewares.check_api_token

/*!
 * Moajs Middle
 * Copyright(c) 2015-2019 Alfred Sang <shiren1118@126.com>
 * MIT Licensed
 */

var jwt = require('jsonwebtoken');//用来创建和确认用户信息摘要
// 检查用户会话
module.exports = function(req, res, next) {
  console.log('检查post的信息或者url查询参数或者头信息');
  //检查post的信息或者url查询参数或者头信息
  var token = req.body.token || req.query.token || req.headers['x-access-token'];
  // 解析 token
  if (token) {
    // 确认token
    jwt.verify(token, 'app.get(superSecret)', function(err, decoded) {
      if (err) {
        return res.json({ success: false, message: 'token信息错误.' });
      } else {
        // 如果没问题就把解码后的信息保存到请求中,供后面的路由使用
        req.api_user = decoded;
        console.dir(req.api_user);
        next();
      }
    });
  } else {
    // 如果没有token,则返回错误
    return res.status(403).send({
        success: false,
        message: '没有提供token!'
    });
  }
};

这个很容易解释,只要参数有token或者头信息里有x-access-token,我们就认定它是一个api接口,

校验通过了,就把token的decode对象,也就是之前加密的用户对象返回来,保存为req.api_user

业务代码

app/controllers/groups_controller.js

exports.api = {
  list: function (req, res, next) {
    console.log(req.method + ' /groups => list, query: ' + JSON.stringify(req.query));

    var user_id = req.api_user._id;

    Group.query({ower_id: user_id}, function(err, groups){
      console.log(groups);
      res.json({
        data:{
          groups : groups
        },
        status:{
          code  : 0,
          msg   : 'success'
        }
      })
    });
  }
}

让scaffold生成代码和api共存,清晰明了

说明一下

  • req.api_user是$middlewares.check_api_token里赋值的
  • 写一个下查询接口,返回json即可

测试接口

然后让我们来测试一下

curl http://127.0.0.1:3019/api/groups/list\?token\=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NTc4MzJkZjk0ZTFjN2YyMDJmYTVlNGUiLCJ1c2VybmFtZSI6InNhbmciLCJwYXNzd29yZCI6IjAwMDAwMCIsImF2YXRhciI6IiIsInBob25lX251bWJlciI6IiIsImFkZHJlc3MiOiIiLCJfX3YiOjB9.Wv5za6GpJSMi346o625_8FxfoM4dJ1cWNuezG10zQG4


  {"data":{"groups":[{"_id":"557d32a282f9ddcc76a540e8","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32b082f9ddcc76a540e9","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d32f082f9ddcc76a540ea","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33804f5905de78e1c25a","name":"sjkljkl","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"","is_public":"","__v":0},{"_id":"557d33984f5905de78e1c25b","name":"anan","desc":"2323","ower_id":"557832df94e1c7f202fa5e4e","users":"2323","is_public":"232","__v":0}]},"status":{"code":0,"msg":"success"}}  

模型,查询以及其他

模型,查询以及其他,沿用之前的东西,仍然以mongoosedao为主

  • one
  • all
  • query

基本上够用了

如果还想玩的更high一点,可以增加一个service层,把多个model的操作放到里面。

总结

以后写api,可以这样玩

1) 在app/routes/api/目录下建立对应的api文件,比如groups.js,topics.js,users.js等

2) 然后在对应的controller里,增加

exports.api = {
  aa:function(req, res, next){
    var user_id = req.api_user._id;
  },
  bb:function(req, res, next){
    var user_id = req.api_user._id;
  }
}

3) 简单写点模型的查询方法就可以了

是不是很简单?

  • 使用mount-routes自动挂载routes
  • 使用mongoosedao更简单的接口

如果以后再提供生成器呢?

想想就很美好,美好就继续美好吧~

补一下

全文完

欢迎关注我的公众号【node全栈】

Nodejs部署再思考

pm2

好处就不说了

今天遇到一个奇怪的问题,部署一台新的机器,无论怎么弄,什么版本都会报错

1
js-bson: Failed to load c++ bson extension, using pure JS version

我曾经无数次的怀疑,是mongodb的问题,于是我手动编译了bson和bson-ext,以及相关的

  • connect-mongo
  • mongoose

可是还不好使。。。。。

我觉得很奇怪,没有理由啊,我去查pm2的源码和文档,发现cluster mode必须是0.12之后才有,之前的方式都是fork模式的。

最后发现 https://github.com/Unitech/PM2/issues/957

@jorge-d commented on Mar 11

1
2
I just tested with node v0.12.0 and pm2 0.12.7 and everything seems to be working fine...
Could you do a simple test ? run pm2 kill; pm2 start app.js -i 0 and tell me whether it starts in cluster of fork mode ?

然后那货的问题就解决了,然后我试了一下

pm2 kill

是杀死pm2自己的进程,然后产生新的,相当于重启。

然后再跑就可以了

反思一下,为什么会出现这样的问题?

node的版本较多

  • nodejs(0.10 和0.12 集群模块不一样,还有就是对es6的支持)
  • iojs(目前以及2.3+)

我最开始部署的时候用的是iojs最新版本,然后我启动过pm2,克隆完源码之后,npm install的时候有模块无法编译,所以我就切换0.10了

哎,我没有想到pm2会一直存在,只要不kill就在,而且我不喜欢动不动就重启服务器,所以花了几个小时的时间才解决。

pm2 deploy

ruby 里有 capistrano 部署

node世界里也有shipit

说白点都是“Universal automation and deployment tool”,通用自动化部署工具而已。

实际上Unitech也打算给pm2增加这样的功能,让pm2成为一个全能的货

它完成了几件事儿

  • evn环境变量
  • ssh远程key配置
  • git代码以及分支切换
  • 各种回调钩子,比如post-deploy

以上特性足矣和任何ci或者其他自动化工具集成了,目前可以当小白鼠的。

部署相关,小弟们必看

部署的基本常识

NGINX+PM2组合

注意配置

server {  
  server_name your.domain.com;
  listen 80;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;
    proxy_pass http://127.0.0.1:3000;
    proxy_redirect off;
  }
}

图片处理https://serversforhackers.com/nginx-caching

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires 1y;
    log_not_found off;
}

如果有兴趣,也可以看一下我写的node部署

http://nodeonly.com/2015/06/02/deploy.html

sudo systemctl enable nginx.service
sudo systemctl enable redis.service
sudo chkconfig mongod on

nohup node /home/deploy/workspace/oschina/mxb-sms/app.js > /home/deploy/workspace/oschina/mxb-sms/sms.log 2>&1 &

这样的部署真的足够了么?

一台一台机器的部署也是醉了。。。。哥是程序员啊

复用和弹性伸缩才是真爱

所以比较好的方式是使用docker来处理,docker是lxc,里面可以部署任意linux系统
配置各种环境,完成任意弹性扩容

目前nearfarm已经有一个不错的实现

https://github.com/nearform/nscale

具体原理以及相关实践,稍后整理

全文完

欢迎关注我的公众号【node全栈】

监控Nodejs的页面响应时间

监控Nodejs的页面响应时间

最近想监控一下Nodejs的性能。记录分析Log太麻烦,最简单的方式是记录每个HTTP请求的处理时间,直接在HTTP Response Header中返回。

记录HTTP请求的时间很简单,就是收到请求记一个时间戳,响应请求的时候再记一个时间戳,两个时间戳之差就是处理时间。

但是,res.send()代码遍布各个js文件,总不能把每个URL处理函数都改一遍吧。

正确的思路是用middleware实现。但是Nodejs没有任何拦截res.send()的方法,怎么破?

其实只要稍微转换一下思路,放弃传统的OOP方式,以函数对象看待res.send(),我们就可以先保存原始的处理函数res.send,再用自己的处理函数替换res.send:

app.use(function (req, res, next) {
    // 记录start time:
    var exec_start_at = Date.now();
    // 保存原始处理函数:
    var _send = res.send;
    // 绑定我们自己的处理函数:
    res.send = function () {
        // 发送Header:
        res.set('X-Execution-Time', String(Date.now() - exec_start_at));
        // 调用原始处理函数:
        return _send.apply(res, arguments);
    };
    next();
});

只用了几行代码,就把时间戳搞定了。

对于res.render()方法不需要处理,因为res.render()内部调用了res.send()。

调用apply()函数时,传入res对象很重要,否则原始的处理函数的this指向undefined直接导致出错。

实测首页响应时间9毫秒:

原作者廖雪峰
原文链接 http://www.liaoxuefeng.com/article/0014007460517001bbb3e2f624a4917b742635e9a6b15dd000

点评

  • 以中间价来处理是复用最大的方法,全局中间价是每一个http请求都要经过的
  • 通过apply给res对象增加header,只要出发res.send就会带上header是很巧妙的做法
  • res.send是所有res的最底层方法,其他方法也会调用,比如res.json也会调用send

send源码

这是express里res.send方法的源码

/**
 * Send a response.
 *
 * Examples:
 *
 *     res.send(new Buffer('wahoo'));
 *     res.send({ some: 'json' });
 *     res.send('<p>some html</p>');
 *
 * @param {string|number|boolean|object|Buffer} body
 * @api public
 */

res.send = function send(body) {
  var chunk = body;
  var encoding;
  var len;
  var req = this.req;
  var type;

  // settings
  var app = this.app;

  // allow status / body
  if (arguments.length === 2) {
    // res.send(body, status) backwards compat
    if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
      deprecate('res.send(body, status): Use res.status(status).send(body) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.send(status, body): Use res.status(status).send(body) instead');
      this.statusCode = arguments[0];
      chunk = arguments[1];
    }
  }

  // disambiguate res.send(status) and res.send(status, num)
  if (typeof chunk === 'number' && arguments.length === 1) {
    // res.send(status) will set status message as text string
    if (!this.get('Content-Type')) {
      this.type('txt');
    }

    deprecate('res.send(status): Use res.sendStatus(status) instead');
    this.statusCode = chunk;
    chunk = http.STATUS_CODES[chunk];
  }

  switch (typeof chunk) {
    // string defaulting to html
    case 'string':
      if (!this.get('Content-Type')) {
        this.type('html');
      }
      break;
    case 'boolean':
    case 'number':
    case 'object':
      if (chunk === null) {
        chunk = '';
      } else if (Buffer.isBuffer(chunk)) {
        if (!this.get('Content-Type')) {
          this.type('bin');
        }
      } else {
        return this.json(chunk);
      }
      break;
  }

  // write strings in utf-8
  if (typeof chunk === 'string') {
    encoding = 'utf8';
    type = this.get('Content-Type');

    // reflect this in content-type
    if (typeof type === 'string') {
      this.set('Content-Type', setCharset(type, 'utf-8'));
    }
  }

  // populate Content-Length
  if (chunk !== undefined) {
    if (!Buffer.isBuffer(chunk)) {
      // convert chunk to Buffer; saves later double conversions
      chunk = new Buffer(chunk, encoding);
      encoding = undefined;
    }

    len = chunk.length;
    this.set('Content-Length', len);
  }

  // populate ETag
  var etag;
  var generateETag = len !== undefined && app.get('etag fn');
  if (typeof generateETag === 'function' && !this.get('ETag')) {
    if ((etag = generateETag(chunk, encoding))) {
      this.set('ETag', etag);
    }
  }

  // freshness
  if (req.fresh) this.statusCode = 304;

  // strip irrelevant headers
  if (204 == this.statusCode || 304 == this.statusCode) {
    this.removeHeader('Content-Type');
    this.removeHeader('Content-Length');
    this.removeHeader('Transfer-Encoding');
    chunk = '';
  }

  if (req.method === 'HEAD') {
    // skip body for HEAD
    this.end();
  } else {
    // respond
    this.end(chunk, encoding);
  }

  return this;
};

没啥难点

res.json = function json(obj) {
  var val = obj;

  // allow status / body
  if (arguments.length === 2) {
    // res.json(body, status) backwards compat
    if (typeof arguments[1] === 'number') {
      deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[1];
    } else {
      deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
      this.statusCode = arguments[0];
      val = arguments[1];
    }
  }

  // settings
  var app = this.app;
  var replacer = app.get('json replacer');
  var spaces = app.get('json spaces');
  var body = JSON.stringify(val, replacer, spaces);

  // content-type
  if (!this.get('Content-Type')) {
    this.set('Content-Type', 'application/json');
  }

  return this.send(body);
};

这里的的最后一行return this.send(body),它实际上就上面的res.send方法

其他方法也一样

  • jsonp
  • render

自己要用,于是copy了一份

request-time

Install

npm install --save request-time

Usages

var express       = require('express');
var request-time  = require('request-time');

var app = new express();
app.use(request-time);

欢迎关注我的公众号【node全栈】