PhantomJS是否在多个请求之间共享内存?

时间:2015-03-03 00:57:58

标签: d3.js phantomjs

我使用PhantomJS + d3呈现美国邮政编码的地图作为后端流程。渲染和邮政编码计数需要足够长的时间,将html和d3 js放在浏览器中需要一分钟才能加载并导致其他问题,因此我们将其移至后端。

如果我通过curl向PhantomJS启动的节点服务器发送一个请求,没问题。如果我将多个地图请求间隔大约15秒,也没问题。但是,如果我非常快速地发起一些卷曲请求,渲染的图像看起来会相同(也就是说同一个图像被写入多个文件。)这是幻像脚本:

var port,         服务器,         服务,         页,         URL,         svgDrawer;

fs     = require('fs');
port   = 9494;
server = require('webserver').create();
page   = require('webpage').create();

service = server.listen(port, function (request, response) {
    var drawerPayload = null;
    try{
        drawerPayload=JSON.parse(request.post);
    } catch(e){
        response.statusCode = 417;
        response.write("Error : Invalid Input JSON");
        response.close();
        return;
    }

    url = 'file:///' + fs.absolute('./'+drawerPayload.inFile);
    page.open(url, function (status) {
        if(status=="success"){
            page.evaluate(function(data){
                $("body").on( "click", data, chartBuilder );
                $("body").click();

                var maxtimeOutMillis = 15000,
                    start = new Date().getTime(),
                    condition = false,
                    interval = setInterval(function() {
                        if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
                            condition = $("svg.chart").hasClass("done"); //< defensive code
                        } else {
                            if(!condition) {
                                clearInterval(interval)
                            } else {
                                page.render(drawerPayload.outFile);
                                clearInterval(interval); //< Stop this interval
                            }
                        }
                    }, 250); //< repeat check every 250ms
            });

            response.statusCode = 200;
        } else {
            response.statusCode = 404;
            response.write("Not Found"+url);
        }
        response.close();
        return;
    });

    page.onError = function (msg, trace) {
        console.log(msg);
        trace.forEach(function(item) {
            console.log('  ', item.file, ':', item.line);
        })
        response.statusCode = 417;
        response.write("Error : "+msg);
        response.close();
        return;
    }
});

并且html + d3看起来像这样:

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.zip {
    stroke: none;
}
.chart {
    fill: white;
    width: 1000px;
    height: 500px;
}
</style>
<body>
    <div id="chart-container">
        <svg class="chart"></svg>
    </div>
</body>

<script src="./jquery-min.js"></script>
<script src="./d3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script>
<script>

function chartBuilder(e){
    var zip_data = e.data;
    $.getJSON("zips_us_topo.json", function(us){
        console.log("rendering...\n");
        var width = 1000;
        var height = 500;
        var projection = d3.geo.albersUsa()
            .scale(width)
            .translate([width / 2, height / 2]);

        var path = d3.geo.path().projection(projection);
        var color = d3.scale.log().domain([1,zip_data.max+1]).range(["#cccccc","#f63337"]);

        var svg = d3.select("svg.chart")
            .attr("width", width)
            .attr("height", height)
            .style({margin: "10px 100px"})
            .append("g")
            .attr("class", "counties")
            .selectAll("path")
            .data(topojson.feature(us, us.objects.zip_codes_for_the_usa).features)
            .enter()
                .append("path")
                    .attr("class", "zip")
                    .style({fill: function(d){
                        return color(zip_data.counts[d.properties.zip] ? zip_data.counts[d.properties.zip]+1 : 1);
                    }})
                    .attr("d", path);
        svg.classed("done", true);
    });
}
</script>

如果请求同时全部卷曲,看起来它会将一个图像写入所有输出文件。 PhantomJS是为每个请求创建一个新页面,还是每次都加载相同的请求?

1 个答案:

答案 0 :(得分:2)

对于所有来的请求,您只有一个page个实例。当新请求进入并劫持当前page.open()请求时,这可能会创建一些竞争条件。根据您的首选方案,基本上有两种方法可以解决这个问题。

多个&#34;标签&#34;

简单的解决方法是为每个请求创建一个新的page实例,它们在同一个浏览器中基本上是不同的选项卡。因此,如果cookie或localStorage存在问题,则不适合您。

page = require('webpage').create();回调中移动server.listen,并且在使用后不要忘记close() page个实例。

一次只有一个请求

由于这是一个不太短的运行过程,您可以在当前未运行时启动page.open()并将所有传入请求放入队列,只要page.open()尚未完成。完成后,保存响应,通过请求队列并对所有响应做出相同的响应。

如果确实存在许多并发请求,那么内存消耗当然比第一个解决方案好得多。


您的代码还有其他问题。您在setInterval()内使用page.evaluate(),这会中断控制流程。在呈现页面之前,很可能会设置response.statusCode = 200;

page.render()内的page.evaluate()是另一个问题。 page.evaluate()是沙盒页面上下文。它无法访问在其外部定义的变量,包括pagerequire。 (Solution针对这个孤立的问题)

通过在页面上下文之外等待其内部的渲染条件,可以一次性解决这两个问题。我建议使用waitFor from the examples

if(status=="success"){
    page.evaluate(function(data){
        window._finishIndicationVariable = false;
        $("body").on( "click", data, chartBuilder );
        $("body").click();

        var maxtimeOutMillis = 15000,
            start = new Date().getTime(),
            condition = false,
            interval = setInterval(function() {
                if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
                    condition = $("svg.chart").hasClass("done"); //< defensive code
                } else {
                    if(!condition) {
                        clearInterval(interval)
                    } else {
                        window._finishIndicationVariable = true;
                        clearInterval(interval); //< Stop this interval
                    }
                }
            }, 250); //< repeat check every 250ms
    });
    waitFor(function check(){
        return page.evaluate(function(){
            return window._finishIndicationVariable;
        });
    }, function onReady(){
        page.render(drawerPayload.outFile);
        response.statusCode = 200;
        response.close();
    });
} else {
    response.statusCode = 404;
    response.write("Not Found"+url);
    response.close();
}

请注意,response.close();使用了两次,因为其中一个if分支是异步的而一个不是。