Reputation: 95518
I'm using server.listen(...)
from PhantomJS. I realize that it is largely experimental and that it shouldn't be used in production. I'm using it for a simple screenshot-server that accepts generates screenshots for a URL; it's a toy project that I'm using to play around with PhantomJS. I've noticed an issue with long-running requests in particular, where the response
object is unavailable. Here are the relevant snippets from my code:
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
is an asynchronous method that uses the WebPage.open(...)
function to open a webpage; this function is also asynchronous. So what seems to be happening is that when the callback that is passed in as an argument to getScreenshot
is finally called, it appears that the response
object has already been deleted. I basically end up with the following error from PhantomJS:
Error: cannot access member `headers' of deleted QObject
I believe this is because the request times out and so the connection is closed. The documentation mentions calling response.write("")
at least once to ensure that the connection stays open. I tried calling response.write("")
at the beginning of server.listen(...)
and I even tried a pretty hacky solution where I used setInterval(...)
to perform a response.write("")
every 500 milliseconds (I even lowered it down to as little as 50). I also made sure to clear the interval once I was done. However, I still seem to get this issue.
Is this something that I'm just going to have to deal with until they make the webserver module more robust? Or is there a way around it?
Upvotes: 6
Views: 3369
Reputation: 13565
(Note that the following refers to PhantomJS 1.9.7 while the OP was likely referring to 1.6.1 or older.)
In the event that multiple onLoadFinished
events are being fired, you can use page.open()
instead of listening for onLoadFinished
yourself. Using page.open()
will wrap your handler in a private handler to ensure that your callback is only called once.
From the source:
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
This functionality is exactly the same as the other answer, exposed as part of the official API.
Upvotes: 0
Reputation: 95518
I was able to figure this out. It appears that while loading certain pages with WebPage.open
(for example http://fark.com
and http://cnn.com
) multiple onLoadFinished
events are fired. This results in the callback in WebPage.open
being called multiple times. So what happens is that when control comes back to the calling function, I've already closed the response and so the response
object is no-longer valid. I fixed this by using creating a flag before the WebPage.open
function is called. Inside the callback, I check the status of the flag to see if I've already encountered a previous onLoadFinished
event. Once I am with whatever I have to do inside the WebPage.open
callback, I update the flag to show that I've finished processing. This way spurious (at least in the context of my code) onLoadFinished
events are no-longer serviced.
Upvotes: 8