NodeJS

Starter

Allows you to build scalable network applications using JavaScript on the server-side.

Node.js:

  • 用JavaScript编写服务端程序
  • V8引擎
  • 采用单线程+非阻塞IO的方式(以事件的方式通知回调函数)达到并行的目的(高吞吐量,充分利用CPU资源)
  • 适合场景:
    • 数据密集型(大量读取数据文件)
    • 实时交互系统
  • 不适合场景:
    • 计算量大
    • 逻辑太复杂

nodeJS 本身并没有根目录的概念,并不能读取某个文件夹的静态文件,只能一个文件一个文件的运行 nodeJS 自身封装了一些模块,使用require(moduleName)引用 nodeJS 本身不具备路由功能,服务跑起来后,不管地址栏添加任何path,结果都不变

npm

npm (Node Package Manager)

  • node包管理和分发工具,node自动安装
  • 能很快找到特定服务需要的包,下载安装管理
# -g 表示全局,不加-g,则安装在当前目录的node_modules下
> npm install -g xxx

# 下载包并记录在package.json文件中的devDependencies下
> npm install xxx --save-dev

# 下载包并记录在package.json文件中的dependencies下
> npm install xxx --save

# 下载指定version    
> npm install xxx@version

# 卸载node模块
> npm uninstall moudleName

# 更新node模块
> npm update moduleName
# 根据当前目录下的package.json文件中声明的内容安装(dependencies的内容)
> npm install

# 初始化(会引导你创建一个package.json文件)
> npm init
# 查看当前目录下已安装的node包
> npm list

# 查看当前包的安装路径
> npm root

# 查看全局的包的安装路径
> npm root -g

# 查看npm安装的版本
> npm -v

HelloWorld

nodeTest/01.js:

var http=require('http');
http.createServer(function(request,response){
    console.log(request.url);
    res.writeHead(200,{'Content-Type':'text/plain'});
    res.end('Hello world');
}).listen(1337);
console.log('Listening on port 1337' );

执行:

> node nodeTest/01.js

浏览器中访问:http://localhost:1337,http://localhost:1337/123查看效果

Events

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

//绑定事件
logger.on('error',function(msg){
    console.log('Err:'+msg);
});

//触发事件
logger.emit('error','Wrong number');
logger.emit('error','Unknow cmd');

Node里面的许多对象都会emit事件 例如: class http.Server是一个EventEmitter,当有一个request时,就会通知绑定的function(request,response){...}执行

http.createServer(function(request,response){...});

//等价于:
var server=http.createServer();
server.on('request',function(request,response){...});

Streams

Stream可分为:

  • readable
    • 事件
      • readable
      • data
      • end
      • close
      • error
    • 方法
      • read
      • pause
      • resume
      • pipe
      • unpipe
  • writeable
    • 事件
      • drain
      • finish
      • pipe
      • unpipe
    • 方法
      • write
      • end
  • readable & writeable

例如1:

http.createServer(function(request,response){
    response.writeHead(200);
    request.on('data',function(chunk){
        response.write(chunk);
    });
    request.on('end',function(){
        response.end();
    })
}).listen(8080)

等同于:

http.createServer(function(request,response){
    response.writeHead(200);
    request.pipe(response)
}).listen(8080)
  • request为readable stream
  • response为writable stream
  • 测试:
      > curl -d 'hello' http://localhost:8080`
      > curl --upload-file readme.md http://localhost:8080`
    

例如2:

readStream.on('data',function(chunk){
    var buffer_good =writeStream.write(chunk);    //返回true/false,表明数据是否被写入目标了
    if (!buffer_good)                //为false,表示数据临时放在了缓存,而未写入目标
        readStream.pause();        //暂停读取
});
writeStream.on('drain',function(){
    readStream.resume();            //恢复读取
})

等同于:

//pipe可以解决当延迟非常大时导致的读写不平衡问题
readStream.pipe(writeStream);

Modules

  • 定义一个Module:module.exports=xxx
  • 调用自定义Module:require(path) 使用相对路径或绝对路径
  • 调用外部的Module:require(moduleName) 会从当前目录逐层往上搜索node_modules目录

示例:

// makeRequest.js
var http=require('http');
module.exports=function(opts,message){
    var request=http.request(opts,function(response){
        response.on('data',function(data){
            console.log(data);
        });
        request.write(message);
        request.end();
    })
}

调用

var makeRequest=require('./makeRequest');
markeRequest({host:'localhost','port':8080,path:'/',method:'POST'},"Hello world!");

Express

