在 Express 的前面添加一个反向代理(比如 Varnish 或者 Nginx)是非常常见的,它不需要额外的配置。 在通过 app.enable(‘trust proxy’) 激活了 “trust proxy” 设置后, Express 就会知道自己在一个代理的后面,就会信任 X-Forwarded-* HTTP 头信息了。( 通常情况下这些 HTTP 头信息是很容易被伪装的。)

使用了这个设置后会有一些很棒的小变化。 首先由反向代理设置的 X-Forwarded-Proto 会告诉程序它是 https 还是 http 。 这个值会影响 req.protocol 属性。

第二个变化是 req.ip 和 req.ips 的值会被 X-Forwarded-For 列表里的地址取代。

##在线用户计数
这一小节我们讲解一个小而全的应用程序,它通过 Redis 记录在线用户数。 首先你需要创建一个package.json 文件,包含两个依赖, 一个是redis 客户端,另一个是Express。 (另外需要确认你安装了redis, 可以能过执行 $ redis-server 来确认):

{
  "name": "app",
  "version": "0.0.1",
  "dependencies": {
    "express": "3.x",
    "redis": "*"
  }
}

##接下来你需要你创建一个应用程序,和一个 redis 连接:

var express = require('express');
var redis = require('redis');
var db = redis.createClient();
var app = express();

接下来是纪录用户在线的中间件。 这里我们使用sorted sets, 它的一个好处是我们可以查询最近N毫秒内在线的用户。 我们通过传入一个时间戳来当作成员的”score”。 注意我们使用 User-Agent 作为一个标识用户的id。

app.use(function(req, res, next){
  var ua = req.headers['user-agent'];
  db.zadd('online', Date.now(), ua, next);
});

下一个中间件是通过 zrevrangebyscore 来查询上一分钟在线用户。 我们将能得到从当前时间算起在 60,000 毫秒内活跃的用户。

app.use(function(req, res, next){
  var min = 60 * 1000;
  var ago = Date.now() - min;
  db.zrevrangebyscore('online', '+inf', ago, function(err, users){
    if (err) return next(err);
    req.online = users;
    next();
  });
});

最后我们来使用它,绑定到一个端口!这些就是这个程序的一切了,在不同的浏览器里访问这个应用程序,你会看到计数的增长。

app.get('/', function(req, res){
  res.send(req.online.length + ' users online');
});

app.listen(3000);

##HinocLab的app.js

/**
 - Module dependencies.
 */

// 创建express对象和一个redis客户端连接
var express = require('express');
var redis = require('redis');
var db = redis.createClient();
var app = module.exports = express();

var routes = require('./routes')
var http = require('http')
var path = require('path')
var markdown=require('markdown-js')
var fs = require('fs');

//写入日志
var accessLogfile = fs.createWriteStream('access.log',{flags:'a'});
var errorLogfile = fs.createWriteStream('error.log',{flags:'a'});

var MongoStore = require('connect-mongo')(express);

var settings = require('./settings');
// Configuration

app.configure(function(){
  app.set('port', process.env.PORT || 3000);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon(__dirname+'/public/logo.ico'));
//  app.use(express.logger('dev'));
  app.use(express.logger({stream:accessLogfile}));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser());
  app.use(express.session({
    secret: settings.cookieSecret,
    store: new MongoStore({
      db: settings.db
    })
  }));

  // 纪录用户在线的中间件 
  // 这里使用user-agent作为用户标识符
  // 这里使用sorted sets,以便查询最近N毫秒内在线的用户;
  app.use(function(req, res, next) {
      var ua = req.headers['user-agent'];
      db.zadd('online', Date.now(), ua, next);
  });

  // 通过 zrevrangebyscore 来查询上一分钟在线用户。
  // 我们将能得到从当前时间算起在 60,000 毫秒内活跃的用户。
  app.use(function(req, res, next) {
      var min = 60 * 1000;
      var ago = Date.now() - min;
      db.zrevrangebyscore('online', '+inf', ago, function (err, users) {
          if (err) {
              return next (err);
          }

          req.online = users;
          next ();
      });
  });



  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(function(err,req,res,next){
    var meta = '[' + new Date() +']' +req.url + '\n';
    errorLogfile.write(meta + err.stack + '\n');
    next();
  });
//  app.use(express.errorHandler());
});

app.engine('md', function(path, options, fn){  
  fs.readFile(path, 'utf8', function(err, str){  
    if (err) return fn(err);  
    str = markdown.parse(str).toString();  
    fn(null, str);  
  });
});


// 从不同浏览器进入就可以看到同时在线用户数不断增加
app.get('/online', function(req, res){
    res.send(req.online.length + ' users online');
});

// Routes

// app.get('/', routes.index);
app.get('/home', routes.home);
app.get('/about', routes.about);
app.get('/report', routes.report);
app.get('/submit', routes.getsubmit);
app.post('/submit', routes.postsubmit);
app.get('/login', routes.getlogin);
app.post('/login', routes.postlogin);

app.get('/logout', routes.logout);

app.post('/modify', routes.modify);
app.get('/login', routes.getlogin);

app.get('/doc/:author/:title', routes.getdoc);

app.get('/doc', routes.getdocindex);

app.get('/xxx', function(req, res) {  
    console.log('404 handler..')  
    res.render('404', {  
        status: 404,  
        title: 'NodeBlog',
        user: req.session.user  
    });  
});

if(!module.parent){
  http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

nginx简介

Nginx 是一个很牛的高性能Web和反向代理服务器, 它具有有很多非常优越的特性:

在高连接并发的情况下,Nginx是Apache服务器不错的替代品: Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一. 能够支持高达 50,000 个并发连接数的响应, 感谢Nginx为我们选择了 epoll and kqueue 作为开发模型。

Nginx作为负载均衡服务器: Nginx 既可以在内部直接支持 Rails 和 PHP 程序对外进行服务, 也可以支持作为 HTTP代理 服务器对外进行服务. Nginx采用C进行编写, 不论是系统资源开销还是CPU使用效率都比 Perlbal 要好很多。

作为邮件代理服务器: Nginx 同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮件代理服务器), Last.fm 描述了成功并且美妙的使用经验。

Nginx 是一个安装非常的简单 , 配置文件 非常简洁(还能够支持perl语法), Bugs 非常少的服务器: Nginx 启动特别容易, 并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动. 你还能够不间断服务的情况下进行软件版本的升级。

安装nginx

brew search nginx
brew install nginx

启动nginx ,sudo nginx ;访问localhost:8080 发现已出现nginx的欢迎页面了。

常用的指令有:

nginx -V 查看版本,以及配置文件地址
nginx -v 查看版本
nginx -c filename 指定配置文件
nginx -h 帮助
#重新加载配置|重启|停止|退出 nginx
nginx -s reload|reopen|stop|quit
#打开 nginx
sudo nginx
#测试配置是否有语法错误
nginx -t

nginx配置目录

vim /usr/local/etc/nginx/nginx.conf

在开发的过程中,通过node app.js 􏰤􏱎运行服务器􏰓可。但它不适合在产􏱏环境下使用,因为到目前为􏱒这个服务 器还有几个重大􏱓􏱔问题

##存在问题

###不支持故障恢复
不知你是􏱜在调试的过程中注意,􏱝程序有错误发生时,􏱞整个进程就会结束,需要重新在终端中􏱠动服务器。这一点在开发中无可厚非,但在产􏱏环境下就是􏱡重的问题 了,因为一􏱢用􏰑访问时触发了程序中􏱣个􏱤􏱥的bug􏱦􏱞个服务器就􏱧􏱨了,所以我们在部署nodejs的时候一定要注意故障恢复

