11

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;

透传还是不透传

中间服务器在转发请求的时候,如果只是单纯的请求数据,那么透传是很好的方法,但是如果想做进一步的优化,可能就需要中间层做一定的处理。

其他资源


chenjsh36
737 声望44 粉丝