NodeJS 做中间服务器
实现前后端分离
node 天生适合处理业务层面的逻辑,但是不适合作为数据计算处理。在做web项目的时候,使用 node 来做中间服务器,处理客户端的页面渲染,转发请求到接口服务器,实现前后端的分离,后端不再需要管理前端的业务渲染,专注于接口的抽象化和灵活化,前端不再需要等待路由页面的添加才能写页面,可以自己往路由添加需要的页面。
共同管理
中间服务器的功能非常简单,因此前后端可以共同管理,前端负责添加页面层级的路由,后端负责转发到接口服务器的路由
淘宝基于 Node 的前后端分离
上图是淘宝基于Node的前后端分离分层,以及Node的职责范围。简单解释下:
最上端是服务端,就是我们常说的后端。后端对于我们来说,就是一个接口的集合,服务端提供各种各样的接口供我们使用。因为有Node层,也不用局限是什么形式的服务。对于后端开发来说,他们只用关心业务代码的接口实现。
服务端下面是Node应用。
Node应用中有一层Model Proxy与服务端进行通讯。这一层主要目前是抹平我们对不同接口的调用方式,封装一些view层需要的Model。
Node层还能轻松实现原来vmcommon,tms(引用淘宝内容管理系统)等需求。
Node层要使用什么框架由开发者自己决定。不过推荐使用express+xTemplate的组合,xTemplate能做到前后端公用。
怎么用Node大家自己决定,但是令人兴奋的是,我们终于可以使用Node轻松实现我们想要的输出方式:JSON/JSONP/RESTful/HTML/BigPipe/Comet/Socket/同步、异步,想怎么整就怎么整,完全根据你的场景决定。
浏览器层在我们这个架构中没有变化,也不希望因为引入Node改变你以前在浏览器中开发的认知。
引入Node,只是把本该就前端控制的部分交由前端掌控。
node + express 构建中间服务器
server.js
var express = require('express')
var rMiddle = require('./router/rMiddle.js')
// remoteapi 开头的转发给接口服务器
app.use('/remoteapi', rMiddle);
// 前端页面渲染
app.get('/index', function(req, res) {
res.sendFile(__dirname + 'index.html');
})
app.listen('8080', function(err) {
if (err) {
return;
}
console.log('Listening at localhost:8080');
});
/router/rMiddle.js
var express = require('express');
var http = require('http');
var querystring = require('querystring');
var router = express.Router();
// 你的接口服务器地址
var connectServer = {
port: '9000',
protocol: 'http://',
host: '192.168.0.1'
};
var serverDomainPort = connectServer.port;
var serverDomainProtocol = connectServer.protocol;
var serverDomainHost = connectServer.host
var serverDomain = serverDomainProtocol + serverDomainHost + ':' + serverDomainPort;
// 定义一个通用 Get 接口,转接所有数据,不再一个个写
router.get('*', (req, res) => {
let reqUrl = serverDomain + req.url;
console.log('[GET Request]: ', reqUrl);
http.get(reqUrl, (sres) => {
var statusCode = sres.statusCode;
var contentType = sres.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error(`Request Failed.\n` + `Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error(`Invalid content-type.\n` + `Expected application/json but received ${contentType}`);
}
if (error) {
// consume response data to free up memory
sres.resume();
res.status(500).end();
return;
}
sres.setEncoding('utf8');
let rawData = '';
sres.on('data', (chunk) => rawData += chunk);
sres.on('end', () => {
try {
let parsedData = JSON.parse(rawData);
res.json(parsedData);
} catch (e) {
res.status(500).send(e.message);
}
});
}).on('error', (e) => {
console.log(`Got error: ${e.message}`);
res.status(500).end();
});
})
// 定义一个通用的 Post 接口,转接所有数据
router.post('*', (req, res) => {
var reqUrl = serverDomain + req.url;
var reqContentType = req.headers['content-type'];
var reqBody = req.body;
// 根据 请求的 content-type 判断用哪种格式化方式
var reqData = reqContentType.indexOf('json') !== -1 ? JSON.stringify(reqBody) : querystring.stringify(reqBody);
var postOpt = {
host: serverDomainHost,
port: serverDomainPort,
path: req.url,
method: 'POST',
headers: {
'Content-Type': reqContentType
}
};
var sreq = http.request(postOpt, (sres) => {
var body = '';
var error;
if (sres.statusCode !== 200) {
error = new Error(`Request Failed.\n` + `Status Code: ${sres.statusCode}`)
}
if (error) {
console.log(error.message);
sres.resume();
res.status(500).end();
return;
}
sres.on('data', (data) => {
body += data;
})
.on('end', () => {
try {
var parsedData = JSON.parse(body);
res.json(parsedData);
} catch (e) {
console.log(e.message);
res.status(500).send(e.message);
}
})
.on('error', () => {
console.log('[ERROR] when req url:', reqUrl, reqData);
res.status(500).send('error');
})
})
sreq.write(reqData);
sreq.end();
})
module.exports = router;
透传还是不透传
中间服务器在转发请求的时候,如果只是单纯的请求数据,那么透传是很好的方法,但是如果想做进一步的优化,可能就需要中间层做一定的处理。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。