###没有日志
没有错误日志和访问日志

###无法利用多核
Nodejs是单线程的,一个进程只能使用一个CPU核心

###独占端口
假如􏱞个服务器只有一个网站,或者可以给每个网站分配一个􏰇􏰈的IP地址,不会有 端口􏰦􏰧冲突的问题。而很多时􏰯为了􏲉充分利用服务器的资源,我们会在同一个服务器上建􏰈多个网站,而且这些网站可能有的是PHP,有的是Rails,有的是Node.js。不能每个进程都􏰇􏲊80端口,所以我们有􏱶要通过􏲋􏲌代理来实现基于域􏰥名的端口共享。

###需要手动启动
先前每次􏱠动服务器都是通过在􏰤􏱎行中􏰡接􏲑入􏰤􏱎来实现的,但在产􏱏环境中, 特别是在服务器重􏱠启以后,全部􏱭􏲒手动是不现实的。因此,我们应该制作一个自动启􏱠动服务器的脚􏲓本,并且通过该􏲓脚本可以实现􏲔􏱒服务器等功能。

##解决问题

###日志功能
Express有两种模式:调试模式、产品模式,产品模式易于部署,设置模式方法:在MAC命令行

export NODE_ENV=production

我们实现访问􏱲􏱳日志和错误􏱲􏱳功能。访问日志􏱲􏱳就是􏲚录用􏰑对服务器的每个请求,包括􏲛􏰑端IP地址,访问时间,访问路径,服务器􏲜应以及􏲛􏰑端代理字符􏲝。而错误日志则􏲚录程序发生错误时的􏲞􏲟,由于调试中需要􏰓时􏰼看错误􏲞􏲟,将所有错误􏰡接显 示到终端􏰓可,而在产􏱏模式中,需要写入错误􏱲􏱳文件。
Express 提供了一个访问􏱲􏱳中间件,只需指定stream 参数为一个输出流􏰓可将访问日志写入文件。打开app.js,在最上方加入以下代码:

var fs = require('fs');
var accessLogfile = fs.createWriteStream('access.log',{flags:'a'});
var errorLogfile = fs.createWriteStream('error.log',{flags:'a'});

􏲠􏲡􏲢然后在app.configure第一行加入

app.use(express.logger({stream:accessLogfile}));

至于错误􏱲􏱳,需要􏰒􏰇实现错误􏲜,修􏲪改如下:

app.configure('production', function(){
  app.use(function(err,req,res,next){
    var meta = '[' + new Date() +']' +req.url + '\n';
    errorLogfile.write(meta + err.stack + '\n');
    next();
  });
//  app.use(express.errorHandler());
});

为了产生一个错误,我们􏲪改routes/index.js 中􏰽 / 􏰾的􏲜应函数,加入以下代码:

throw new Error('An error for test purposes.');

完整的app.js代码如下

/**
 - Module dependencies.
 */
var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , path = require('path')
  , markdown=require('markdown-js')
  , fs = require('fs');

//写入日志
var accessLogfile = fs.createWriteStream('access.log',{flags:'a'});
var errorLogfile = fs.createWriteStream('error.log',{flags:'a'});

var app = express();
var MongoStore = require('connect-mongo')(express);

var settings = require('./settings');
// Configuration

