1
votes

I wish to upload files in the form of a stream into Azure Storage but I don't want to use Azure SDK instead I want to do it in a more generic way using REST API and not BlobServiceClient.

Is there a way to do so? The reference links for the same can be found here:

https://docs.microsoft.com/en-us/azure/storage/common/storage-samples-javascript?toc=/azure/storage/blobs/toc.json#blob-samples

https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/storage/storage-blob/samples/javascript/advanced.js#L74

But the links mentioned here propose a solution using Azure SDK. I want to do it without Azure SDK

Here's the code:

const CryptoJS = require("crypto-js");
const request = require("request");
const fs = require("fs");

const account = process.env.ACCOUNT_NAME || "";
const key = process.env.ACCOUNT_KEY || "";
const containerName = "demo";
const blobName = "dummyfile.txt";
var strTime = new Date().toUTCString();

// read file to Stream
var filePath = `${__dirname}/README.md`;
const readStream = fs.createReadStream(filePath);
var stat = fs.statSync(filePath);

string_params = {
  verb: "PUT",
  "Content-Encoding": "",
  "Content-Language": "",
  "Content-Length": stat.size,
  "Content-MD5": "",
  "Content-Type": "application/octet-stream",
  Date: "",
  "If-Modified-Since": "",
  "If-Match": "",
  "If-None-Match": "",
  "If-Unmodified-Since": "",
  Range: "",
  CanonicalizedHeaders:
    "x-ms-blob-type:BlockBlob\nx-ms-date:" +
    strTime +
    "\nx-ms-version:" +
    "2020-04-08\n",
  CanonicalizedResource: `/${account}/${containerName}/${blobName}`,
};

var strToSign = `${string_params["verb"]}\n${string_params["Content-Encoding"]}\n${string_params["Content-Language"]}\n${string_params["Content-Length"]}\n${string_params["Content-MD5"]}\n${string_params["Content-Type"]}\n${string_params["Date"]}\n${string_params["If-Modified-Since"]}\n${string_params["If-Match"]}\n${string_params["If-None-Match"]}\n${string_params["If-Unmodified-Since"]}\n${string_params["Range"]}\n${string_params["CanonicalizedHeaders"]}${string_params["CanonicalizedResource"]}`;

var secret = CryptoJS.enc.Base64.parse(key);
var hash = CryptoJS.HmacSHA256(strToSign, secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
var auth = `SharedKey ${account}:` + hashInBase64;

const options = {
  url: `https://${account}.blob.core.windows.net/${containerName}/${blobName}`,
  headers: {
    Authorization: auth,
    "x-ms-blob-type": "BlockBlob",
    "x-ms-date": strTime,
    "x-ms-version": "2020-04-08",
    "Content-Type": "application/octet-stream",
    "Content-Length": stat.size,
  },
  body: readStream,
};

function callback(error, response, body) {
  console.log(response.statusCode);
  console.log(response.statusMessage);
  if (!error && response.statusCode == 200) {
    console.log(error);
    console.log(response);
    console.log(body);
  }
}

request.put(options, callback);

The error which I am getting is:

TypeError: Cannot read property 'statusCode' of undefined

Screenshot of error

Edit: The problem was solved by Pamela's code + the issue I found was there's was an error in initializing .env variables.

2

2 Answers

1
votes

You could use Put Blob Rest API to upload a stream. There is a sample using node.js.

const CryptoJS = require("crypto-js");
const request = require("request");
const fs = require('fs');

const account = "account-name";
const key = "account-key";
var strTime = new Date().toUTCString();

var filePath = 'your-file-path';
const readStream = fs.createReadStream(filePath);
var stat = fs.statSync(filePath);

string_params = {
    'verb': 'PUT',
    'Content-Encoding': '',
    'Content-Language': '',
    'Content-Length': stat.size,
    'Content-MD5': '',
    'Content-Type': 'application/octet-stream',
    'Date': '',
    'If-Modified-Since': '',
    'If-Match': '',
    'If-None-Match': '',
    'If-Unmodified-Since': '',
    'Range': '',
    'CanonicalizedHeaders': 'x-ms-blob-type:BlockBlob\nx-ms-date:' + strTime + '\nx-ms-version:' + '2020-04-08\n',
    'CanonicalizedResource': `/${account}/containername/myblob`
}


var strToSign = `${string_params['verb']}\n${string_params['Content-Encoding']}\n${string_params['Content-Language']}\n${string_params['Content-Length']}\n${string_params['Content-MD5']}\n${string_params['Content-Type']}\n${string_params['Date']}\n${string_params['If-Modified-Since']}\n${string_params['If-Match']}\n${string_params['If-None-Match']}\n${string_params['If-Unmodified-Since']}\n${string_params['Range']}\n${string_params['CanonicalizedHeaders']}${string_params['CanonicalizedResource']}`
console.log(strToSign);

var secret = CryptoJS.enc.Base64.parse(key);
var hash = CryptoJS.HmacSHA256(strToSign, secret);
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
var auth = `SharedKey ${account}:` + hashInBase64;
console.log(auth)

const options = {
    url: `https://${account}.blob.core.windows.net/containername/myblob`,
    headers: {
        Authorization: auth,
        'x-ms-blob-type': 'BlockBlob',
        "x-ms-date": strTime,
        "x-ms-version": "2020-04-08",
        'Content-Type': "application/octet-stream",
        'Content-Length': stat.size
    },
    body: readStream
};

function callback(error, response, body) {
    console.log(response.statusCode);
    console.log(response.statusMessage);
    if (!error && response.statusCode == 200) {
        console.log(error);
        console.log(response);
        console.log(body);
    }
}

request.put(options, callback);
1
votes

You can try using Azure REST API for the Blob Operations. You can find REST API's for Blob here - Operations on Blob

The question is how are you going to handle stream upload at runtime. Azure SDK supports the stream upload. However in case of REST API, you'll need to handle that by yourself.

Uploading stream or parallel data you'll need some wrapper around this REST API Calls to achieve that

Hope you find this useful.