zoukankan      html  css  js  c++  java
  • nodejs学习之实现http数据转发

      此前在做项目的时候,一直用json文件用作模拟数据,后来发现了mock.js,于是就用了mock.js,再后来感觉这些数据再怎么模拟都是静态数据。所以就想用nodejs实现一个数据转发功能,在本地拉取服务端的数据。那时就简易做出了一个针对那个项目的数据拉取功能。而在最近,在看一些博客的时候,想把几个博客的页面内容全部拉取到一个页面来看。所以就把此前数据拉取功能稍作改造封装了一下。

      做出的一个简易数据拉取demo:点我看效果

      

      然后大概简述一下demo的实现。当作学习记录。

      首先是数据转发模块,我将其封装了一下,封装成了transdata.js

    "use strict";
    
    var http = require("http");
    var stream = require("stream");
    var url = require("url");
    var zlib = require("zlib");
    
    var noop = function () {};
    
    //两种请求
    var transdata = {
        post: function (opt) {
            opt.method = "post";
            main(opt);
        },
    
        get: function (opt) {
            if (arguments.length >= 2 && (typeof arguments[0] == "string") && (typeof arguments[1] == "function")) {
                opt = {
                    url: arguments[0],
                    success: arguments[1]
                };
    
                if (arguments[2] && (typeof arguments[2] == "function")) {
                    opt.error = arguments[2];
                }
            }
    
            opt.method = "get";
            main(opt);
        }
    };

      先是头部这段代码,就是简单的做了一点封装,封装成了两个方法,一个是get,一个是post,但是其实两个最终调用的都是main方法。其中,opt则是要传入的参数。参数包括了url、请求对象,响应对象等。

      main方法如下

    //转发请求主要逻辑
    function main(opt) {
        var options, creq;
    
    //    res可以为response对象,也可以为一个可写流,success和error为请求成功或失败后的回调
        opt.res = ((opt.res instanceof http.ServerResponse) || (opt.res instanceof stream.Writable)) ? opt.res : null;
        opt.success = (typeof opt.success == "function") ? opt.success : noop;
        opt.error = (typeof opt.error == "function") ? opt.error : noop;
    
        try {
            opt.url = (typeof opt.url == "string") ? url.parse(opt.url) : null;
        } catch (e) {
            opt.url = null;
        }
    
        if (!opt.url) {
            opt.error(new Error("url is illegal"));
            return;
        }
    
        options = {
            hostname: opt.url.hostname,
            port: opt.url.port,
            path: opt.url.pathname,
            method: opt.method,
            headers: {
                'Accept-Encoding': 'gzip, deflate',
                'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,zh-TW;q=0.2',
                'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.37 Safari/537.36'
            }
        };
    
    //  如果req为可读流则使用pipe连接,传输数据,如果不是则直接写出字符串
        if (opt.method == 'post') {
            if (opt.req instanceof stream.Readable) {
                if(opt.req instanceof http.IncomingMessage){
                    options.headers["Content-Type"] = opt.req.headers["content-type"];
                    options.headers["Content-Length"] = opt.req.headers["content-length"];
                }
                process.nextTick(function () {
                    opt.req.pipe(creq);
                })
            } else {
                var str = ((typeof opt.req) == "string") ? opt.req : "";
    
                process.nextTick(function () {
                    creq.end(str);
                })
            }
        } else {
            process.nextTick(function () {
                creq.end();
            })
        }
    
        creq = http.request(options, function (res) {
            reqCallback(opt.res, res, opt.success)
        }).on('error', function (e) {
            opt.error(e);
        });
    }

      首先将opt参数进行一些错误处理。其中res可以为响应对象,也可以为一个可写流。而success和error就是请求成功和失败后的回调,同时再将url转成对象方面后面使用。因为要在后台发起一个请求,所以请求的参数options是必须的啦。

      写好options后再判断要转发的请求是post还是get,如果是post而且传入的参数req是一个请求头或者可读流,则直接使用pipe连接res,进行数据传输。如果req是string,则直接写入。发起请求获得响应后则调用reqCallback方法,对数据进行处理。

      reqCallback方法如下:

    //请求成功后的回调
    function reqCallback(ores, res, callback) {
        if (ores) {
            ores.on('finish', function () {
                callback();
            });
    
            if (ores instanceof http.ServerResponse) {
                var options = {};
    
                //复制响应头信息
                if (res.headers) {
                    for (var k in res.headers) {
                        options[k] = res.headers[k];
                    }
                }
    
                ores.writeHead(200, options);
            }
    
            res.pipe(ores);
        } else {
            var size = 0;
            var chunks = [];
    
            res.on('data', function (chunk) {
                size += chunk.length;
                chunks.push(chunk);
            }).on('end', function () {
                var buffer = Buffer.concat(chunks, size);
    
                //如果数据用gzip或者deflate压缩,则用zlib进行解压缩
                if (res.headers && res.headers['content-encoding'] && res.headers['content-encoding'].match(/(deflate)|(gzip)/)) {
                    zlib.unzip(buffer, function (err, buffer) {
                        if (!err) {
                            callback(buffer.toString())
                        } else {
                            console.log(err);
                            callback("");
                        }
                    });
                } else {
                    callback(buffer.toString())
                }
            })
        }
    }

      对数据的处理比较简单,如果res是响应对象,则直接通过pipe连接,如果不是,则获取到数据,如果数据用gzip压缩了,则用zlib进行解压,然后放在回调中即可。

      transdata的调用比较简单,像get直接:

    transdata.get(url , function(result){})

      而我项目中用到的数据转发的是用到post请求,也很简单,直接:

    var transdata = require("transdata");
    var http = require("http");
    http.createServer(function(req , res){
        transdata.post({
            req:req,
            url:'http://XXX/XX:9000/getdata',
            res:res,
            success:function(){
                console.log("success");
            },
            error:function(e){
                console.log("error");
            }
        });
    })

      transdata写完,再回到上面那个demo的实现上来,既然有了transdata,获取数据就很容易了。代码如下:

    var creeper = function(req , res , urlObj){
        var header = fs.readFileSync(baseDir + "header.ejs").toString();
        var contents = fs.readFileSync(baseDir + "contents.ejs").toString();
        var foot = fs.readFileSync(baseDir + "foot.ejs").toString();
    
        res.writeHead(200 , {'content-type':'text/html;charset=utf-8'});
        res.write(ejs.render(header , {data:ids}));
    
        console.log("开始采集数据...");
    
        var count = 0;
        for(var i=0;i<ids.length;i++){
            (function(index){
                var id = ids[index];
                var nowSource = source[id];
                transdata.get(nowSource.url , function(result){
                    count++;
                    console.log(">【"+id+ "】get√");
    
                    var $ = cheerio.load(result);
                    var $colum = $(nowSource.colum);
    
                    result = [];
                    $colum.each(function(){
                        result.push(nowSource.handle($(this)))
                    });
                    if(typeof +nowSource.max == "number"){result = result.slice(0 , nowSource.max)}
    
                    if(result.length){
                        var data = {};
                        data[id] = result;
                        result.index = index;
    
                        var html = ejs.render(contents , {data:data});
                        html = html.replace(/(
    |
    )s*/g , '').replace(/'/g , "\'");
                        res.write("<script>loadHtml("+index+" , 'dom_"+index+"' , '"+html+"')</script>");
                    }
    
                    if(count == ids.length){
                        console.log("数据采集完成..");
                        res.end(foot);
                    }
                })
            }(i))
        }
    };

      获取到数据,数据为html信息,而处理html信息的工具就是cheerio,用法跟jquery的选择器一样,就用cheerio对数据进行操作并且获取自己需要的数据,这些就不进行赘述。相对比较简单。

      整个项目源代码的github地址附上:

      https://github.com/whxaxes/node-test/tree/master/server/creeper

      同时附上transdata.js的github地址:

      https://github.com/whxaxes/transdata

      有兴趣的可以一看。

  • 相关阅读:
    ubuntu Server 16.04 LTS 安装odoo
    linux常用命令大全
    sql 百万级数据库优化方案
    FreeSpire.XLS的使用
    备份集中的数据库与现有的数据库不同解决方案
    图片延迟加载的实现
    亚马逊菜单应用例子
    提取吗
    linux内核学习网站
    phpexcel1
  • 原文地址:https://www.cnblogs.com/axes/p/4466496.html
Copyright © 2011-2022 走看看