app.configure(function(){
  app.set('port', process.env.PORT || 3300);
  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');
  app.use(express.favicon(__dirname+'/public/logo.ico'));
//  app.use(express.logger('dev'));
  app.use(express.logger({stream:accessLogfile}));
  app.use(express.bodyParser());
  app.use(express.methodOverride());
  app.use(express.cookieParser());
  app.use(express.session({
    secret: settings.cookieSecret,
    store: new MongoStore({
      db: settings.db
    })
  }));
  app.use(app.router);
  app.use(express.static(path.join(__dirname, 'public')));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.configure('production', function(){
  app.use(function(err,req,res,next){
    var meta = '[' + new Date() +']' +req.url + '\n';
    errorLogfile.write(meta + err.stack + '\n');
    next();
  });
//  app.use(express.errorHandler());
});

app.engine('md', function(path, options, fn){  
  fs.readFile(path, 'utf8', function(err, str){  
    if (err) return fn(err);  
    str = markdown.parse(str).toString();  
    fn(null, str);  
  });
});

// Routes

app.get('/', routes.index);
app.get('/home', routes.home);
app.get('/about', routes.about);
app.get('/report', routes.report);
app.get('/submit', routes.getsubmit);
app.post('/submit', routes.postsubmit);
app.get('/login', routes.getlogin);
app.post('/login', routes.postlogin);

app.get('/logout', routes.logout);

app.post('/modify', routes.modify);
app.get('/login', routes.getlogin);

app.get('/doc/:author/:title', routes.getdoc);

app.get('/doc', routes.getdocindex);

app.get('/xxx', function(req, res) {  
    console.log('404 handler..')  
    res.render('404', {  
        status: 404,  
        title: 'NodeBlog',
        user: req.session.user  
    });  
});

http.createServer(app).listen(app.get('port'), function(){
  console.log("Express server listening on port " + app.get('port'));
});

##多核多线程使用(cluster使用)
从0.6 版本开始,Node.js 提供了一个核心模块:cluster。cluster的功能是生成与􏱝当前进程􏰫相同的􏴸进程,并且􏺲允许􏴬进程和􏴸进程之间共享端口。Node.js另的􏰝一个核心模块 child_process 也提供了􏰫􏲬的进程生成功能,但最大的区别在于cluster允许跨进程端口复用,给我们的网络服务器开发带来了很大的方便􏺳

为了在外部模块调用app.js􏱦首先需要􏺴􏱒禁止服务器自启􏱠动。􏲪修改app.js,在app.listen (3000); 前后加上􏺵􏺭判断语􏷵:

if(!module.parent){
  http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

这个语􏷵的功能是􏺵􏺭􏱝前模块是不是由其他模块调用的,如果不是,说􏴹它是􏰡接􏱠直接启动的,此时􏱠启动调试服务器。如果是,则不自动􏱠动服务器。以后􏰡直接调用node app.js服务器会􏰡接运行,但在其他模块中调用require(‘./app’) 则不会自动启动,需要􏲳显式地调用listen() 函数。

接下来就让我们通过cluster 调用app.js􏺳。创建cluster.js􏱦内容如下所示:

var cluster = require('cluster');
var os = require('os');

// 获取cpu数量
var numCPUs = os.cpus().length;

var workers = {};

if(cluster.isMaster){
    // 主线程分支
    cluster.on('death',function (worker) {
        // 当一个工作进程结束时,重启工作进程
        delete workers[worker.pid];
        worker = cluster.fork();
        workers[worker.pid] = worker;
    });

    // 初始开启与cpu数量相同的工作进程
    for (var i = 0; i < numCPUs; i++) {
        var worker = cluster.fork();
        workers[worker.pid] = worker;
    }
} else {
    // 工作进程分支,启动服务器
    var app = require('./app');
    var http = require('http');
    http.createServer(app).listen(app.get('port'), function(){
        console.log("Express server listening on port " + app.get('port'));
     });
}

// 当主进程被终止,关闭所有工作进程
process.on('SIGTERM',function () {
    for(var pid in workers){
        process.kill(pid);
    }
    process.exit(0);
});

对应修改app.js

var app = module.exports = express();

if(!module.parent){
    http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

cluster.js 的功能是创建与CPU 核心个数􏰫同的服务器进程,以􏵎􏳢􏲉分利用多核CPU 的 资源。主进程生成􏹁􏹂个工作进程,并􏶍听工作进程结􏱟事件,􏱝工作进程结􏱟时,重新􏱠启动一个工作进程。分支进程产生时会自顶􏲌下重新􏵱行􏱝前程序,并通过分支􏺵􏺭进入工作进程分支,在其中读取模块并􏱠动服务器。通过cluster􏱠启动的工作进程可以􏰡接实现端口复用,因此所有工作进程只需􏶍听同一端口。􏱝主进程终􏱒时,还要主动关闭所有工作进程。

##启动脚本
接下来,我们还需要一个􏱠脚􏲓本来简化维􏷁工作。如果你维􏷁过Linux 服务器,会对 /etc/init.d/ 下面的􏲓脚本有印􏵑象。例如使用/etc/init.d/nginx start 和/etc/init.d/ nginx stop 可以􏱠动和关闭Nginx 服务器。我们通过bash也来实现一个􏰘􏲬的功能, 创建microblog 并使用chmod +x microblog 􏵝􏼢其􏵱执行权限脚本内容为:

#! /bin/sh

NODE_ENV=production
DAEMON="node cluster.js"
NAME=HINOCLab
DESC=HINOCLab
PIDFILE="HINOCLab.pid"

case "$1" in
    start)
        echo "starting $DESC: "
            nohup $DAEMON > /dev/null &
        echo $! > $PIDFILE
        echo "$NAME."
            ;;
    stop)
        echo "stoping $DESC: "
            pid='cat $PIDFILE'
        kill $pid
            rm $PIDFILE
        echo "$NAME."
            ;;
esac
exit 0

注意不能有多余的空格

##共享80端口
􏽂􏽃虚拟主机就是让多个网站共享使用同一服务器,同一IP地址通过域􏰥的不同来􏳾分请求。主流的HTTP服务器都提供了􏽂􏽃主机支持,如Nginx、Apache、IIS等。以Nginx为例,介绍如何通过􏲋􏲌代理实现Node.js 􏽂􏽃主机。

在Nginx中设置􏲋􏲌代理和􏽂􏽃主机非常简􏰒单,下面是配置文件的一个示例:

server {
    listen 80;
    server_name mysite.com;
    location / {
    proxy_pass http://localhost:3000;
    }
}

这个配置文件的功能是􏶍听访问mysite.com 80 端口的请求,并将所有的请求􏴔发给 http://localhost:3000, 到的Node.js 服务器。现在访问http://mysite.com/,就􏰫􏱝于服务器访问 http://localhost:3000 了。
在􏳸加了􏽂􏽃主机以后,还可以在Nginx配置文件中􏳸加访问􏷦􏴠文件的规则(具体请
参考Nginx文档),􏷫去app.js中的app.use(express.static(__dirname + ‘/public’));。 这􏵌可以􏰡接让Nginx 来处理􏷦􏴠文件,􏰕少􏲋􏲌代理以及Node.js 的开销。

exception in initAndListen: 0 assertion src/mongo/db/storage/extent.h:80, terminating

修复方法:

这算是一个Mongod 启动的一个常见错误,非法关闭的时候,lock 文件没有干掉,第二次启动的时候检查到有lock 文件的时候,就报这个错误了。

解决方法:进入 mongod 上一次启动的时候指定的 data 目录 –dbpath=/data/mongodb

删除掉该文件:

rm /data/mongodb/mongo.lock --linux

del /data/mongodb/mongo.lock --windows

再执行:

./mongod  --repair

启动:

/usr/local/src/mongodb-linux-x86_64-2.0.2/bin/mongod --port=27017 --pidfilepath=/var/run/mongod.pid --dbpath=/data/mongodb --directoryperdb --nojournal --noauth

OK,问题解决。

正确关闭mongod 的方法:进入mongo shell

use admin

db.shutdownServer()

也可以按照文档粗暴的杀掉它,它内部应该有KILL信号处理程序。

killall mongod

请不要 kill -9 ,会造成文件数据混乱丢失 repair 也无力回天。

ctrl+c 可以退出mongo的界面 或是ext

TIPS:首先声明使用sae实在是因为我的豆豆用不完

##sae中上传文件或者图片的方法
在sae中上传图片的方法基本没有变化
主要需要注意上传路径的设置,如下

$upload->savePath =  '/Public/upload';   // 设置附件上传目录

由于在config_sae.php中已经对/Public/upload有设置了

'/Public/upload'=>sae_storage_root('public').'/upload',

跟据上面的设置,默认上传到storage服务中的Public文件夹中

public function addHandle(){
    if(!IS_POST){
        halt('页面不存在');
    }

    import('ORG.Net.UploadFile');
    $upload = new UploadFile();                 // 实例化上传类

    $upload->savePath =  '/Public/upload';   // 设置附件上传目录
    $upload->allowExts  = array(
        'jpg','png','gif','bmp'
        );// 设置附件上传类型
    // 开启缩略图
    $upload->thumb = true;
    //设置需要生成缩略图的文件后缀
    $upload->thumbPrefix = 'l_';  //生产1张缩略图
    //设置缩略图最大宽度
    $upload->thumbMaxWidth = '100';
    //设置缩略图最大高度
    $upload->thumbMaxHeight = '100';

    if (!$upload->upload()) {
        $this->error('上传失败');
    }

    //取得成功上传的文件信息
    $info = $upload->getUploadFileInfo();

    $_POST['off'] = 0;
    $_POST['savename'] = $info[0]['savename'];

    if(M('fruit')->add($_POST) ){
        $this->success('添加水果成功',U('Fruit/index'));
    } else{
        $this->error('添加失败,请联系开发人员');
    }
}

完成上传功能后,上传的图片名为551cb261dcdbc.png
但是存储在storage Public文件夹下面的文件名为upload551cb261dcdbc.png
其中缩略图文件名l_upload551cb261dcdbc.png
所以在处理的过程中还有一些技巧

##显示图片
在config_sae.php中添加

'/stor'=>sae_storage_root('public'),

然后在显示页面添加

<td>
    <img src="/stor/l_upload{$v.savename}" />
</td>

##删除图片

public function deleteFruit(){
    $fruit = M('fruit')->where(array('id' => I('id') ))->select();
    $name1 = '/Public/upload'.$fruit[0]['savename'];////////////
    $name2 = '/Public/l_upload'.$fruit[0]['savename'];//////////

    if (M('fruit')->where(array('id' => I('id')))->delete()) {
        sae_unlink($name1);////////
        sae_unlink($name2);////////
        $this->success('删除成功');
    }else{
        $this->error('删除失败');
    }
}

其中在Thinkphp/Extend/Engine/Sae/Common/sae_functions.php
//平滑函数,sae和本地都可以用,增加系统平滑性

function sae_unlink($filePath) {
    if (IS_SAE) {
        $arr = explode('/', ltrim($filePath, './'));
        $domain = array_shift($arr);

        // echo $domain;

        $filePath = implode('/', $arr);

        // echo $filePath;

        $s = Think::instance('SaeStorage');
        return $s->delete($domain, $filePath);
    } else {
        return unlink($filePath);
    }
}

##svn版本过期
在使用svn过程中,首先出现的问题是svn无法使用如下命令

svn co https://svn.sinaapp.com/myhello

检测结果svn版本为1.6.5,版本过低,只要是因为mac升级到10.10

解决方案:(最简单,最暴力)
卸载Xcode,重新安装最新版本,因为Xcode会自动安装svn

##svn的使用

###首先将应用同步到本地

svn co https://svn.sinaapp.com/myhello

上传代码

svn ci -m "submit code"  # 注意,这里填写的submit code为更新的理由,必填项

##Introduction
This tutorial will have you deploying a Node.js app in minutes.

Hang on for a few more minutes to learn how it all works, so you can make the most out of Heroku.

The tutorial assumes that you have a free Heroku account, and that you have Node.js and npm installed.

##Set up
In this step you will install the Heroku Toolbelt. This provides you access to the Heroku Command Line utility, as well as git and Foreman, tools you’ll use in later steps.

Download Heroku Toolbelt for Mac OS X

Once installed, you can use the heroku command from your command shell.

Log in using the email address and password you used when creating your Heroku account:

$ heroku login
Enter your Heroku credentials.
Email: zeke@example.com
Password:
Authentication successful.

Authenticating is required to allow both the heroku and git commands to operate.

Note that if you’re behind a firewall that requires use of a proxy to connect with external HTTP/HTTPS services, you can set the HTTP_PROXY or HTTPS_PROXY environment variables before running the heroku command.

##Prepare the app
In this step, you will prepare a simple application that can be deployed.

Execute the following commands to clone the sample application:

$ git clone https://github.com/heroku/node-js-getting-started.git
$ cd node-js-getting-started

You now have a functioning git repository that contains a simple application as well as a package.json file, which is used by Node’s dependency manager.

##Deploy the app
In this step you will deploy the app to Heroku.

Create an app on Heroku, which prepares Heroku to receive your source code.

$ heroku create
Creating sharp-rain-871... done, stack is cedar-14
http://sharp-rain-871.herokuapp.com/ | https://git.heroku.com/sharp-rain-871.git
Git remote heroku added

When you create an app, a git remote (called heroku) is also created and associated with your local git repository.

Heroku generates a random name (in this case sharp-rain-871) for your app - you can pass a parameter to specify your own, or rename it later with heroku apps:rename.

Now deploy your code:

$ git push heroku master
Counting objects: 343, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (224/224), done.
Writing objects: 100% (250/250), 238.01 KiB, done.
Total 250 (delta 63), reused 0 (delta 0)

-----> Node.js app detected
-----> Resolving engine versions
   Using Node.js version: 0.10.3
   Using npm version: 1.2.18
-----> Fetching Node.js binaries
-----> Vendoring node into slug
-----> Installing dependencies with npm
   ....
   Dependencies installed
-----> Building runtime environment
-----> Discovering process types
   Procfile declares types -> web

-----> Compiled slug size: 4.1MB
-----> Launching... done, v9
   http://sharp-rain-871.herokuapp.com deployed to Heroku

To git@heroku.com:sharp-rain-871.git
* [new branch]      master -> master

The application is now deployed. Ensure that at least one instance of the app is running:

$ heroku ps:scale web=1

Now visit the app at the URL generated by its app name. As a handy shortcut, you can open the website as follows:

$ heroku open

##View logs
Heroku treats logs as streams of time-ordered events aggregated from the output streams of all your app and Heroku components, providing a single channel for all of the events.

View information about your running app using one of the logging commands, heroku logs:

$ heroku logs --tail
2011-03-10T10:22:30-08:00 heroku[web.1]: State changed from created to starting
2011-03-10T10:22:32-08:00 heroku[web.1]: Running process with command: `node index.js`
2011-03-10T10:22:33-08:00 heroku[web.1]: Listening on 18320
2011-03-10T10:22:34-08:00 heroku[web.1]: State changed from starting to up

Visit your application in the browser again, and you’ll see another log message generated.

Press Control+C to stop streaming the logs

##Define a Procfile
Use a Procfile, a text file in the root directory of your application, to explicitly declare what command should be executed to start your app.

The Procfile in the example app you deployed looks like this:

web: node index.js

This declares a single process type, web, and the command needed to run it. The name web is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed.

Procfiles can contain additional process types. For example, you might declare one for a background worker process that processes items off of a queue.

##Scale the app
Right now, your app is running on a single web dyno. Think of a dyno as a lightweight container that runs the command specified in the Procfile.

You can check how many dynos are running using the ps command:

$ heroku ps
=== web (1X): `node index.js`
web.1: up 2014/04/25 16:26:38 (~ 1s ago)

Having only a single web dyno running will result in the dyno going to sleep after one hour of inactivity. This causes a delay of a few seconds for the first request upon waking. Subsequent requests will perform normally.

To avoid this, you can scale to more than one web dyno. For example:

$ heroku ps:scale web=2

For abuse prevention, scaling the application may require account verification. If your account has not been verified, you will be directed to visit the verification site.

For each application, Heroku provides 750 free dyno-hours. Running your app at 2 dynos would exceed this free, monthly allowance, so scale back:

$ heroku ps:scale web=1

##Declare app dependencies
Heroku recognizes an app as Node.js by the existence of a package.json file in the root directory. For your own apps, you can create one by running npm init.

The demo app you deployed already has a package.json, and it looks something like this:

{
  "name": "node-js-getting-started",
  "version": "0.1.2",
  ...
  "dependencies": {
    "express": "~4.9.x"
  },
  ...
  "engines": {
    "node": "0.10.x"
  },
}

The package.json file determines both the version of Node.js that will be used to run your application on Heroku, as well as the dependencies that should be installed with your application. When an app is deployed, Heroku reads this file and installs the appropriate node version together with the dependencies using the npm install command.

Run this command in your local directory to install the dependencies, preparing your system for running the app locally:

$ npm install
npm http GET https://registry.npmjs.org/express
npm http 304 https://registry.npmjs.org/express
npm http GET https://registry.npmjs.org/stream-spigot
npm http GET https://registry.npmjs.org/connect/2.12.0
...
express@4.9.4 node_modules/express
+---methods@0.1.0
+---merge-descriptors@0.0.1
+---range-parser@0.0.4
+---cookie-signature@1.0.1
+---debug@2.0.0
+---fresh@0.2.0
+---buffer-crc32@0.2.1
+---cookie@0.1.0
+---mkdirp@0.3.5
+---send@0.9.2 (destroy@1.0.3, ms@0.6.2, mime@1.2.11)
...

Once dependencies are installed, you will be ready to run your app locally.

##Run the app locally
Now start your application locally using Foreman, which was installed as part of the Toolbelt:

$ foreman start web
14:39:04 web.1     | started with pid 24384
14:39:04 web.1     | Listening on 5000

Just like Heroku, Foreman examines the Procfile to determine what to run.

Your app will now be running at localhost:5000. Test that it’s working with curl or a web browser, then Ctrl-C to exit.

Foreman doesn’t just run your app - it also sets “config vars”, something you’ll encounter in a later tutorial.

##Push local changes
In this step you’ll learn how to propagate a local change to the application through to Heroku. As an example, you’ll modify the application to add an additional dependency and the code to use it.

Modify package.json to include a dependency for cool-ascii-faces:

"dependencies": {
  "express": "~4.9.x",
  "cool-ascii-faces": "~1.3.x"
},

Modify index.js so that it requires this module at the start. Also modify the call to route that handles ‘/‘ to use it. Your final code should look like this:

var express = require('express')
var app = express();
var cool = require('cool-ascii-faces');

app.set('port', (process.env.PORT || 5000))

app.get('/', function(request, response) {
  response.send(cool());
});

app.listen(app.get('port'), function() {
  console.log("Node app is running at localhost:" + app.get('port'))
})

Now test locally:

$ npm install
$ foreman start

Visiting your application at http://localhost:5000/, you should see cute faces displayed on each refresh: ( ⚆ _ ⚆ ).

Now deploy. Almost every deploy to Heroku follows this same pattern. First, add the modified files to the local git repository:

$ git add .

Now commit the changes to the repository:

$ git commit -m "Demo"

Now deploy, just as you did previously:

$ git push heroku master

Finally, check that everything is working:

$ heroku open

##Provision add-ons
Add-ons are third-party cloud services that provide out-of-the-box additional services for your application, from persistence through logging to monitoring and more.

By default, Heroku stores 1500 lines of logs from your application. However, it makes the full log stream available as a service - and several add-on providers have written logging services that provide things such as log persistence, search, and email and SMS alerts when certain conditions are met.

Provision the papertrail logging add-on:

$ heroku addons:add papertrail
Adding papertrail on sharp-rain-871... done, v4 (free)
Welcome to Papertrail. Questions and ideas are welcome (support@papertrailapp.com). Happy logging!
Use `heroku addons:docs papertrail` to view documentation.

To help with abuse prevention, provisioning an add-on may require account verification. If your account has not been verified, you will be directed to visit the verification site.

The add-on is now deployed and configured for your application. You can list add-ons for your app like so:

$ heroku addons

To see this particular add-on in action, visit your application’s Heroku URL a few times. Each visit will generate more log messages, which should now get routed to the papertrail add-on. Visit the papertrail console to see the log messages:

$ heroku addons:open papertrail

A console will open up, showing the latest log events, and providing you with an interface to search and set up alerts:

##Start a console
You can run a command, typically scripts and applications that are part of your app, in a one-off dyno using the heroku run command. It can also be used to launch a REPL process attached to your local terminal for experimenting in your app’s environment:

$ heroku run node
Running `node` attached to terminal... up, ps.1
>

If you receive an error, Error connecting to process, then you may need to configure your firewall.

When the console starts, it has nothing loaded other than the Node.js standard library. From here you can require some of your application files.

For example, you will be be able to run the following:

> var cool = require('cool-ascii-faces')
> cool()
( ⚆ _ ⚆ )

To get a real feel for how dynos work, you can create another one-off dyno and run the bash command, which opens up a shell on that dyno. You can then execute commands there. Each dyno has its own ephemeral filespace, populated with your app and its dependencies - once the command completes (in this case, bash), the dyno is removed.

$ heroku run bash
Running `bash` attached to terminal... up, run.3052
~ $ ls
Procfile  README.md  composer.json  composer.lock  vendor  views  web
~ $ exit
exit

Don’t forget to type exit to exit the shell and terminate the dyno.

##Define config vars
Heroku lets you externalise configuration - storing data such as encryption keys or external resource addresses in config vars.

At runtime, config vars are exposed as environment variables to the application. For example, modify index.js so that the method repeats an action depending on the value of the TIMES environment variable:

app.get('/', function(request, response) {
  var result = ''
  var times = process.env.TIMES || 5
  for (i=0; i < times; i++)
    result += cool();
  response.send(result);
});

Foreman will automatically set up the environment based on the contents of the .env file in your local directory. Create a .env file that has the following contents:

TIMES=2

If you run the app with foreman start, you’ll see two faces will be generated every time.

To set the config var on Heroku, execute the following:

$ heroku config:set TIMES=2

View the config vars that are set using heroku config:

$ heroku config
== sharp-rain-871 Config Vars
PAPERTRAIL_API_TOKEN: erdKhPeeeehIcdfY7ne
TIMES: 2

Deploy your changed application to Heroku to see this in action.

##Provision a database
The add-on marketplace has a large number of data stores, from Redis and MongoDB providers, to Postgres and MySQL. In this step you will add a free Heroku Postgres Starter Tier dev database to your app.

Add the database:

$ heroku addons:add heroku-postgresql:hobby-dev
Adding heroku-postgresql:hobby-dev... done, v3 (free)

This creates a database, and sets a DATABASE_URL environment variable (you can check by running heroku config).

Edit your package.json file to add the pg npm module to your dependencies:

"dependencies": {
    "pg": "4.x",
    "express": "~4.9.x",
    "cool-ascii-faces": "~1.3.x"
}

Type npm install to install the new module for running your app locally. Now edit your index.js file to use this module to connect to the database specified in your DATABASE_URL environment variable:

var pg = require('pg');

app.get('/db', function (request, response) {
  pg.connect(process.env.DATABASE_URL, function(err, client, done) {
    client.query('SELECT * FROM test_table', function(err, result) {
      done();
      if (err)
       { console.error(err); response.send("Error " + err); }
      else
       { response.send(result.rows); }
    });
  });
})

This ensures that when you access your app using the /db route, it will return all rows in the test_table table.

Deploy this to Heroku. If you access /db you will receive an error as there is no table in the database. Assuming that you have Postgres installed locally, use the heroku pg:psql command to connect to the remote database, create a table and insert a row:

$ heroku pg:psql
psql (9.3.2, server 9.3.3)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.
=> create table test_table (id integer, name text);
CREATE TABLE
=> insert into test_table values (1, 'hello database');
INSERT 0 1
=> \q

Now when you access your app’s /db route, you will see something like this:

[
  {
    "id": 1,
    "name": "hello database"
  }
]

Read more about Heroku PostgreSQL.

A similar technique can be used to install MongoDB or Redis add-ons.

##Next steps
You now know how to deploy an app, change its configuration, view logs, scale, and attach add-ons.

Here’s some recommended reading. The first, an article, will give you a firmer understanding of the basics. The second is a pointer to the main Node.js category here on Dev Center:

  • Read How Heroku Works(https://devcenter.heroku.com/articles/how-heroku-works) for a technical overview of the concepts you’ll encounter while writing, configuring, deploying and running applications.
  • Read Deploying Node.js Apps on Heroku(https://devcenter.heroku.com/articles/deploying-nodejs) to understand how to take an existing Node.js app and deploy it to Heroku.
  • Visit the Node.js category(https://devcenter.heroku.com/categories/nodejs) to learn more about developing and deploying Node.js applications.

http模块主要用于创建http server服务,此次实验还会讲到url模块和path模块,同时也会用到前面讲过的fs模块。url模块用于解析url,path模块用于处理和转换文件路径。

通过前面的实验,相信大家对Node.js模块的使用已经比较熟悉。在这个实验中,我们就通过编写一个简单的http server来学习http模块。

##一、创建http server
通过Node.js创建http server非常简单,示例代码如下:

// 文件名:demo.js

// 引入http模块
var http = require('http');

// 创建http server
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');

console.log('Server running at http://127.0.0.1:1337/');

运行此文件:

$ node demo.js

然后打开虚拟机浏览器,访问“http://127.0.0.1:1337/”,就会看到页面上显示了“Hello World”,说明我们的http server创建成功了。

当然,我们在这个实验要做的比这个稍微复杂一点。

在这个实验中,我们会创建一个简单的http server,所有的代码都放在app这个文件夹中。首先,新建一个文app件夹,在文件夹中新建server.js文件,输入如下代码(其中的注释为代码解释):

//
// 创建http server
//

// 加载所需模块
var http = require('http');
var url = require('url');
var fs = require('fs');

// 设置ip和端口
// 实际应用中,可以把这些写到配置文件中
var host = '127.0.0.1',
    port = 8080;

// 创建http server
function start(route, handle) {
    // 参数
    // route  判断url是否存在,存在则调用handle处理,不存在则返回404
    // handle 处理不同的url请求


    // 处理request请求
    function onRequest(req, res) {
        // 使用url.parse()方法解析url
        // 它会把url string转化为一个object
        // 这样我们就可以很方便的获取url中的host、port、pathname等值了
        var pathname = url.parse(request.url).pathname;
        console.log('Request for ' + pathname + ' received.');

        // 判断并处理不同url请求
        // 后面介绍此方法
        route(handle, pathname, res, req);
    }

    // 使用http.createSserver()方法创建http server
    // 并传入onRequest()方法
    // 然后使用listen()方法监听指定地址
    http.createServer(onRequest).listen(port, host);
    console.log('Server has started and listening on ' + host + ':' + port);
}

// 导出 start 方法
exports.start = start;

在文件的最后,我们导出了start方法,以便在主程序中使用。你肯定注意到了,在代码中使用了route()方法,它用于处理判断请求的url是否存在,现在我们就来编写这个方法。

##二、创建路由
在app文件夹中新建router.js,输入如下代码:

// 路由函数
// 处理不同url的请求
// 并返回相应内容
function route(handle, pathname, res, req) {
    console.log('About to route a request for ' + pathname);

    // 判断此url是否存在特定处理函数
    // 存在则调用handle处理
    // 不存在则返回404页面
    if (typeof handle[pathname] === 'function') {
        // 后面介绍handle函数
        handle[pathname](res, req);
    } else {
        console.log('No request handler found for ' + pathname);

        // 读取404页面
        // 所有页面都存放在view文件夹下
        var content = fs.readFileSync('./views/404.html');
        res.writeHead(404, { 'Content-Type': 'text/html' });
        res.write(content);
        res.end();
    }
}
// 导出 route 方法
exports.route = route;

在此方法中,调用了handle()方法,这个方法用于处理不同的url请求。

在app文件夹中新建requestHandlers.js文件,输入如下代码:

// 处理url请求

var fs = require('fs');

// home.html 主页
function home(res) {
    console.log('Request handler "home" was called.');

    // 读取home.html文件
    var content = fs.readFileSync('./views/home.html');
    res.write(200, { 'Content-Type': 'text/html' });
    res.write(content);
    res.end();
}

// about.html 关于页面
function about(res) {
    console.log('Request handler "about" was called.');

    // 读取about.html文件
    var content = fs.readFileSync('./views/about.html');
    res.write(200, { 'Content-Type': 'text/html' });
    res.write(content);
    res.end();
}

// 导出页面处理函数
exports.home = home;
exports.about = about;

这个方法比较简单,就是读取文件,然后输出到response。

##三、创建主程序
创建http server,判断url,处理url都写完了,那么我们可以写主程序来运行http server了,在app文件夹新建main.js文件,输入如下代码:

// 主程序

// 引入server,router及requestHandler
var server = require('./server');
var router = require('./router');
var requestHandlers = require('./requestHandlers');

// 保存url处理方法
var handle = {};
handle['/'] = requestHandlers.home;
handle['/about'] = requestHandlers.about;

// 启动http server
server.start(router.router, handle);

到此,所有的服务器代码都写完了,那么我们来添加代码中用到的两个html文件吧。

##四、创建HTML文件
在app文件夹中新建views文件夹,在views文件夹中,新建home.html文件、about.html文件和404.html文件。

文件中的代码如下所示:

home.html文件:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Home page</title>
    </head>
    <body>
        <p>home page</p>
    </body>
</html>

about.html文件:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>About page</title>
    </head>
    <body>
        <p>about page</p>
    </body>
</html>

404.html文件:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>404 page</title>
    </head>
    <body>
        <p>404 page not found</p>
    </body>
</html>

HTML文件的代码写得比较简单,可自由发挥。

那么现在我们来运行程序吧:

$ node main.js

运行成功后,打开虚拟机桌面的浏览器,访问“http://127.0.0.1:8080”就会看到页面显示“home page”,访问“http://127.0.0.1:8080/about”就会看到页面显示“about page”,访问“http://127.0.0.1:8080”下的其他页面就会看到页面显示“404 page not found”。

参考链接:http://nodejs.org/api/fs.html

#一、同步和异步
使用require(‘fs’)载入fs模块,模块中所有方法都有同步和异步两种形式。

异步方法中回调函数的第一个参数总是留给异常参数(exception),如果方法成功完成,那么这个参数为null或者undefined。

##异步方法实例代码(无需在虚拟机中编写):

var fs = require('fs'); // 载入fs模块

fs.unlink('/tmp/shiyanlou', function(err) {
    if (err) {
        throw err;
    }
    console.log('成功删除了 /tmp/shiyanlou');
});

##同步方法实例代码(无需在虚拟机中编写):

var fs = require('fs');

fs.unlinkSync('/tmp/shiyanlou'); // Sync 表示是同步方法
console.log('成功删除了 /tmp/shiyanlou');

同步方法执行完并返回结果后,才能执行后续的代码。而异步方法采用回调函数接收返回结果,可以立即执行后续的代码。

#二、readFile读取文件
使用fs.readFile(filename, [options], callback)方法读取文件。

readFile接收三个参数,filename是文件名;[options]是可选的参数,为一个对象,用于指定文件编码(encoding)及操作方式(flag);callback是回调函数。

在虚拟机家目录(/home/shiyanlou)下新建一个文件text.txt,文件中的内容如下:

line one
line two

使用readFile读取此文件,虚拟机家目录下新建文件readfile.js,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

fs.readFile('./test.txt', function(err, data) {
    // 读取文件失败/错误
    if (err) {
        throw err;
    }
    // 读取文件成功
    console.log(data);
});

readFile的回调函数接收两个参数,err是读取文件出错时触发的错误对象,data是从文件读取的数据。

运行程序:

shiyanlou:~/ $ node readfile.js

会看到输出的内容类似于这样:

<Buffer 6c 69 6e 65 20 6f 6e 65 0a 6c 69 6e 65 20 74 77 6f 0a>

这是原始二进制数据在缓冲区中的内容。要显示文件内容可以使用toString()或者设置输出编码,readFile.js可以改成这样:

var fs = require('fs'); // 引入fs模块

// 使用toString()
fs.readFile('./test.txt', function(err, data) {
    // 读取文件失败/错误
    if (err) {
        throw err;
    }
    // 读取文件成功
    console.log('toString: ', data.toString());
});

// 设置编码格式
fs.readFile('./test.txt', 'utf-8', function(err, data) {
    // 读取文件失败/错误
    if (err) {
        throw err;
    }
    // 读取文件成功
    console.log('utf-8: ', data.toString());
});

这样再运行程序就能正常显示出文件中的内容了。

fs.readFileSync(filename, [options])是readFile的同步方法。

#三、writeFile写入文件
使用fs.writeFile(filename, data, [options], callback)写入内容到文件。

writeFile接收四个参数,filename是文件名称;data是要写入文件的数据;[options]是一个对象为可选参数,包含编码格式(encoding),模式(mode)以及操作方式(flag);callback是回调函数。

##writeFile

在虚拟机家目录下新建writeFile.js文件,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

// 写入文件内容(如果文件不存在会创建一个文件)
// 写入时会先清空文件
fs.writeFile('./test2.txt', 'test test', function(err) {
    if (err) {
        throw err;
    }

    console.log('Saved.');

    // 写入成功后读取测试
    fs.readFile('./test2.txt', 'utf-8', function(err, data) {
        if (err) {
            throw err;
        }
        console.log(data);
    });
});

运行程序:

shiyanlou:~/ $ node writeFile.js

如果要追加数据到文件,可以传递一个flag参数,修改代码为如下所示:

var fs = require('fs'); // 引入fs模块

// 写入文件内容(如果文件不存在会创建一个文件)
// 传递了追加参数 { 'flag': 'a' }
fs.writeFile('./test2.txt', 'test test', { 'flag': 'a' }, function(err) {
    if (err) {
        throw err;
    }

    console.log('Saved.');

    // 写入成功后读取测试
    fs.readFile('./test2.txt', 'utf-8', function(err, data) {
        if (err) {
            throw err;
        }
        console.log(data);
    });
});

运行程序:

shiyanlou:~/ $ node writeFile.js

flag传递的值,r代表读取文件,,w代表写入文件,a代表追加写入文件,还有其他的值不作详细介绍。

#四、使用fs.read和fs.write读写文件
使用fs.read和fs.write读写文件需要使用fs.open打开文件和fs.close关闭文件。

##1、fs.read()

先介绍fs.open(path, flags, [mode], callback)方法,此方法用于打开文件,以便fs.read()读取。path是文件路径,flags是打开文件的方式,[mode]是文件的权限(可选参数,默认值是0666),callback是回调函数。

flags的值:

r :读取文件,文件不存在时报错;
r+ :读取并写入文件,文件不存在时报错;
rs :以同步方式读取文件,文件不存在时报错;
rs+ :以同步方式读取并写入文件,文件不存在时报错;
w :写入文件,文件不存在则创建,存在则清空;
wx :和w一样,但是文件存在时会报错;
w+ :读取并写入文件,文件不存在则创建,存在则清空;
wx+ :和w+一样,但是文件存在时会报错;
a :以追加方式写入文件,文件不存在则创建;
ax :和a一样,但是文件存在时会报错;
a+ :读取并追加写入文件,文件不存在则创建;
ax+ :和a+一样,但是文件存在时会报错。

fs.close(fd, [callback])用于关闭文件,fd是所打开文件的文件描述符。

fs.read(fd, buffer, offset, length, position, callback)方法接收6个参数。

fd是文件描述符,必须接收fs.open()方法中的回调函数返回的第二个参数;
buffer是存放读取到的数据的Buffer对象;
offset指定向buffer中存放数据的起始位置;
length指定读取文件中数据的字节数;
position指定在文件中读取文件内容的起始位置;
callback是回调函数,回调寒素的参数:
err用于抛出异常;
bytesRead是从文件中读取内容的实际字节数;
buffer是被读取的缓存区对象。

在家目录中新建文件testread.txt在文件中随意输入一些内容,然后新建read.js文件,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

// 打开文件
fs.open('./testeread.txt', `r`, function(err, fd) {
    if (err) {
        throw err;
    }
    console.log('open file success.');
    var buffer = new Buffer(255);
    // 读取文件
    fs.read(fd, buffer, 0, 10, 0, function(err, bytesRead, buffer) {
        if (err) {
            throw err;
        }
        // 打印出buffer中存入的数据
        console.log(bytesRead, buffer.slice(0, bytesRead).toString());

        // 关闭文件
        fs.close(fd);
    });
});

运行程序:

shiyanlou:~/ $ node read.js

#2、fs.write()

fs.write(fd, buffer, offset, length, position, callback)方法的参数和fs.read()相同,buffer是需要写入文件的内容。

在家目录中新建文件testwrite.txt,然后新建write.js文件,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

// 打开文件
fs.open('./testwrite.txt', `w`, function(err, fd) {
    if (err) {
        throw err;
    }
    console.log('open file success.');
    var buffer = new Buffer('shiyanlou');
    // 读取文件
    fs.write(fd, buffer, 0, 6, 0, function(err, writeten, buffer) {
        if (err) {
            throw err;
        }

        console.log('write success.');
        // 打印出buffer中存入的数据
        console.log(bytesRead, buffer.slice(0, bytesRead).toString());

        // 关闭文件
        fs.close(fd);
    });
});

运行程序:

shiyanlou:~/ $ node write.js

#五、目录操作

##1、创建目录

使用fs.mkdir(path, [mode], callback)创建目录,path是需要创建的目录,[mode]是目录的权限(默认值是0777),callback 是回调函数。

在家目录下创建mkdir.js文件,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

// 创建 newdir 目录
fs.mkdir('./newdir', function(err) {
    if (err) {
        throw err;
    }
    console.log('make dir success.');
});

运行代码:

shiyanlou:~/ $ node mkdir.js

运行程序后会发现在当前目录下已经创建了newdir目录,删除目录可以使用fs.rmdir(path, callback),但是只能删除空目录。

##2、读取目录

使用fs.readdir(path, callback)读取文件目录。

在家目录下新建readdir.js文件,输入如下代码并保存:

var fs = require('fs'); // 引入fs模块

fs.readdir('./newdir', function(err, files) {
    if (err) {
        throw err;
    }
    // files是一个数组
    // 每个元素是此目录下的文件或文件夹的名称
    console.log(files);
});

运行代码:

shiyanlou:~/ $ node readdir.js

#六、结束
fs模块中还有很多其他方法,其使用与前面介绍的方法类似,在此不一一介绍,可自行查阅官方API文档。

#一、Node.js Events模块
在Node.js中,很多对象都会发出事件。比如,fs.readStream打开文件时会发出一个事件。所有发出事件的对象都是events.EventEmitter的实例,可以通过require(“events”);获得event模块。

通常,事件名采用“驼峰式”命名方式,但是,并没有严格规定。这只是推荐的命名方法。

函数可以添加给对象,对象发出事件时,对应函数就会被执行。这些函数被称作监听器(listeners)。在监听器函数中,this引用的是它(指此监听器函数)添加到的EventEmitter对象。

##1. Class: events.EventEmitter
通过require(‘events’).EventEmitter得到EventEmitter类。

当EventEmitter对象遇到错误时,通常会触发error事件。error事件在Node.js中是一种特殊情况,如果没有监听器,那么默认会打印出栈跟踪器并退出程序。

###添加监听器

为事件绑定事件处理程序,可以使用emitter.addListener(event, listener)和emitter.on(event, listener),它们的作用是完全一样的。传入的参数是事件(event)和处理函数(listener)。

在虚拟机桌面新建文件test1.js,输入如下代码并保存:

var http = require('http');
var server = http.createServer();

// 为request事件绑定处理函数
// 也可以使用server.addListener
server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou');
    console.log('shiyanlou');
    res.end();
});

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

运行代码:

shiyanlou@sdf234jh4:~$ cd Desktop
shiyanlou@sdf234jh4:~/Desktop$ node test1.js

然后打开虚拟机桌面的Firefox浏览器,在地址栏输入127.0.0.1:1337,即可看到页面上打印出了“shiyanlou”字样,同时console界面也会输出’shiyanlou’字样。

###只执行一次的监听器

使用emitter.once(event, listener)绑定的事件监听器只会执行一次,然后就会被删除掉。

在虚拟机桌面新建文件test2.js,输入如下代码并保存:

var http = require('http');
var server = http.createServer();

// 为request事件绑定处理函数,事件只会执行一次
server.once('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou');
    console.log('shiyanlou');
    res.end();
});

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

运行代码:

shiyanlou@sdf234jh4:~/Desktop$ node test2.js
打开虚拟机桌面的Firefox浏览器,在地址栏输入127.0.0.1:1337,即可看到页面上打印出了“shiyanlou”字样,再次刷新此页面,就不会再显示,因为此事件只会执行一次。

###移除监听器

移除监听器使用emitter.removeListener(event, listener)。

在虚拟机桌面新建文件test3.js,输入如下代码并保存:

var http = require('http');
var server = http.createServer();

function callback(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('Hello World');
    console.log('Hello World');
    res.end();
}

server.on('request', callback);

// 移除绑定的监听器callback
server.removeListener('request', callback);

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou');
    console.log('shiyanlou');
    res.end();
});

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

