La ventaja de los streams es que al leer por partes nuestra información usa eficientemente la memoria y toma menos tiempo en procesar la información. Sino usáramos streams tendríamos que esperar a tener toda la información en memoria para luego procesarla.
Que es un stream?
Los streams son el concepto más importante junto con los eventos (un stream es una instancia de un event emitter, los streams (flujo) son información en forma de string o buffer que transitan de un programa o archivo a otro en pequeñas partes usualmente en forma binaria hasta completar toda la información. Este concepto nace en Linux donde la información se origina en alguna parte (teclado, archivo, disco) y fluye hacia otro lugar donde se puede transformar (pantalla, disco, archivo) y todo esto se logra gracias a un estándar llamado stdio
(standard input/output).
stdin
podemos leer información desde nuestra entrada estándar (teclado) con scanf
o escribir en nuestra salida estándar (pantalla) con prinf
La ventaja de los streams es que al leer por partes nuestra información usa eficientemente la memoria y toma menos tiempo en procesar la información. Sino usáramos streams tendríamos que esperar a tener toda la información en memoria para luego procesarla.
tipos de streams
En nodeJs existen cuatro tipo de streams:
- Writable: solo podemos escribir en ellos
- Readable: solo podemos leer de ellos
- Duplex: podemos leer y escribir
- Transform: es un duplex donde a medida que pasa la información la podemos transformar
Estos son algunos ejemplos muy comunes en NodeJs de streams:
- Un
console.log()
es una instacia que puede escribir enprocess.stdout
yprocess.stderr
los cuales son streams duplex, osea que pueden leer un mensaje que viene de console.log y escribirlo en nuestra terminal.
// aparece el mensaje en la terminal
console.log("hello world");
process.stdin
es un stream duplex al igual queprocess.stdout
y podemos pasar información desde nuestra terminal o desde un archivo para que se escriban en stdout por medio del métodopipe
el cual es una tubería que une nuestros dos streams para que transiten porciones de información.
// stdio.js
process.stdin.pipe(process.stdout)
> node stdio.js
// or
// cat file.txt | node stdio.js
pipe
o escuchamos eventos y cuando implementamos streams requerimos el modulo stream
- En el siguiente ejemplo podemos ver como por medio de los eventos del
request
, podemos obtener el stream de información endata
, el cual es un buffer y finalmente procesarlo en el eventoend
, donde ya tenemos todo nuestro buffer completo.
const server = http.createServer((req, res) => {
const { headers, method, url } = req;
let body = [];
let status = 200;
// event Emitter
req.on('error', (err) => {
console.error(err);
}).on('data', (chunk) => {
//stream - https://nodejs.org/api/net.html#net_event_data
body.push(chunk);
}).on('end', () => {
body = Buffer.concat(body).toString();
if (url === "/" && method === "POST") {
status = 200;
} else {
const err = new Error("Not found");
body = err.message;
status = 404;
}
res.statusCode = status;
res.setHeader("Content-Type", headers["content-type"]);
res.end(body);
});
});
Creando un Readable stream
En un readable stream generamos datos o los leemos, para ello debemos primero usar la interfaz que nos provee Nodejs llamada Readable
, donde como mínimo debemos definir el método read
, el cual puede ser vacío o tener alguna lógica.
// Leemos pedazos de información que se almacena en un buffer
INPUT --chunk--> |* |*| Readable | *| * | ---> pipe ---> OUTPUT
// readable.js
const Stream = require('stream')
// definimos nuestro Readable con todo por defecto
const readableStream = new Stream.Readable({
read(size) {}
})
// escribimos en nuestro Readable por medio de push
readableStream.push("Hello world!");
// pasamos nuestro buffer a la terminal
readableStream.pipe(process.stdout)
createReadStream es un Readable del filesystem, con el cual podemos leer de un archivo
// readableExample.js
var fs = require('fs');
var readStream = fs.createReadStream('./output');
readStream.pipe(process.stdout);
Creando un Writable stream
Los Writable
streams son nuestro destino donde queremos escribir ya sea la terminal o un archivo.
// La información del buffer la escribimos por pedazos
INPUT ---> pipe ---> |* |*| Writable | *| * | --chunk--> OUTPUT
// writable.js
const Stream = require('stream')
// definimos nuestro Writable con todo por defecto
const writableStream = new Stream.Writable({
write(chunk, encoding, next) {
// por defecto escribimos en la terminal
console.log(chunk.toString())
next()
}
})
process.stdin.pipe(writableStream)
createWriteStream es un Writable del filesystem, con el cual podemos escribir en un archivo.
// writableExample.js
var fs = require('fs');
var writeStream = fs.createWriteStream('./output');
process.stdin.pipe(writeStream)
Creando un Duplex stream
un Duplex
stream es la forma de combinar un readable y un writable para que trabajen eficientemente, si nos fijamos bien solo vamos a tener un buffer con duplex mientras que readable y writable tiene su propio buffer osea dos.
// leemos y escribimos pedazos de información
INPUT --chunk--> |* |*| duplex | *| * | --chunk--> OUTPUT
//duplex.js
const { Duplex } = require('stream');
const duplexStream = new Duplex({
write(chunk, encoding, callback) {
console.log(chunk.toString());
callback();
},
read(size) {
}
});
duplexStream.push("Hello world!");
duplexStream.pipe(process.stdout);
Crypto es un duplex stream de nodejs con el cual podemos encriptar, esto significa que también es un Transform stream.
// duplexExample.js
const crypto = require('crypto');
const algorithm = 'aes-192-cbc';
const password = 'Password used to generate key';
// Use the async `crypto.scrypt()` instead.
const key = crypto.scryptSync(password, 'salt', 24);
// Use `crypto.randomBytes()` to generate a random iv instead of the static iv
// shown here.
const iv = Buffer.alloc(16, 0); // Initialization vector.
const cipher = crypto.createCipheriv(algorithm, key, iv);
process.stdin.pipe(cipher).pipe(process.stdout);
echo "hello world" | node duplexExample.js
Creando un transform stream
Un Transform
stream es básicamente un Duplex donde solo debemos implementar el write que ahora se llama transform
y aquí podemos procesar, filtrar, transformar nuestros pedazos de información, esto también se puede hacer en un writable o duplex, pero es mejor tener separado los conceptos.
// transform.js
const { Transform } = require('stream');
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
// Convierto a mayúscula
this.push(chunk.toString().toUpperCase());
callback();
}
});
process.stdin.pipe(upperCaseTransform).pipe(process.stdout);