Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PhantomJS: Ensuring that the response object stays alive in server.listen(...)

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?

like image 393
Vivin Paliath Avatar asked Jul 22 '12 05:07

Vivin Paliath


1 Answers

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.

like image 181
Vivin Paliath Avatar answered Oct 16 '22 14:10

Vivin Paliath