Express.js框架: 极大的简化了nodeJS开发

> npm install express
var express=require('express');
var app=express();

...

app.listen(1337,function(){
    console.log("Listen on port 1337");
});

Middleware

一个Express应用,从本质上来说,就是一系列中间件的调用

Middleware

  • 中间件就是一函数,形式如下:

    function(request,response,next){
      ...
      //next(); or next(xxx); or response.xxx
    }
    
    //错误处理的中间件
    function(err, req, res, next){
      ...
      //err.status,err.message
    }
    
    • request
      • query string:request.query.xxx
      • placeholder in url:request.params.xxx
    • response
      • response.send(xxx),response.json(xxx),response.sendFile(xxx)
      • response.status(xxx), response.status(xxx).json(xxx)
      • response.render(xxx)
      • response.redirect(xxx)
    • next
      • next()表示传递给下一个中间件进行处理
      • 如果带参数,则代表抛出一个错误
  • 注册中间件

    • app.use([path], [function, ...] function)
    • app.METHOD([path], [function, ...] function) (METHOD可为get,post,put,delete,all,param等)

使用举例1:Express的static middleware

  • 项目目录结构:
      nodeTest
      - app.js
      - public/
          - blocks.jpg
          - index.html
    
  • 使用static middleware
      app.use(express.static('public'));
    
  • 访问静态资源:
    • GET http://localhost:1337/blocks.jpg
    • GET http://localhost:1337/index.html

使用举例2:自定义Middleware

  • logger.js
      module.exports=function(request,response,next){
          ...
          next();
      }
    
  • 使用自定义的logger middleware
      var logger=require('./logger');
      app.use(logger);
    

Route Instances

app.route('/blocks')
    .get(function(request,response,next){
        ...
    })
    .post(parseUrlencoded, function(request, response) {
    ...
    });

app.route('/blocks/:name')
    .all(function(request,response,next){
        ...
        next();
    })
    .get(function(request,response,next){
        ...
    })
    .delete(function(request,response,next){
        ...
    });

从Express 4.0开始,路由器功能成了一个单独的组件Express.Router

  • router.use(...)
  • router.METHOD(...)
  • router.route(...)
  • ...[path], [function, ...] function
var express = require('express');
var router=express.Router();
router.route('/')
    .get(function(request,response,next){
        ...
    })
    .post(parseUrlencoded, function(request, response) {
    ...
    });
router.route('/:name')
    .all(function(request,response,next){
        ...
        next();
    })
    .get(function(request,response,next){
        ...
    })
    .delete(function(request,response,next){
        ...
    });
module.exports = router;
var blocks=require('./routes/blocks');
app.use('/blocks',blocks);

Request Params

示例1:request.params.xxx,request.query.xxx

app.get('/blocks/:name', function(request, response) {
    console.log(request.params.name);
    console.log(request.query.limit);
    response.send(request.query.limit+","+request.params.name);
});
> curl -i http://localhost:1337/blocks/test?limit=5

HTTP/1.1 200 OK
"5,test"

示例2:app.param(...)

app.param('name', function(request, response, next) {
    request.name=request.params.name;
    next();
});

app.get('/blocks/:name', function(request, response,next) {
    console.log(request.name);
    console.log(request.params.name);
    ...
});

示例3:app.all(...)

app.all('/blocks/:name',function(request,response,next){
    request.name=request.params.name;
    next();
})
app.get('/blocks/:name', function(request, response,next) {
    console.log(request.name);
    console.log(request.params.name);
    ...
});

常用module

body parser

