Exporta archivos a Microsoft Azure con cURL de forma eficiente
Para exportar un archivo a Azure Blob Storage con cURL, envía una solicitud
PUT autenticada con x-ms-blob-type: BlockBlob.
El siguiente ejemplo firma esa solicitud con una clave de cuenta de almacenamiento existente,
se niega a reemplazar un blob existente y descarga el resultado para comparar sus bytes con los
del archivo local.
Comprueba los requisitos previos
Usa una shell de Linux con Node.js y cURL instalados. Este ejemplo se probó con Node.js 26.12.0 y cURL 8.23.0; usa esas versiones para reproducirlo en las mismas condiciones. Node ejecuta el programa auxiliar en TypeScript sin compilador ni instalación de paquetes. La página de descargas de Node.js y las versiones de cURL proporcionan las herramientas.
Comprueba ambos ejecutables antes de guardar el programa:
node --version && curl --version
También necesitas:
- Una cuenta de almacenamiento existente y un contenedor privado accesibles mediante el endpoint
público de Azure
https://ACCOUNT.blob.core.windows.net. Para un proyecto nuevo, usa una cuenta de uso general v2 compatible. El aprovisionamiento de la cuenta y del contenedor queda fuera de esta guía. - Permiso para usar Shared Key en esa cuenta y su clave de cuenta en Base64, proporcionada mediante el proceso de gestión de secretos de tu organización. Tenla disponible en un archivo protegido que solo tú puedas leer. El programa lee ese archivo a través de la entrada estándar; mantenlo fuera de tu proyecto y del control de versiones.
- Un archivo local regular de entre cero y 8 MiB. Mantenlo sin cambios hasta que termine la verificación. Este ejemplo para archivos pequeños almacena temporalmente en memoria la entrada y los bytes descargados.
Usa caracteres ASCII imprimibles en la ruta local completa. Para el nombre del blob remoto, usa segmentos separados por barras que contengan letras, dígitos, puntos, guiones bajos y guiones. Cada segmento debe comenzar con una letra, un dígito, un guion bajo o un guion. El programa auxiliar rechaza deliberadamente otros nombres en lugar de inferir cómo codificarlos en la URL. Un archivo vacío es válido.
Firma la solicitud
Shared Key usa una firma HMAC-SHA256 calculada a partir del método HTTP, determinados encabezados
de la solicitud y la ruta del recurso. La clave de cuenta se decodifica de Base64 antes de firmar.
El orden de los encabezados importa, y una subida de cero bytes usa un campo content-length vacío
en la firma, aunque su encabezado HTTP Content-Length sea
0. Estos detalles siguen la
especificación de Shared Key de Microsoft.
Una clave de cuenta otorga acceso amplio. Microsoft recomienda Microsoft Entra ID con identidades administradas siempre que sea posible; esta guía está destinada específicamente a un entorno ya autorizado para usar Shared Key. En este programa, un token Bearer o un token SAS no pueden sustituir la clave de cuenta.
Guarda este programa completo como azure-upload.mts. La extensión
.mts hace explícito su formato de módulo, incluso dentro de un proyecto
CommonJS existente.
import { spawnSync } from 'node:child_process'
import { createHash, createHmac } from 'node:crypto'
import { readFileSync, statSync } from 'node:fs'
import { resolve } from 'node:path'
const maxBytes = 8 * 1024 * 1024
function main(): void {
const [input, account, container, blob, ...extra] = process.argv.slice(2)
if (!input || !account || !container || !blob || extra.length > 0) {
throw new Error('Usage: node azure-upload.mts FILE ACCOUNT CONTAINER BLOB < KEY_FILE')
}
const file = resolve(input)
if (!/^[\x20-\x7e]+$/.test(file)) throw new Error('Use a printable ASCII local path.')
if (!/^[a-z0-9]{3,24}$/.test(account)) throw new Error('Invalid account name.')
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(container) || container.length < 3 || container.length > 63) {
throw new Error('Invalid container name.')
}
if (blob.length > 1024 || !blob.split('/').every((part) => /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/.test(part))) {
throw new Error('Use simple slash-separated blob-name segments.')
}
const info = statSync(file)
if (!info.isFile() || info.size > maxBytes) throw new Error('Use a regular file of at most 8 MiB.')
const original = readFileSync(file)
if (original.length > maxBytes) throw new Error('The input grew beyond 8 MiB.')
const encodedKey = readFileSync(0, 'utf8').trim()
if (!/^[A-Za-z0-9+/]{86}==$/.test(encodedKey)) throw new Error('Expected a Base64 512-bit account key.')
const key = Buffer.from(encodedKey, 'base64')
const url = `https://${account}.blob.core.windows.net/${container}/${blob}`
function request(method: 'PUT' | 'GET'): Buffer {
const uploading = method === 'PUT'
const length = uploading ? original.length : 0
const contentType = uploading ? 'application/octet-stream' : ''
const azureHeaders = [
...(uploading ? ['x-ms-blob-type:BlockBlob'] : []),
`x-ms-date:${new Date().toUTCString()}`,
'x-ms-version:2023-11-03',
]
const fields = [method, '', '', length === 0 ? '' : String(length), '', contentType,
'', '', '', uploading ? '*' : '', '', '']
const stringToSign = `${fields.join('\n')}\n${azureHeaders.join('\n')}\n/${account}/${container}/${blob}`
const signature = createHmac('sha256', key).update(stringToSign).digest('base64')
const headers = [
...azureHeaders,
`Authorization: SharedKey ${account}:${signature}`,
...(uploading ? [`Content-Length: ${length}`, `Content-Type: ${contentType}`, 'If-None-Match: *'] : []),
]
const result = spawnSync('curl', [
'--disable', '--globoff', '--silent', '--show-error', '--fail', '--proto', '=https',
'--request', method, '--header', '@-',
'--write-out', '%{stderr}HTTP:%{http_code}',
...(uploading ? ['--upload-file', file] : []),
'--output', uploading ? '/dev/null' : '-', url,
], { input: `${headers.join('\n')}\n`, maxBuffer: maxBytes + 65536 })
const status = /HTTP:(\d{3})$/.exec(result.stderr?.toString() ?? '')?.[1] ?? 'unknown'
if (result.status !== 0 || status !== (uploading ? '201' : '200')) {
console.error(`${method} failed: HTTP ${status}; cURL exit ${result.status ?? 'unknown'}.`)
throw new Error('Request failed.')
}
return result.stdout
}
request('PUT')
const downloaded = request('GET')
if (!original.equals(downloaded)) throw new Error('Downloaded bytes differ from the input.')
const sha256 = createHash('sha256').update(original).digest('hex')
console.log(`Verified ${original.length} bytes; SHA-256 ${sha256}`)
}
try {
main()
} catch {
console.error('Export failed. Check the input, account key, destination, and connection.')
process.exitCode = 1
}
El programa auxiliar pasa el encabezado de autorización a cURL a través de la entrada estándar con
--header @-, lo que mantiene la clave y la firma fuera de los argumentos del
comando. También desactiva la carga de .curlrc para que una configuración
guardada de cURL no pueda añadir reintentos ni cambiar el manejo de la salida. Evita el rastreo de
la shell y las opciones de rastreo o salida detallada de cURL al manejar credenciales. Consulta el
manual de cURL para conocer estas opciones.
Sube el archivo y comprueba los bytes
Reemplaza a continuación la cuenta, el contenedor, el nombre del blob de destino y la ruta del
archivo de clave. El contenedor ya debe existir; elige un destino que no exista. Ejecuta este
comando desde el directorio que contiene azure-upload.mts y tu archivo de entrada:
node azure-upload.mts ./localfile.bin youraccount yourcontainer exports/localfile.bin < /secure/path/azure-account.key
Una solicitud Put Blob exitosa devuelve HTTP
201. El programa exige después HTTP 200
en una descarga autenticada independiente y compara la respuesta completa con la entrada original.
Solo después de que ambas comprobaciones se superen, imprime
Verified, el número de bytes y el resumen
SHA-256. Una subida exitosa por sí sola no produce ese mensaje. El blob permanece en tu contenedor
privado; este programa no lo elimina.
Gestiona un rechazo o un resultado incierto
La condición If-None-Match: * hace que la subida solo
permita crear blobs. Repetir el comando con un destino existente debe fallar y dejar sus bytes
intactos. Azure puede indicar 409 con
BlobAlreadyExists o un fallo de la condición 412;
no diagnostiques todos los conflictos como problemas de autenticación. Microsoft enumera los
códigos de error de Blob Storage.
El programa informa únicamente del estado HTTP y del código de salida de cURL, sin imprimir los
cuerpos de error del proveedor.
Un 403 requiere comprobar el acceso: confirma la correspondencia entre
la cuenta y la clave, si se permite Shared Key y si tu red puede acceder a esa cuenta. Comprueba
el archivo local y la ruta del archivo de clave si el programa falla antes de informar de un
estado HTTP. No hay reintentos automáticos habilitados.
Una conexión interrumpida puede dejar incierto el resultado de la escritura: Azure puede haber almacenado el blob antes de que se perdiera la respuesta. Una descarga de verificación fallida también deja el blob subido en su lugar. Inspecciona ese destino exacto con tus herramientas habituales de acceso autenticado al almacenamiento y compara sus bytes descargados antes de decidir si conservarlo o eliminarlo. Repetir este comando, que solo permite crear blobs, no puede confirmar el contenido de un blob existente ni lo reemplazará. No elimines un destino existente solo para que un reintento tenga éxito.
Elige otra herramienta para transferencias más grandes
El límite de 8 MiB corresponde a este ejemplo que almacena temporalmente los datos en memoria,
no al límite del servicio de Azure. Usa una solicitud Put Blob en lugar de
cargar bloques provisionales y confirmarlos. Para archivos más grandes, transferencias reanudables
o automatización por lotes, usa un cliente de almacenamiento que gestione esas transferencias;
este ejemplo no ofrece garantías de transferencia en varios bloques ni de tasa de transferencia.
Las bibliotecas cliente de Azure Blob son un siguiente paso útil
cuando una pequeña exportación REST deja de ser suficiente.
