En este post vamos a crear un servicio en Cloud Run con un servidor Node.js que registra los chunks de datos entrantes. También crearemos un cliente Node.js que envía chunks de datos al servidor.
Veremos cómo hacer streaming de datos del cliente al servidor con HTTP/1 usando chunked transfer encoding.
Si tienes prisa, puedes revisar mi repo de git con todos los archivos aquí.
¿Por qué?
Lo primero que te estarás preguntando es el PORQUÉ.
En general, HTTP/2 es la opción preferida. Ofrece beneficios importantes en performance, menor latencia y mejor uso de los recursos. Sin embargo, aunque Cloud Run ofrece HTTP/2 End-to-End para mejorar el rendimiento, tu aplicación también debe poder recibir llamadas HTTP/2. Esa capacidad no está disponible en todos los casos, lo que puede convertirse en un obstáculo en ciertos despliegues:
- Necesitas dar soporte a clientes antiguos que no admiten HTTP/2 o que tienen problemas conocidos con este protocolo.
- Si tu aplicación depende de librerías o frameworks que requieren chunked encoding y no son totalmente compatibles con HTTP/2, usar HTTP/1 puede ser la única alternativa.
Vamos a usar el header Transfer-Encoding: chunked, una funcionalidad de HTTP/1.1 que le permite al cliente enviar datos en chunks al servidor sin conocer de antemano el tamaño total. Los datos se dividen en chunks que se envían por separado y el servidor los reconstruye a medida que llegan.
Esto resulta especialmente útil cuando el emisor no conoce la longitud del contenido al iniciar la transmisión, lo que ocurre con frecuencia en contenido dinámico o de streaming.
Vale la pena destacar que HTTP/2 y HTTP/3 manejan la transmisión de datos de forma distinta: usan binary framing y multiplexing para que múltiples solicitudes y respuestas convivan en simultáneo, lo que puede traducirse en mejoras de performance. Aun así, puede que no tengas la posibilidad de modificar tu aplicación para que acepte HTTP/2.
Ten en cuenta que el concepto de chunked transfer encoding no aplica directamente en HTTP/2 ni en HTTP/3 como sí lo hace en HTTP/1.1.
Direcciones del streaming de datos
Hacer streaming del servidor al cliente y del cliente al servidor son dos conceptos distintos, cada uno con sus propios casos de uso y técnicas.
1. Del servidor al cliente (Server-Sent Events): se usa típicamente cuando el servidor tiene información nueva que necesita enviarle al cliente.
Por ejemplo, en una aplicación en tiempo real como un chat o una app de actualizaciones deportivas en vivo, el servidor necesita enviarle al cliente nuevos mensajes o actualizaciones apenas estén disponibles.
Esto se logra con una técnica llamada Server-Sent Events (SSE), en la que el cliente abre una conexión hacia el servidor y este la mantiene abierta, enviando actualizaciones por esa conexión cada vez que están disponibles.

2. Del cliente al servidor (HTTP Streaming o Chunked Transfer Encoding): se usa típicamente cuando el cliente tiene una gran cantidad de datos para enviar al servidor y quiere comenzar la transmisión antes de que todos los datos estén listos.
Por ejemplo, en una carga de archivos, el cliente puede querer empezar a subir un archivo grande antes de que se haya leído por completo en memoria.
Esto se logra con una funcionalidad del protocolo HTTP llamada chunked transfer encoding, en la que el cliente envía los datos en chunks y el servidor procesa cada uno a medida que llega.
En ambos casos, el objetivo es que los datos se envíen y procesen de forma incremental, en vez de exigir que todos estén listos al inicio de la solicitud. Esto se traduce en mejor performance y menor consumo de memoria, sobre todo cuando se trabaja con grandes volúmenes de datos.
Recuerda que la elección entre streaming del servidor al cliente o del cliente al servidor depende de los requerimientos específicos de tu aplicación. En este post nos enfocamos solo en el streaming del cliente al servidor. Si quieres ver streaming del servidor al cliente con gRPC, puedes consultar este post del blog de Google.
Configuración del servidor
Primero vamos a crear el servidor. Usaremos Express, un framework popular de Node.js. Nuestro servidor escuchará solicitudes POST en el endpoint /upload y registrará cualquier chunk de datos entrante.
Crea el archivo package.json:
{
"name": "stream-test",
"version": "1.0.0",
"description": "",
"main": "client.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2"
}
}
Y este es el server.js:
// server.js
const express = require('express');
const app = express();
app.use(express.raw({ type: '*/*', limit: '5mb' }));
app.post('/upload', (req, res) => {
req.on('data', chunk => {
console.log(`Received chunk: ${chunk}`);
});
req.on('end', () => {
res.send('Upload complete');
});
});
app.listen(3000, () => console.log('Server listening on port 3000'));
Podemos contenerizar este servidor con Docker. Este es un Dockerfile sencillo para el servidor:
# Dockerfile.server
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node", "server.js" ]
Servidor en Cloud Run
Ahora vamos a crear las imágenes de contenedor, hacer push al Artifact Registry en GCP y desplegar el servicio en Cloud Run. Puede que primero necesites crear tu repositorio de AR aquí:
REPOSITORY=us-central1-docker.pkg.dev/<GCP_PROJECT>/<REPO>
docker build -t $REPOSITORY/stream-server:1.0 -f Dockerfile.server .
Despliega tu servidor:
gcloud run deploy stream-server --image $REPOSITORY/stream-server:1.0
Una vez desplegado el servicio, deberías ver logs similares a estos:

Ahora que tenemos el servidor corriendo en Cloud Run, necesitamos obtener su URL:
export SERVER_URL=$(gcloud run services describe stream-server --region us-central1 --format 'value(status.url)')
Configuración del cliente
Ahora vamos a crear el cliente. Al iniciarse, enviará chunks de datos al servidor. Recuerda reemplazar <SERVER_URL> en el campo hostname con la URL real del servidor.
Este es el client.js:
// client.js
const https = require('https');
const options = {
hostname: '<SERVER_URL>',
port: 443,
path: '/upload',
method: 'POST',
headers: {
'Transfer-Encoding': 'chunked'
}
};
const req = https.request(options, (res) => {
res.on('data', (chunk) => {
console.log(`Response: ${chunk}`);
});
});
// write chunks of data to the request
req.write('chunk1');
req.write('chunk2');
req.write('chunk3');
req.end();
También podemos contenerizar este cliente con Docker. Este es un Dockerfile sencillo para el cliente:
# Dockerfile.client
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node", "client.js" ]
Y ahora construimos la imagen del cliente y ejecutamos el código:
docker build -t stream-client:1.0 -f Dockerfile.client .
docker run --rm stream-client:1.0
Confirmemos que funciona revisando los logs de Cloud Run del lado del servidor:

Un momento: parece que los chunks no se están registrando por separado. El problema es que el protocolo HTTP no garantiza que cada llamada a req.write() dispare un evento 'data' independiente en el servidor.
Los chunks que estás enviando son pequeños y se mandan muy seguidos, así que probablemente el servidor los está recibiendo en un único evento ‘data’.
Para confirmar que el streaming funciona, puedes enviar una mayor cantidad de datos que no quepa en un solo paquete TCP. Eso forzará a que los datos se dividan en múltiples chunks, lo que debería disparar varios eventos ‘data’ en el servidor.
Vamos a corregir el archivo client.js agregando un "for loop" para escribir datos, reconstruimos el contenedor y probamos de nuevo.
// client.js
const https = require('https');
const options = {
hostname: 'stream-server-kdfodunfwq-uc.a.run.app',
port: 443,
path: '/upload',
method: 'POST',
headers: {
'Transfer-Encoding': 'chunked'
}
};
const req = https.request(options, (res) => {
res.on('data', (chunk) => {
console.log(`Response: ${chunk}`);
});
});
// write a large amount of data to the request
for (let i = 0; i < 1e6; i++) {
req.write(`This is chunk number ${i}\n`);
}
req.end();
docker build -t stream-client:2.0 -f Dockerfile.client .
docker run --rm stream-client:2.0
¡Ahora sí funciona, excelente!

En este post vimos cómo hacer streaming de datos entre dos instancias de Cloud Run con Node.js. Esta configuración nos permite enviar datos en chunks del cliente al servidor, mientras el servidor registra cada chunk a medida que llega.
Ten en cuenta que el protocolo HTTP no garantiza que cada llamada a req.write() dispare un evento 'data' independiente en el servidor. Los chunks que enviábamos al inicio eran bastante pequeños y se mandaban muy seguidos, así que el servidor los recibía en un único evento ‘data’.
Pudimos confirmar que el streaming funcionaba enviando una mayor cantidad de datos que no cabía en un solo paquete TCP. Eso obligó a dividir los datos en múltiples chunks, lo que disparó varios eventos ‘data’ en el servidor.
Otra vez: en general, HTTP/2 es la opción preferida. Úsalo si es posible.
¡Espero que este post te haya resultado útil! Si tienes alguna duda, no dudes en dejar un comentario abajo.