nodejs+express搭建一个高性能WebServer

  因为最近的项目中要用到nodejs作一个WebServer服务器,因此最近学习了一下nodejs的语法和express框架。学习的过程当中也参考了许多文章博客,同时也有一些本身的心得体会,如今都一一记录下来。html

  首先,第一个问题,为何选了nodejs来作WebServer?或者换一种说法,用nodejs作WebServer与其余语言相比有哪些优点?nodejs是运行chrome的V8上的JavaScript,采用事件驱动、非阻塞异步IO模型,最重要的是,它是单线程的(固然并非真正的单线程,这里的单线程指的是主线程只有一个,而底层的工做线程有多个,要否则怎么实现异步的IO对吧),站在开发者的角度上讲,你在nodejs上写的全部逻辑,都是运行在一个主线程上的。单线程的好处是很显而易见的,内存占用少,较多线程而言CPU切换的开销小,编写单线程程序简单,绝对的线程安全,也不用考虑多线程之间的内存共享和同步的问题。然而单线程的劣势也是很明显的,没法利用CPU多核的优点,没法处理CPU密集型的任务,由于容易形成主线程因为长时间耗在计算任务上而出现线程的阻塞。然而这些劣势在WebServer服务器上都是能够避免的,WebServer服务器原本就应该避免复杂的计算,计算是留给后台作的,WebServer要关注的问题始终是怎样实现一个较高的IO效率,如何实现一个较大的吞吐率以及怎样将前端过来的请求以最快的速度响应给前端,nodejs的异步IO刚好能够实现这一点。关于单线程没法利用多核CPU的问题,其实若是真的要把CPU充分利用起来的话,能够在一台服务器上开多个nodejs服务,监听不一样的端口,再用一个负载均衡将请求轮询分发到这些端口上,这里要保证每份nodejs服务都是同样的且无状态的,若是要实现session机制的话,能够用共享的一个redis来实现。因为个人WebServer是部署在云端的,因此我在开发的过程当中用了一个clb来作负载均衡,在云上申请了一个redis来作共享的session存储。前端

  下面,来说讲个人具体实现。因为个人WebServer服务器要与前端的微信小程序交互,根据微信小程序的官方开发文档,小程序、开发者服务器与微信的服务接口之间的交互应该是这样的:node

  首先,微信小程序前端获取code,再把code发送给开发者服务器,开发者服务器拿到这个code以后,再带上小程序的appid和appsecret,去调微信的接口,拿到openid和session_key,而后自定义一个本身的登录态(就至关于再作一层session,根据openid和session_key经过某种算法生成一个本身的sessionid,由于openid和session_key是不该该直接给前端的,会形成安全问题),最后把这个sessionid返回给小程序前端,前端拿到这个sessionid以后,之后每次请求都要带上这个sessionid,后台检测这个sessionid是否合法,这就至关于在微信的登录机制上定义了一个本身的登录态。具体的实现代码以下(个人WebServer服务器如今充当了上图中开发者服务器的角色):redis

配置文件config.js:算法

 1 var config={
 2     //redis配置
 3     redisPort:6379,
 4     redisHost:'127.0.0.1',
 5     redisPasswd:'xxxxx',
 6     //微信小程序配置
 7     appid:'12345678',
 8     secret:'1234567890',
 9     wxAddress:'https://api.weixin.qq.com/sns/jscode2session',
10 }
11 module.exports=config;

server端代码:chrome

 1 var app=require('express')();
 2 var request=require('request');
 3 var querystring=require('querystring');
 4 var redis=require('redis');
 5 var crypto=require('crypto');
 6 var bodyParser=require('body-parser');
 7 var config=require('./config');
 8 
 9 //链接redis