运行代码:

shiyanlou@sdf234jh4:~/Desktop$ node test3.js

打开虚拟机桌面的Firefox浏览器,在地址栏输入127.0.0.1:1337,即可看到页面上打印出了“shiyanlou”字样,为什么没有显示“Hello World”呢?因为显示“Hello World”的监听器被移除了。

###移除所有监听器

移除所有监听器使用emitter.removeAllListeners([event])。

在虚拟机桌面新建文件test4.js,输入如下代码并保存:

var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou,111');
    console.log('shiyanlou,111');
    res.end();
});

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou,222');
    console.log('shiyanlou,222');
    res.end();
});

// 移除绑定的所有监听器
server.removeAllListeners('request');

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou');
    console.log('shiyanlou');
    res.end();
});

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

运行代码:

shiyanlou@sdf234jh4:~/Desktop$ node test4.js

打开虚拟机桌面的Firefox浏览器,在地址栏输入127.0.0.1:1337,即可看到页面上打印出了“shiyanlou”字样,说明前面的监听器被移除了,都没有执行,所以没有显示,同时console界面也只会输出’shiyanlou’字样。

###设置监听器最大绑定数

emitter.setMaxListeners(n)可以设置同一事件的监听器最大绑定数,默认情况下,超过10个就会警告提示,这能帮我们快速找到类存泄露的地方。显然,不是所有的事件触发器都限制在10个监听器,通过这个方法可以设置,如果设置为0就是无限制。

