Потоки в NODE JS

Два основных вида потоков

  var fs=require('fs');
  
  //fs.ReadStream наследует от stream.Readeable
  var stream = new fs.ReadStream(_filename);//вычитать текущий файл
  stream.on('readable',function(){ 
	var data=stream.read();
	console.log(data);
  });
  
  stream.on('end',function(){
	console.log('THE END')
  });
  

А теперь вычитаем большой файл:

  var fs=require('fs');
  
  //fs.ReadStream наследует от stream.Readeable
  var stream = new fs.ReadStream("big.html");//вычитать big.html файл
  stream.on('readable',function(){ 
	var data=stream.read();
	console.log(data);
  });
  
  stream.on('end',function(){
	console.log('THE END')
  });
  

Проверим длину частей потоков(она ровна 64kb)

  ....
  stream.on('readable',function(){ 
	var data=stream.read();
	console.log(data.length);//длина частей потока
  });
  ....
  

Специальные события модуля "fs": "open" ---> "close"

Если файл не дочитан, например вызвали метод "destroy()" событие "end" не сработает , но гарантировано сработает событие "close".

Error

Если мы не хотим "положить" сервер при возникноавении ошибки нужно обрабатывать событие 'error'

  ....
  stream.on('error',function(err){ 
	if(err.code=='ENOENT')
	console.log('file not found');
  }else{
	console.error(err)
  }
  );
 
  

Writable-запись

Если посетитель запросит следующий url то отдать ему файл, пример решения этой задачи без потоков будет таким:---> читаем фaйл ---> когда файл прочитается вызываем callback ---> при ошибке сообщаем о ней ---> все хорошо устанавливаем заголовок (mime) и записиваем res.end() отдаем контент

  var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
 //res instansceof http.ServerResponse (наследует от)-stream.Writable
 
 if(req.url == '/big.html'){
	fs.readFile('big.html', function(err,content){
		if(err){
			res.statusCode = 500;
			res.end('Server error');
		}else{
			res.setHeader('Content-Type','text/html'; 'charset=utf-8');
			res.end(content)
		}
	});
 }
 }).listen(3000);
  

Это решение работает но его проблема пожирание памяти,если файл большой , а "клиент" медленный то весь сонтент зависнет в памяти, а если клиентов много... сервер займет всю доступную память , что не допустимо!

Что бы этого не происходило заменим отдачу ответа(файла) на другой код отдачи файла использующий потоки.

  var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
	if(req.url == '/big.html'){ 
		var file=new fs.ReadStream('big.html');//это входной поток данных
	
	}
 
 }).listen(3000)
  

Алгоритм потоков записи:

Реализем правильную отдачу файла:

   var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
	if(req.url == '/big.html'){ 
		var file=new fs.ReadStream('big.html');//это входной поток данных
		sendFile(file,res);
	}
 
 }).listen(3000)
  
  function sendFile(file,res){
	file.on('readable',write);
	
	function write(){
		var fileContent=file.read();//считать
		if(fileContent && !res.write(fileContent)){//отправить
		
		file.removeListener('readable',write);
		res.once('drain',function(){//подождать
			file.on('readable',write);
			write();
		});
		}
	}
  }
  file.on('end',function(){
	res.end();
  })
  
  

Получившийся код достаточно универсальный он реализует алгоритм отправки данных из одного потока в другой(sendFile(file,res)), используя стандартние методы Readable & Writable.

Такой метод передачи данных существует в библиотеке и называется file.pipe(res)

     var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
	if(req.url == '/big.html'){ 
		var file=new fs.ReadStream('big.html');//это входной поток данных
		sendFile(file,res);
	}
 
 }).listen(3000);
 
 function sendFile(file,res){
	file.pipe(res)// "Readable".pipe("Writable")
	//при помощи метода pipe() можно писать один входной поток в несколько выходных
		file.pipe(process.stdout)// кроме ответа клиенту вывод производим в стандартый вывод процеса
 }
   
  

В даном коде нет обработчика ошибок и если произойдет ошиба упадет весь сервер!!

   var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
	if(req.url == '/big.html'){ 
		var file=new fs.ReadStream('big.html');//это входной поток данных
		sendFile(file,res);
	}
 
 }).listen(3000);
 
 function sendFile(file,res){
	file.pipe(res);
	
	file.on('error',function(){
		res.statusCode=500;
		res.end('Server Errror');
		console.log(err)
	});
	
 }
  

В ряде руководств такой код является окончательным но ставиь его на реальный сервер ни в коем случае нельзя.

Если клиент открыл соединение но закрыл его до того как закончилась запись (загрузка файла била завершена), топ получается, что файл останется подвисшим и все ассоциируемые с ним структуры останутся в памяти, также у операционных систем существует лимит на количество открытых файлов, а в третих вместе с файлом навечно зависает в памяти исоответствующий обьект потока и все замыкание в котором он находится.

Что бы это исправить нам нужно отслеживать событие "res.on('close')"

    var http=require('http');
  var fs=require('fs');
 
 new http.Server(function(req,res){
	if(req.url == '/big.html'){ 
		var file=new fs.ReadStream('big.html');//это входной поток данных
		sendFile(file,res);
	}
 
 }).listen(3000);
 
 function sendFile(file,res){
	file.pipe(res);
	
	file.on('error',function(){
		res.statusCode=500;
		res.end('Server Errror');
		console.log(err)
	});
	/*
	file.on('open',function(){
			console.log('open');
	}).on('close',function(){
			console.log('close');
	});
	*/
	res.on('close',function(){// сигнал что соединение было оборвано
		file.destroy();// вызываем метод- прерываем поток, соответсвенно файл будет закрыт 
	});
		
 }
   

Теперь наш код можно пускать на жывой сервер!!!