node.jshttpeventsexpressswagger-tools

Terminate outgoing http requests when incoming client http request is terminated


Overview of app

I have a node.js server application implemented with the express.js 4 module and the node.js core http module. At a high level, the app takes incoming client http messages, makes various http calls (using http module) to other external APIs, and lastly sends back a response to the client based on the responses from the aforementioned various external http API responses.

The Issue

My issue is that when the incoming client http request is terminated by the client (e.g. when the client wants to cancel their request), my node.js app continues to proceed in making the aforementioned various external http API calls. I cannot seem to find a way to signal to the rest of my node.js app to terminate its various outgoing http requests to external APIs in such cases.

When the client terminates their request, the express app (i.e. the express http server) receives a "close" event, which I'm listening for. The "close" event listener in my code catches this event; however, I cannot seem to figure out how to then signal to the "downstream" or "subsequent" http requests made by my code to terminate.

My Goal

How can I signal to all the outgoing http requests to external APIs which are associated with a single client incoming request to terminate when the client terminates their incoming request to my service?

I've provided a simplified version of my node.js app below with some inline code comments to help illustrate my issue more clearly. Any help or insight would be very much appreciated. Thanks!

Additional Info

I'm using the Apigee swagger-tools middleware to do my api routing.

I've found a few answered questions out there which are similar but not quite directly applicable to my question:

Handling cancelled request with Express/Node.js and Angular

How to detect user cancels request

Best,

Chris

test-app.js

// test-app.js
"use strict";

var swaggerTools = require("swagger-tools");
var app = require("express")();

// swaggerRouter configuration
// sends incoming http messages to test-controller.js
var options = {
    controllers: './controllers'
};

// The Swagger document (require it, build it programmatically, fetch it from a URL, ...)
// describes the API specification
var apiSpec = require('./test-swagger.json');

// Initialize the Swagger middleware
swaggerTools.initializeMiddleware(apiSpec, function (middleware) {
    "use strict"

    // Interpret Swagger resources and attach metadata to request - must be first in swagger-tools middleware chain
    app.use(middleware.swaggerMetadata());

    // Validate Swagger requests/responses based on test-swagger.json API specification
    app.use(middleware.swaggerValidator());

    // Route validated requests to appropriate controller, test-controller.js
    app.use(middleware.swaggerRouter(options));
});

// Run http server on port 8080
app.listen(8080, function () {
    "use strict";
    console.log("Server running on port %d", this.address().port);
})
    .on("connection", function (socket) {
        console.log("a new connection was made by an incoming client request.");
        socket.on("close", function () {
            console.log("socket connection was closed by client");
            // somehow signal to the rest of my node.js app to terminate any
            // http requests being made to external APIs, e.g. twitter api
            socket.destroy();
        });
    })

test-controller.js

//test-controller.js
"use strict";
var http = require("https");

// only one function currently, consequently, all incoming http requests are
// routed to this function, i.e. "compile"
module.exports = {
    compile: compile
};

function compile(req, res, next) {
    var options = {
        "method": "GET",
        "hostname": "api.twitter.com",
        "path": "/1.1/statuses/mentions_timeline.json?count=2&since_id=14927799",
        "headers": {"accept": "application/json"}
    };
    // how can i terminate this request when the http.server in test-app.js receives the "close" event?
    http.request(options)
        .on("response", function(response) {
            var apiResponse = [];
            response.on("data", function (chunk) {
                apiResponse.push(chunk);
            });
            response.on("end", function () {
                apiResponse = Buffer.concat(apiResponse).toString();
                res.status(response.statusCode).set(response.headers).send(apiResponse);
            });
        })
}

Solution

  • In your test controller's compile method you should just be able to do something like this:

    var request = http.request(options, function (response) {
      res.writeHead(response.statusCode, response.headers)
      response.pipe(res, { end: true })
    })
    req.on('close', function(){
      request.abort()
    })