###自定义事件

使用emitter.emit(event, [arg1], [arg2], […])可以触发自定义的事件。

在虚拟机桌面新建文件test5.js,输入如下代码并保存:

var http = require('http');
var server = http.createServer();

// 绑定自定义事件myevent
server.on('myevent', function(arg) {
    console.log(arg);
});

// 触发自定义事件
server.emit('myevent', 'shiyanlou');

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

运行代码:

shiyanlou@sdf234jh4:~/Desktop$ node test5.js

可以看到console界面输出了’shiyanlou’字样,说明触发自定义事件成功。

###查看事件绑定的监听器个数

使用EventEmitter.listenerCount(emitter, event)可以查看事件监听器数量。

在虚拟机桌面新建文件test6.js,输入如下代码并保存:

var http = require('http');
var events = require('events'); // 加载events模块
var server = http.createServer();

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou,111');
    console.log('shiyanlou,111');
    res.end();
});

server.on('request', function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.write('shiyanlou,222');
    console.log('shiyanlou,222');
    res.end();
});

server.listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

// 查看server绑定的'request'事件的监听器个数
var num = events.EventEmitter.listenerCount(server, 'request');
console.log(num);

运行代码:

shiyanlou@sdf234jh4:~/Desktop$ node test6.js

可以看到console界面输出了数字“2”,因为server绑定了两个监听器到’request’事件。

