我使用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是为每个请求创建一个新页面,还是每次都加载相同的请求?
答案 0 :(得分:2)
对于所有来的请求,您只有一个page
个实例。当新请求进入并劫持当前page.open()
请求时,这可能会创建一些竞争条件。根据您的首选方案,基本上有两种方法可以解决这个问题。
简单的解决方法是为每个请求创建一个新的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()
是沙盒页面上下文。它无法访问在其外部定义的变量,包括page
和require
。 (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分支是异步的而一个不是。