Exportar arquivos com eficiência para Microsoft Azure com cURL
Para exportar um arquivo para o Azure Blob Storage com cURL, envie uma requisição autenticada
PUT com x-ms-blob-type: BlockBlob. O exemplo abaixo assina essa
requisição com uma chave de conta de armazenamento existente, recusa a substituição de um blob
existente e baixa o resultado para comparar seus bytes com os do arquivo local.
Verifique os pré-requisitos
Use um shell Linux com Node.js e cURL instalados. Este exemplo foi testado com Node.js 26.12.0 e cURL 8.23.0; use essas versões para reproduzi-lo nas mesmas condições. O Node executa o utilitário TypeScript sem compilador nem instalação de pacotes. A página de download do Node.js e as versões do cURL disponibilizam as ferramentas.
Verifique os dois executáveis antes de salvar o programa:
node --version && curl --version
Você também precisa de:
- Uma conta de armazenamento existente e um contêiner privado acessíveis pelo endpoint público do
Azure
https://ACCOUNT.blob.core.windows.net. Para um novo projeto, use uma conta de uso geral v2 com suporte. O provisionamento da conta e do contêiner está fora do escopo deste tutorial. - Permissão para usar Shared Key nessa conta e a chave da conta em Base64 fornecida pelo processo de gerenciamento de segredos da sua organização. Mantenha-a em um arquivo protegido que somente você possa ler. O programa lê esse arquivo pela entrada padrão; mantenha-o fora do projeto e do controle de versão.
- Um arquivo local regular de zero a 8 MiB. Mantenha-o inalterado até a conclusão da verificação. Este exemplo para arquivos pequenos armazena em memória a entrada e os bytes baixados.
Use caracteres ASCII imprimíveis em todo o caminho local. Para o nome do blob remoto, use segmentos separados por barras que contenham letras, dígitos, pontos, sublinhados e hífens, começando cada segmento com uma letra, um dígito, um sublinhado ou um hífen. O utilitário rejeita outros nomes intencionalmente, em vez de tentar adivinhar o escape da URL. Um arquivo vazio é válido.
Assine a requisição
Shared Key usa uma assinatura HMAC-SHA256 calculada sobre o método HTTP, os cabeçalhos selecionados
da requisição e o caminho do recurso. A chave da conta é decodificada de Base64 antes da assinatura.
A ordem dos cabeçalhos importa, e um upload de zero bytes usa um campo content-length vazio na
assinatura, embora seu cabeçalho HTTP Content-Length seja 0.
Esses detalhes seguem a especificação Shared Key da Microsoft.
Uma chave de conta concede acesso amplo. A Microsoft recomenda o Microsoft Entra ID com identidades gerenciadas sempre que possível; esta receita se destina especificamente a um ambiente já autorizado a usar Shared Key. Um token de portador ou um token SAS não pode substituir a chave da conta neste programa.
Salve este programa completo como azure-upload.mts. A extensão
.mts torna explícito o formato de módulo, inclusive dentro de um projeto
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
}
O utilitário passa o cabeçalho de autorização ao cURL pela entrada padrão com
--header @-, mantendo a chave e a assinatura fora dos argumentos do comando.
Ele também desativa o carregamento de .curlrc para que uma configuração
salva do cURL não possa adicionar novas tentativas nem alterar o tratamento da saída. Evite o
rastreamento do shell e as opções de rastreamento ou saída detalhada do cURL ao lidar com
credenciais. Consulte o manual do cURL para saber mais sobre essas
opções.
Faça o upload e confira os bytes
Substitua a conta, o contêiner, o nome do blob de destino e o caminho do arquivo da chave abaixo.
O contêiner já deve existir; escolha um destino que ainda não exista. Execute este comando no
diretório que contém azure-upload.mts e seu arquivo de entrada:
node azure-upload.mts ./localfile.bin youraccount yourcontainer exports/localfile.bin < /secure/path/azure-account.key
Uma operação Put Blob bem-sucedida retorna HTTP
201. Em seguida, o programa exige HTTP 200
de um download autenticado separado e compara a resposta completa com a entrada original.
Somente depois que ambas as verificações passam, ele imprime
Verified, a contagem de bytes e o hash SHA-256.
Um upload bem-sucedido, por si só, não produz essa mensagem. O blob permanece no seu contêiner
privado; este programa não o exclui.
Lide com uma recusa ou um resultado incerto
A condição If-None-Match: * permite que o upload
apenas crie um novo blob. Repetir o comando com um destino existente deve falhar e manter seus
bytes intactos. O Azure pode retornar 409 com
BlobAlreadyExists ou uma falha de condição 412; não
diagnostique todo conflito como um problema de autenticação. A Microsoft lista os
códigos de erro do Blob Storage.
O programa informa apenas o status HTTP e o código de saída do cURL, sem imprimir os corpos das
respostas de erro do provedor.
Um status 403 exige uma verificação de acesso: confirme se a chave
corresponde à conta, se Shared Key é permitido e se sua rede pode acessar essa conta. Verifique
o arquivo local e o caminho do arquivo da chave se o programa falhar antes de informar um status
HTTP. Nenhuma nova tentativa automática está habilitada.
Uma conexão interrompida pode deixar o resultado da gravação incerto: o Azure pode ter armazenado o blob antes de a resposta se perder. Uma falha no download de verificação também deixa o blob enviado no destino. Inspecione esse destino exato com suas ferramentas habituais de acesso autenticado ao armazenamento e compare os bytes baixados antes de decidir mantê-lo ou removê-lo. Repetir este comando, que permite apenas a criação, não pode confirmar o conteúdo de um blob existente e não o substituirá. Não exclua um destino existente apenas para que uma nova tentativa tenha sucesso.
Escolha outra ferramenta para transferências maiores
O limite de 8 MiB pertence a este exemplo que armazena os dados em memória, e não ao serviço do
Azure. Ele usa uma única requisição Put Blob em vez de preparar e confirmar
blocos. Para arquivos maiores, retomada de transferências ou automação em lote, use um cliente de
armazenamento que gerencie essas transferências; este exemplo não oferece garantias de transferência
em múltiplos blocos nem de taxa de transferência. As
bibliotecas de cliente do Azure Blob são um próximo passo útil
quando uma pequena exportação REST já não atende às suas necessidades.