##1. 模块的使用
编写一个模块:

在虚拟机桌面新建一个文件mymodule.js,输入如下代码并保存:

function hello() {
    console.log('Hello');
}

function world() {
    console.log('World');
}

这就是一个Node.js模块,但是怎么在其他模块中引入并使用这个模块呢?我们需要为模块提供对外的接口,这就要用到module.exports和exports。

我们可以这样写mymodul.js

function hello() {
    console.log('Hello');
}

function world() {
    console.log('World');
}

exports.hello = hello;
exports.world = world;

在其他模块中,可以使用require(module_name);载入需要的模块,如,在虚拟机桌面新建index.js,输入如下代码并保存:

var hello = require('./mymodule'); // 也可以写作 var hello = require('./mymodule.js');

// 现在就可以使用mymodule.js中的函数了

hello.hello(); // >> Hello
hello.world(); // >> World

也可以这样写mymodule.js

function Hello() {
    this.hello = function() {
        console.log('Hello');
    };

    this.world = function() {
        console.log('World');
    };
}

module.exports = Hello;

此时,index.js就要改成这样:

var Hello = require('./mymodule');

var hello = new Hello();

hello.hello(); // >> Hello
hello.world(); // >> World

##2. module.exports和exports
module是一个对象,每个模块中都有一个module对象,module是当前模块的一个引用。module.exports对象是Module系统创建的,而exports可以看作是对module.exports对象的一个引用。在模块中require另一个模块时,以module.exports的值为准,因为有的情况下,module.exports和exports它们的值是不同的。module.exports和exports的关系可以表示成这样:

// module.exports和exports相同的情况
var m = {};        // 表示 module
var e = m.e = {};  // e 表示 exports, m.e 表示 module.exports

m.e.a = 5;
e.b = 6;

console.log(m.e);  // Object { a: 5, b: 6 }
console.log(e);    // Object { a: 5, b: 6 }


// module.exports和exports不同的情况
var m = {};        // 表示 module
var e = m.e = {};  // e 表示 exports, m.e 表示 module.exports

m.e = { c: 9 };    // m.e(module.exports)引用的对象被改了
e.d = 10;

console.log(m.e);  // Object { c: 9 }
console.log(e);    // Object { d: 10 }

这是xampp和优胜美地的不兼容造成了,新版本的xampp已经修复了这个bug。对于不想升级xampp同时又想修复这个问题的同学,可以按照以下方式解决:
1、打开/Applications/XAMPP/xamppfiles/xampp进行编辑
2、找到这一行:$XAMPP_ROOT/bin/mysql.server start > /dev/null &
3、添加如下一行在找到的那一行上面:unset DYLD_LIBRARY_PATH,结果如下:
unset DYLD_LIBRARY_PATH
$XAMPP_ROOT/bin/mysql.server start > /dev/null &

然后即可启动mysql

参考:https://community.bitnami.com/t/mysqld-doesnt-start-in-mac-os-yosemite/25153/6
转自:http://www.zhujianfeng.info/?p=234

##thinkphp模型介绍
在thinkphp中有三种模型

###普通模型
一般使用普通模型的时候我们是用来处理一个表的
通常使用的M(‘user’) (M当中的数据表只能小写)

###视图模型

###关联模型
如下为一个关联模型的文件

<?php
/**
 * 用户与角色关联模型
 */
class UserRelationModel extends RelationModel{
    //定义主表名称
    protected $tableName = 'user';

    //定义关联关系
    protected $_link = array(
        'role' => array(
            'mapping_type' => MANY_TO_MANY,//多对多关系,关联关系
            'foreign_key' => 'user_id',//主表在中间表中的字段名称
            'relation_key' => 'role_id',//副表在中间表中的字段名称
            'relation_table' => 'think_role_user',//中间表名称
            'mapping_fields' => 'id, name, remark',//只读取副表的这些字段
            )
        );
}
?>

读取主表

$user = D('UserRelation')->select()

读取主表和用户表

$user = D('UserRelation')->relation('role')->select();

读取主表中除了password之外的所有字段

$user = D('UserRelation')->field('password',true)->relation(true)->select();