10 var opts={auth_pass : config.redisPasswd};
11 var redisStore=redis.createClient(config.redisPort, config.redisHost, opts);
12 redisStore.on('connect', function(){
13     console.log('redis connect successful');
14 });
15 //使用JSON解析工具
16 app.use(bodyParser.urlencoded({extended: false}));
17 app.use(bodyParser.json());
18 //监听登陆请求
19 app.get('/onLogin', function(req, res){
20     let code=req.query.code;
21     console.log("onLogin: code:"+code);
22     var getData=querystring.stringify({
23         appid: config.appid,
24         secret: config.secret,
25         js_code:code,
26         grant_type:'authorization_code'
27     });
28     var url=config.wxAddress+"?"+getData;
29     var session_id="";
30     request.get(url, function(err, req){
31         if(!err && req.statusCode===200){
32             var json=JSON.parse(req.body);
33             var openid=json.openid;
34             var session_key=json.session_key;
35             console.log('openid: '+openid);
36             console.log('session_key: '+session_key);
37             if(openid && session_key){
38                 //根据openid和session_key用md5算法生成session_id
39                 var hash=crypto.createHash('md5');
40                 hash.update(openid+session_key);
41                 session_id=hash.digest('hex');
42                 console.log('session_id:'+session_id);
43                 //将session_id存入redis并设置超时时间为30分钟
44                 redisStore.set(session_id, openid+":"+session_key);
45                 redisStore.expire(session_id, 1800);
46                 将session_id传递给客户端
47                 res.set("Content-Type", "application/json");
48                 res.json({sessionid: session_id});
49             }else{
50                 res.json({warning: 'code is invalid'});
51             }
52         }else{
53             console.log(err);
54         }
55     });
56 });
57 var server=app.listen(8889, function(){
58     var host=server.address().address;
59     var port=server.address().port;
60     console.log('address is http://%s:%s', host, port);
61 });

登录功能完成后,就能够写其余的接口了,在接口中判断sessionid是否有效,若是有效就提供服务,无效直接拒绝:express

 1 app.get('/products', function(req, res){
 2     let session_id=req.header('sessionid');
 3     let session_val=redisStore.get(session_id);
 4     if(session_val){
 5         console.log('sessionid is not ok');
 6         ...
 7     }else{
 8         console.log('sessionid is ok');
 9         res.json({warning: 'sessionid is invalid'});
10     }
11 });

  有时候,为了确保安全,咱们还须要采用https与前端进行通讯,这个时候就须要有ca证书了,固然若是尚未来得及申请,咱们也能够本身手动生成一个证书供测试用。咱们可使用openssl来生成证书:json

一、生成私钥文件:小程序

  openssl genrsa 1024 > private.pem微信小程序

二、经过私钥文件生成CSR证书签名:

  openssl req -new -key private.pem -out csr.pem

三、经过私钥文件和证书签名生成证书文件:

  openssl x509 -req -days 365 -in csr.pem -signkey private.pem -out file.crt

填完国家、省份、公司名等信息以后证书就制做完成了。

  加入https后,server端的代码就变成了这样:

 1 var app=require('express')();
 2 var request=require('request');
 3 var querystring=require('querystring');
 4 var redis=require('redis');
 5 var crypto=require('crypto');
 6 var bodyParser=require('body-parser');
 7 var config=require('./config');
 8 var fs=require('fs');
 9 var http=require('http');
10 var https=require('https');
11 
12 //读取https证书
13 var privateKey=fs.readFileSync('./private.pem', 'utf8');
14 var certificate=fs.readFileSync('./file.crt', 'utf8');
15 var credentials={key: privateKey, cert: certificate};
16 //链接redis
17 var opts={auth_pass : config.redisPasswd};
18 var redisStore=redis.createClient(config.redisPort, config.redisHost, opts);
19 redisStore.on('connect', function(){
20     console.log('redis connect successful');
21 });
22 //使用JSON解析工具
23 app.use(bodyParser.urlencoded({extended: false}));
24 app.use(bodyParser.json());
25 //开启监听
26 var httpsServer=https.createServer(credentials, app);
27 httpsServer.listen(config.httpsPort, function(){
28     console.log('HTTPS server is running on https://localhost:%s', config.httpsPort);
29 });
30 //监听登陆请求
31 app.get('/onLogin', function(req, res){
32     let code=req.query.code;
33     console.log('Request path:'+req.path);
34     console.log("code:"+code);
35     var getData=querystring.stringify({
36         appid: config.appid,
37         secret: config.secret,
38         js_code:code,
39         grant_type:'authorization_code'
40     });
41     var url=config.wxAddress+"?"+getData;
42     var session_id="";
43     request.get(url, function(err, req){
44         if(!err && req.statusCode===200){
45             var json=JSON.parse(req.body);
46             var openid=json.openid;
47             var session_key=json.session_key;
48             console.log('openid: '+openid);
49             console.log('session_key: '+session_key);
50             if(openid && session_key){
51                 //根据openid和session_key用md5算法生成session_id
52                 var hash=crypto.createHash('md5');
53                 hash.update(openid+session_key);
54                 session_id=hash.digest('hex');
55                 console.log('session_id:'+session_id);
56                 //将session_id存入redis并设置超时时间为30分钟
57                 redisStore.set(session_id, openid+":"+session_key);
58                 redisStore.expire(session_id, 1800);
59                 //将session_id传递给客户端
60                 res.set("Content-Type", "application/json");
61                 res.json({sessionid: session_id});
62             }else{
63                 res.json({warning: 'code is invalid'});
64             }
65         }else{
66             console.log(err);
67         }
68     });
69 });
70 app.get('/products', function(req, res){
71     let session_id=req.header('sessionid');
72     let session_val=redisStore.get(session_id);
73     if(session_val){
74         console.log('sessionid is not ok');
75         ...
76     }else{
77         console.log('sessionid is ok');
78         res.json({warning: 'sessionid is invalid'});
79     }
80 });

  咱们还能够把本身生成的sessionid放在cookie里面,这样前端登录后,之后每次发请求就不用在参数里面带上sessionid了,由于咱们能够直接从cookie里面获取sessionid,而后进行校验。因此最终的代码就变成了这样:

 1 var app=require('express')();
 2 var request=require('request');
 3 var querystring=require('querystring');
 4 var redis=require('redis');
 5 var crypto=require('crypto');
 6 var bodyParser=require('body-parser');
 7 var config=require('./config');
 8 var fs=require('fs');
 9 var http=require('http');
