Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js - Check if stream has error before piping response

In Node.js, say that I want to read a file from somewhere and stream the response (e.g., from the filesystem using fs.createReadStream()).

application.get('/files/:id', function (request, response) {
    var readStream = fs.createReadStream('/saved-files/' + request.params.id);
    var mimeType = getMimeTypeSomehow(request.params.id);
    if (mimeType === 'application/pdf') {
        response.set('Content-Range', ...);
        response.status(206);
    } else {
        response.status(200);
    }
    readStream.pipe(response);
});

However, I want to detect if there is an error with the stream before sending my response headers. How do I do that?

Pseudocode:

application.get('/files/:id', function (request, response) {
    var readStream = fs.createReadStream('/saved-files/' + request.params.id);
    readStream.on('ready', function () {
        var mimeType = getMimeTypeSomehow(request.params.id);
        if (mimeType === 'application/pdf') {
            response.set('Content-Range', ...);
            response.status(206);
        } else {
            response.status(200);
        }
        readStream.pipe(response);
    });
    readStream.on('error', function () {
        response.status(404).end();
    });
});
like image 566
Jackson Avatar asked Aug 05 '15 23:08

Jackson


1 Answers

Write stream is ended when readStream ends or has an error. You can prevent this default behaviour by passing end:false during pipe and end the write stream manually.

So even if the error occurs, your write stream is still open and you can do other stuff(e.g. sending 404 status) with writestream in the error callback.

var readStream = fs.createReadStream('/saved-files/' + request.params.id);
readStream.on('error', function () {
    res.status(404).end();
});
readStream.on('end', function(){
  res.end(); //end write stream manually when readstream ends
})
readStream.pipe(res,{end:false}); // prevent default behaviour

Update 1: For file streams, you can listen for open event to check if the file is ready to read:

readStream.on('open', function () {
    // set response headers and status
});

Update 2: As OP mentioned there may be no open event for other streams, we may use the following if the stream is inherited from node's stream module. The trick is we write the data manually instead of pipe() method. That way we can do some 'initialization' on writable before starting to write first byte.

So we bind once('data') first and then bind on('data'). First one will be called before actual writing is happened.

readStream
.on('error',function(err) {
  res.status(404).end();
})
.once('data',function(){ 
  //will be called once and before the on('data') callback
  //so it's safe to set headers here
  res.set('Content-Type', 'text/html');
})
.on('data', function(chunk){ 
  //now start writing data 
  res.write(chunk);
})
.on('end',res.end.bind(res)); //ending writable when readable ends
like image 186
hassansin Avatar answered Sep 21 '22 17:09

hassansin