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(/(\bdeflate\b)|(\bgzip\b)/)) {
                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(/(\r|\n)\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

 

  有興趣的可以一看。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM