0

I am working on node.js HTTPS requests. I have done enough research and checked all things as well but still I think I am missing something. What I want to achieve is, when someone sends HTTPS GET request to my URL, I have to send them one file in response so they can download. Everything works fine, I have implemented "FS", "HTTPS", and it writes in proper manner as well, but when I send bulk traffic on my URL after every 1000 requests on an average 40 requests gets failed.

I am not sure why is this happening, I have set this as well:

var https = require('https');
https.globalAgent.maxSockets = Infinity;

Can anyone please help me to understand and resolve issue? Thanks in advance!

Anvesh

EDIT: On request I am adding my code, I have removed few lines of code because of privacy.

var https = require('https');
https.globalAgent.maxSockets = Infinity;
//var port = process.env.port || 1337;
fileSystem = require('fs'),
fileToWriteLog = require('fs'),
    path = require('path');
var mime = require('mime');
var cors = require('cors');


var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

var express = require('express'),
    app = module.exports.app = express();

app.use(cors());


// Created to use HTTPS
var options = {
    pfx: fileSystem.readFileSync('Certificate/key.pfx'),
    passphrase: 'XXX'   
};

var server = https.createServer(options,app);
var io = require('socket.io').listen(server);  //pass a https.Server instance
var port = 8080;
console.log(process.env.PORT);
server.listen(port,"0.0.0.0", function () {
    console.log("Secure Express server listening on port " + port);
});  //listen on port 8082

// routing

app.get('/stream/:patam1/:param2', function (request, response) {    
        var patam1 = request.param("patam1");
        var param2 = decodeURIComponent(request.param("param2"));


                var filePath = "XXXX" + param2;
                console.log(filePath);

                var stat = fileSystem.statSync(filePath);
                response.writeHead(200, {
                    'Content-Type': 'XXX', 
                    'Content-Length': stat.size
                });

                var readStream = fileSystem.createReadStream(filePath);
                readStream.on('data', function (data) {                                 
                    var flushed = response.write(data);
                    // Pause the read stream when the write stream gets saturated
                    if (!flushed)
                        readStream.pause();
                });
                response.on('drain', function () {                                  
                    // Resume the read stream when the write stream gets hungry 
                    readStream.resume();
                });
                // readStream.on('end', function () {                        
                     // response.end();
                // });          

});
Anvesh
  • 309
  • 1
  • 8
  • 24
  • How do you send those 1000 requests? How does your server handle them? Please give us more information. – Lewis Dec 01 '16 at 10:14
  • I have prepared windows exe program which sends 1000 requests at a time and my server gets big hit with all those 1000 requests. But that's what we want, we want to handle millions of requests at a time. Server is relatively simple with HTTPS, fs modules. It serves HTTPS GET requests. – Anvesh Dec 01 '16 at 10:21
  • There's a couple queues between your windows app and your server that might not be behaving properly if they can't queue 1000 requests. We'll need to see more code to determine if it's your app or your testing method that is flawed. – Dave Briand Dec 01 '16 at 11:03
  • @David: I have added my code, please help. – Anvesh Dec 01 '16 at 11:30

1 Answers1

1

General issues

I don't know what OS does your server run on. If it's Linux or Windows then see those answers on what you can do to increase the possible number of concurrent connection:

The problem that you have here can be either with your client (that runs on Windows as you wrote in the comments) or the server (you didn't specify its OS). Also the problem can be either with your programs or with the OS, or even some other things in between your client and server, like a proxy etc.

You can use Apache ab command to test you server so that at least you can eliminate the possibility that your program that sends the request is at fault here. E.g.:

ab -n 10000 -c 1000 -k http://localhost:8080/

This will send 10000 requests total, 1000 at a time.

See this for more details on how to test the server with ab.

Blocking operations

Here's your problem:

var stat = fileSystem.statSync(filePath);

You will always get problems with concurrent connections when you use blocking (Sync) calls in your handlers. I don't know if that's the only problem or not but you need to use asynchronous calls only if you want your server to handle concurrent connections well. Use stat instead of statSync.

The Sync functions are good for one time jobs at the program startup, like you use the readFileSync to populate options, not to be run inside of event handlers.

See this answer for info on how to serve files from the disk without any synchronous calls in many different ways (with Express, without Express etc). See:

Streams

You don't need to use stat (or statSync) at all. You don't have to set the Content-length header because Node can use chunked encoding and you can just stream the data.

I would change this:

app.get('/stream/:patam1/:param2', function (request, response) {    
        var patam1 = request.param("patam1");
        var param2 = decodeURIComponent(request.param("param2"));

                var filePath = "XXXX" + param2;
                console.log(filePath);

                var stat = fileSystem.statSync(filePath);
                response.writeHead(200, {
                    'Content-Type': 'XXX', 
                    'Content-Length': stat.size
                });

                var readStream = fileSystem.createReadStream(filePath);
                readStream.on('data', function (data) {                                 
                    var flushed = response.write(data);
                    // Pause the read stream when the write stream gets saturated
                    if (!flushed)
                        readStream.pause();
                });
                response.on('drain', function () {                                  
                    // Resume the read stream when the write stream gets hungry 
                    readStream.resume();
                });
                // readStream.on('end', function () {                        
                     // response.end();
                // });          

});

To something like this:

app.get('/stream/:patam1/:param2', function (request, response) {    
        var patam1 = request.param("patam1");
        var param2 = decodeURIComponent(request.param("param2"));

                var filePath = "XXXX" + param2;
                console.log(filePath);

                response.set('Content-Type', 'XXX');

                var readStream = fileSystem.createReadStream(filePath);
                readStream.on('open', function () {
                    response.set('Content-Type', 'XXX');
                    readStream.pipe(response);
                });
                readStream.on('error', function () {
                    response.set('Content-Type', 'text/plain');
                    response.status(404).end('Not found');
                });    
});

See my node-static-http-servers project on GitHub and in particular the express example. It does more or less what you're doing here. It's explained here in more detail.

Path traversal

Another suggestion I would have would be to shield your server against path traversal exploits. So when you set the file name with:

var filePath = "XXXX" + param2;

It would be better to use the path module:

var filePath = path.join("XXXX", param2);

and then test if it is not outside of the directory that you want to serve, like:

if (filePath.indexOf('XXXX/') !== 0) {
    return response.status(403).end('Forbidden');
}

The / slash in indexOf is important.

Async stat example

Instead of this:

         var filePath = "XXXX" + param2;
            console.log(filePath);

            var stat = fileSystem.statSync(filePath);
            response.writeHead(200, {
                'Content-Type': 'XXX', 
                'Content-Length': stat.size
            });

            var readStream = fileSystem.createReadStream(filePath);
            readStream.on('data', function (data) {                                 
                var flushed = response.write(data);
                // Pause the read stream when the write stream gets saturated
                if (!flushed)
                    readStream.pause();
            });
            response.on('drain', function () {                                  
                // Resume the read stream when the write stream gets hungry 
                readStream.resume();
            });

You could write something like this:

        // check for path traversal as I described above:
        var filePath = path.join("XXXX", param2);
        if (filePath.indexOf('XXXX/') !== 0) {
            return response.status(403).end('Forbidden');
        }
        console.log(filePath);

        fileSystem.stat(filePath, function (err, stat) {

            if (err) {
                response.set('Content-Type', 'text/plain');
                return response.status(404).end('Not found');
            }

            var readStream = fileSystem.createReadStream(filePath);

            readStream.on('open', function () {
                response.set('Content-Type', 'XXX');
                response.set(Content-Length': stat.size);
                readStream.pipe(response);
            });
            readStream.on('error', function () {
                response.set('Content-Type', 'text/plain');
                response.status(404).end('Not found');
            });

        });
Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
  • My node.js server runs on windows OS. So as per your suggestion what will "stat" do please? Will it solve issues with concurrent connections? Thanks for the help! – Anvesh Dec 01 '16 at 11:43
  • @Anvesh You don't need to set the content-length so you don't need to use the `sync` at all. See my updated answer, I added some code examples. – rsp Dec 01 '16 at 11:52
  • No i need that on other app, so I have to pass size of file. – Anvesh Dec 01 '16 at 13:34
  • @Anvesh If you need content-length then you can use async version of stat. See my last update to my answer. Hope it helps. – rsp Dec 01 '16 at 14:01