Node.js body parsing middleware.provides the following parsers: (官网)

  • JSON body parser bodyParser.json(options) (parse application/json)
  • Raw body parser bodyParser.raw(options)
  • Text body parser bodyParser.text(options)
  • URL-encoded form body parser bodyParser.urlencoded(options) (parse application/x-www-form-urlencoded

注意:body-parser does not handle multipart bodies (无法解析multipart/form-data

使用示例:

安装body-parser包(parse form elements to object properties)

> npm install body-parser

后台使用:( request.body :get form data)

var bodyParser=require('body-parser');
var parseUrlencoded=bodyParser.urlencoded({extended:false});

app.post('/blocks',parseUrlencoded,function(request,response,next){
    //request.body.xxx
    ...
})

前台触发:

$.ajax({
    type:'POST',url:'/blocks',data:$("#blockForm").serialize()
}).done(function(data){
    console.log(data);
});

formidable

> npm install formidable

后端:

var formidable=require('formidable');
app.post('/blocks',function(req,res){
    var form=formidable.IncomingForm();
    form.on('field',function(name,value){
        console.log(name+":"+value);
    });
    form.on('file',function(name,file){
        console.log(name+"+"+file.size);
    });
    form.on('end',function(){
        console.log('end');
        ...
    });
});
var formidable=require('formidable');
app.post('/blocks',function(req,res){
    var form=formidable.IncomingForm();
    form.parse(req, function(err, fields, files) {
        console.log(fields);
        console.log(files);
    });
});
var formidable=require('formidable');
app.post('/blocks',function(req,res){
    var form=formidable.IncomingForm();

    //all fields and files are collected and passed to the callback
    form.parse(req, function(err, fields, files) {
        console.log(arguments);
        //end
         res.json({success:false,data:JSON.stringify(block)});
    });
    //overwrite onPart to filter
    form.onPart = function(part) {
      if(part.filename){
          //在这里处理file,不再交给formidable继续处理(不会再触发file事件)
        part.pipe(fs.createWriteStream(part.filename));
      }else{
          form.handlePart(part);    //交给formidable继续处理
      }
    };
    // 处理POST 普通数据( 不包含文件 )
    form.on('field',function(name,value){
         block[name]=value;
    });
});

前端:

 var formData = new FormData($("form")[0]);
 $.ajax({
    url: '/blocks',
    type: 'POST',
    data: formData,
    processData: false
}).done(function(data){
    console.log(data);
});

session

> npm install cookie-parser
> npm install express-session

示例1:

var cookieParser = require('cookie-parser');
var session=require("express-session");
app.use(cookieParser)
    .use(session({
        resave:false,
        saveUninitialized:true,
        secret:"xxxx"
    }));

app.post("/j_spring_security_check",function(request,response,next){
    request.session.loginUser=request.body.username;
    ...
});

app.get('/j_spring_security_logout',function(request,response){
    console.log(request.session);
    request.session.destroy(function(err){
        if(err)
            throw err;
        console.log("destroy session!");
    });
    ...
});

示例2:使用Redis存储session

> npm install connect-redis
var RedisStore = require('connect-redis')(session);
app.use(session({
store: new RedisStore({
    host: "127.0.0.1",
    port: 6379,
    db: "test_session"
  }),
  resave:false,
  saveUninitialized:false,
  secret: 'xxx'
}));

示例3:使用MongoDB存储session

> npm install connect-mongo
var MongoStore = require('connect-mongo')(session);
app.use(session({
     store: new MongoStore({
         host: 'localhost',
         port: 27017,
         db: 'test-app'
     })
     secret: 'xxx',
     resave: false,
     saveUninitialized: true,
 }));

view engine

示例:使用ejs

> npm install ejs
var ejs=require("ejs");
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');
app.engine('html',ejs.__express) ;

app.get('/test',function(request,response,next){
    request.render('test', {title: 'Test Message'});
});

Socket.io

Using duplexed websocket connection

安装

> npm install socket.io

使用示例:

app.js

var socket=require('socket.io');
var express=require('express');
var path = require('path');

var app=express();
var server = require('http').Server(app);
var io=socket.listen(server);
server.listen(1337);

app.get('/', function(req, res){
  //res.send('<h1>Hello world</h1>');
  res.sendFile(path.join(__dirname,'/01.html'));
});

io.set('log level', 2);        // 0:error,1:warn,2:info,3:debug
io.sockets.on('connection',function(client){
    client.on('join',function(name){
        client.set('nickname',name);
        client.broadcast.emit("status",name+" joined!");
    });
    client.on('message',function(data){
        client.get('nickname',function(err,name){
            io.sockets.emit("chat",name+" : "+data);
        });
    });
    client.on('disconnect',function(name){
        client.get('nickname',function(err,name){
            client.broadcast.emit("status",name+" leaved!");
        });
    });
});

01.html

<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="vender/jquery/jquery.js"></script>

<script type="text/javascript">
    var server=io.connect('http://localhost:8080');
    var content=$("#content");
    server.on("connect",function(){
        content.html('Connected');
        var nickname=prompt("What is your nickname?");
        server.emit("join",nickname);
        $("#clientName").text("--"+nickname);
    });
    server.on("chat",function(data){
        content.append("<p>"+data+"</p>");
    });
    server.on("status",function(data){
        content.append("<div><small>"+data+"</small></div>");
    });
    $("#chatForm").on("submit",function(event){
        event.preventDefault();
        server.emit("message",$("#chatInput").val());
        $("#chatInput").val("");
    });
</script>

测试

> node nodeTest/app.js

Reference