10 var https=require('https');
11 var cookieParser=require('cookie-parser');
12 
13 //读取https证书
14 var privateKey=fs.readFileSync('./private.pem', 'utf8');
15 var certificate=fs.readFileSync('./file.crt', 'utf8');
16 var credentials={key: privateKey, cert: certificate};
17 //链接redis
18 var opts={auth_pass : config.redisPasswd};
19 var redisStore=redis.createClient(config.redisPort, config.redisHost, opts);
20 redisStore.on('connect', function(){
21     console.log('redis connect successful');
22 });
23 //使用JSON解析工具
24 app.use(bodyParser.urlencoded({extended: false}));
25 app.use(bodyParser.json());
26 //使用cookie
27 app.use(cookieParser());
28 //开启监听
29 var httpsServer=https.createServer(credentials, app);
30 httpsServer.listen(config.httpsPort, function(){
31     console.log('HTTPS server is running on https://localhost:%s', config.httpsPort);
32 });
33 //监听登陆请求
34 app.get('/onLogin', function(req, res){
35     let code=req.query.code;
36     console.log('Request path:'+req.path);
37     console.log("code:"+code);
38     var getData=querystring.stringify({
39         appid: config.appid,
40         secret: config.secret,
41         js_code:code,
42         grant_type:'authorization_code'
43     });
44     var url=config.wxAddress+"?"+getData;
45     var session_id="";
46     request.get(url, function(err, req){
47         if(!err && req.statusCode===200){
48             var json=JSON.parse(req.body);
49             var openid=json.openid;
50             var session_key=json.session_key;
51             console.log('openid: '+openid);
52             console.log('session_key: '+session_key);
53             if(openid && session_key){
54                 //根据openid和session_key用md5算法生成session_id
55                 var hash=crypto.createHash('md5');
56                 hash.update(openid+session_key);
57                 session_id=hash.digest('hex');
58                 console.log('session_id:'+session_id);
59                 //将session_id存入redis并设置超时时间为20分钟
60                 redisStore.set(session_id, openid+":"+session_key);
61                 redisStore.expire(session_id, 1200);
62                 //将session_id存入cookie设置超时时间为20分钟
63                 res.cookie('sessionid', session_id, {maxAge: 20*60*1000});
64                 res.json({sessionid: session_id, errorCode: 0});
65             }else{
66                 res.json({msg: 'code is invalid', code: 8001});
67                 console.log('code is invalid, errorCode: 8001');
68             }
69         }else{
70             res.json({msg: 'unknow errro', code: 9001});
71             console.log('unknow error, errorCode: 9001, err:'+err);
72         }
73     });
74 });
75 app.get('/products', function(req, res){
76     let session_id=req.cookies.sessionid;
77     let session_val=redisStore.get(session_id);
78     if(session_val){
79         console.log('sessionid is not ok');
80     }else{
81         console.log('sessionid is ok');
82         res.json({warning: 'sessionid is invalid'});
83     }
84 });

 

 

参考文章:

一、http://www.javashuo.com/article/p-nrxwnsrb-k.html

二、https://www.jianshu.com/p/853099ae2edd

三、http://www.javashuo.com/article/p-twdsgyec-z.html

四、https://blog.csdn.net/qq_38125123/article/details/71196853

五、http://www.javashuo.com/article/p-chaxjhmh-gg.html

六、https://www.zhihu.com/question/61337684

七、http://www.javashuo.com/article/p-ecmrxuia-t.html

八、http://www.javashuo.com/article/p-axvnbpzd-gw.html

相关文章
相关标签/搜索