javascript - PhantomJS:确保 response 对象在 server.listen(...) 中保持活跃
我正在使用 PhantomJS 的 server.listen(...)。我意识到它在很大程度上是实验性的,不应该在生产环境中使用。我使用它来建立一个简单的截图服务器,该服务器可以为一个 URL 生成截图;这是一个我用来玩转 PhantomJS 的小项目。我注意到一个特别与长时间运行的请求相关的问题,即 response 对象不可用。以下是我代码中相关的片段:
var service = server.listen(8080, function (request, response) {
response.statusCode = 200;
if (loglevel === level.VERBOSE) {
log(request);
} else {
console.log("Incoming request with querystring:", request.url);
}
var params = parseQueryString(request.url);
if (params[screenshotOptions.ACTION] === action.SCREENSHOT) {
getScreenshot(params, function (screenshot) {
response.headers["success"] = screenshot.success; //<-- here is where I get the error that response.headers is unavailable. Execution pretty much stops at that point for that particular request.
response.headers["message"] = screenshot.message;
if (screenshot.success) {
response.write(screenshot.base64);
} else {
response.write("<html><body>There were errors!<br /><br />");
response.write(screenshot.message.replace(/\n/g, "<br />"));
response.write("</body></html>");
}
response.close();
});
} else {
response.write("<html><body><h1>Welcome to the screenshot server!</h1></body></html>")
response.close();
}
});
getScreenshot 是一个异步方法,它使用 WebPage.open(...) 函数来打开网页;这个函数也是异步的。所以看起来发生的情况是,当作为参数传入 getScreenshot 的回调函数最终被调用时,response 对象似乎已经被删除。我最终基本上遇到了 PhantomJS 的以下错误:
Error: cannot access member `headers' of deleted QObject
我认为这是因为请求超时,因此连接被关闭。文档提到至少调用一次response.write("")以确保连接保持打开。我尝试在server.listen(...)开始时调用response.write(""),我甚至尝试了一个相当笨拙的解决方案,即使用setInterval(...)每500毫秒执行一次response.write("")(我甚至将间隔降低到最少50毫秒)。我还确保在完成后清除了该间隔。然而,我似乎仍然遇到这个问题。
这是我必须一直忍受,直到他们让网页服务器模块更强大吗?还是有办法绕过它?
答案#1
我能够弄清楚这个问题。看起来在使用 WebPage.open 加载某些页面时(例如 http://fark.com 和 http://cnn.com),会触发多次 onLoadFinished 事件。这导致 WebPage.open 中的回调被多次调用。所以发生的情况是,当控制权返回到调用函数时,我已经关闭了响应,因此 response 对象不再有效。我通过在调用 WebPage.open 函数之前创建一个标志来解决了这个问题。在回调函数中,我会检查该标志的状态,以查看我是否已经遇到过先前的 onLoadFinished 事件。一旦我完成了在 WebPage.open 回调中需要做的事情,我会更新标志以显示我已完成处理。这样一来,虚假的(至少在我的代码上下文中) onLoadFinished 事件就不再被处理了。
答案#2
(请注意,以下内容指的是 PhantomJS 1.9.7,而原帖作者可能指的是 1.6.1 或更早版本。)
如果触发了多个 onLoadFinished 事件,你可以使用 page.open() 来代替自己监听 onLoadFinished。使用 page.open() 会将你的处理器包装在一个私有处理器中,以确保你的回调函数只被调用一次。
来自来源:
definePageSignalHandler(page, handlers, "_onPageOpenFinished", "loadFinished");
page.open = function (url, arg1, arg2, arg3, arg4) {
var thisPage = this;
if (arguments.length === 1) {
this.openUrl(url, 'get', this.settings);
return;
}
else if (arguments.length === 2 && typeof arg1 === 'function') {
this._onPageOpenFinished = function() {
thisPage._onPageOpenFinished = null;
arg1.apply(thisPage, arguments);
}
this.openUrl(url, 'get', this.settings);
return;
}
// ... Truncated for brevity
此功能与其他答案完全相同,作为官方 API 的一部分公开。
