Uploading a file in Azure File Storage using node.js

14,026

Solution 1

There are several issue here. Let us go over them one by one.

1. In your Java client you cannot just dump the binary data into an Azure mobile service connection.

The reason for this is that an Azure mobile service has two body parsers that ensure that no matter what, the request body is parsed for you. So, while you can walk around the Express body parser by specifying an uncommon content type, you will still hit the Azure body parser that will mess up your data stream by naively assuming that it is a UTF-8 string.

The only option therefore is to skip the Express parser by specifying a content type it cannot handle and then play along with the Azure parser by encoding your binary data with Base64 encoding.

So, in the Java client replace

Path path = Paths.get("C:/Users/uma.maheshwaran/Desktop/Temp.txt");
byte[] data = Files.readAllBytes(path);

with

con.setRequestProperty("content-type", "binary");    
Path path = Paths.get("C:/Users/uma.maheshwaran/Desktop/Temp.txt");
byte[] data = Files.readAllBytes(path);
data = Base64.getEncoder().encode(data);

If you are not on Java 8, replace the java.util.Base64 encoder with any other Base64 encoder you have access to.

2. The createFileFromStream Azure storage api function you are trying to use expects a stream.

At the same time, the best you can get when parsing a request body manually is a byte array. Unfortunately, Azure mobile services use NodeJS version 0.8, which means there is no easy way to construct a readable stream from a byte array, and you you will have to assemble your own stream suitable for Azure storage api. Some duct tape and [email protected] should do just fine.

var base64 = require('base64-js'),
    Stream = require('stream'),
    fileService = require('azure-storage')
        .createFileService('yourStorageAccount', 'yourStoragePassword');

exports.post = function (req, res) {
    var data = base64.toByteArray(req.body),
        buffer = new Buffer(data),
        stream = new Stream();
        stream['_ended'] = false;
        stream['pause'] = function() {
            stream['_paused'] = true;
        };
        stream['resume'] = function() {
            if(stream['_paused'] && !stream['_ended']) {
                stream.emit('data', buffer);
                stream['_ended'] = true;
                stream.emit('end');
            }
        }; 
    try {
        fileService.createFileFromStream(req.headers.sharename, req.headers.directorypath, 
            req.headers.filename, stream, data.length, function (error, result, resp) {
                res.statusCode = error ? 500 : 200;
                res.end();
            }
        );
    } catch (e) {
        res.statusCode = 500;
        res.end();
    }
};

These are the dependencies you need for this sample.

"dependencies": {   
    "azure-storage": "^0.7.0",
    "base64-js": "^0.0.8",
    "stream": "0.0.1"
}

If specifying them in your service's package.json does not work you can always go to this link and install them manually via the console.

cd site\wwwroot
npm install azure-storage
npm install base64-js
npm install [email protected]

3. To increase the default upload limit of 1Mb, specify MS_MaxRequestBodySizeKB for your service.

MS_MaxRequestBodySizeKB

Do keep in mind though that since you are transferring you data as Base64-encoded you have to account for this overhead. So, to support uploading files up to 20Mb in size, you have to set MS_MaxRequestBodySizeKB to roughly 20 * 1024 * 4 / 3 = 27307.

Solution 2

We can leverage this answer of the thread on SO How to send an image from Android client to Node.js server via HttpUrlConnection?, which create a custom middleware to get the upload file content into a buffer array, then we can use createFileFromText() to store the file in Azure Storage.

Here is the code snippet:

function rawBody(req, res, next) {
    var chunks = [];

    req.on('data', function (chunk) {
        chunks.push(chunk);
    });

    req.on('end', function () {
        var buffer = Buffer.concat(chunks);

        req.bodyLength = buffer.length;
        req.rawBody = buffer;
        next();
    });

    req.on('error', function (err) {
        console.log(err);
        res.status(500);
    });
}
router.post('/upload', rawBody,function (req, res){

    fileService.createShareIfNotExists('taskshare', function (error, result, response) {
        if (!error) {
            // if result = true, share was created.
            // if result = false, share already existed.
            fileService.createDirectoryIfNotExists('taskshare', 'taskdirectory', function (error, result, response) {
                if (!error) {
                    // if result = true, share was created.
                    // if result = false, share already existed.
                    try {
                        fileService.createFileFromText('taskshare', 'taskdirectory', 'test.txt', req.rawBody, function (error, result, resp) {
                            if (!error) {
                                // file uploaded
                                res.send(200, "File Uploaded");
                            } else {
                                res.send(200, "Error!");
                            }
                        });
                    } catch (ex) {
                        res.send(500, { error: ex.message });
                    }

                }
            });
        }
    });

})
router.get('/getfile', function (req, res){
    fileService.createReadStream('taskshare', 'taskdirectory', 'test.txt').pipe(res);
})

Solution 3

I find the easiest way is to use pkgcloud which abstracts the differences between cloud providers and also provides a clean interface for uploading and downloading files. It uses streams so the implementation is memory efficient as well.

var pkgcloud = require('pkgcloud')
var fs = require('fs')
var client = pkgcloud.storage.createClient({
  provider: 'azure',
  storageAccount: 'your-storage-account',
  storageAccessKey: 'your-access-key'
});

var readStream = fs.createReadStream('a-file.txt');
var writeStream = client.upload({
  container: 'your-storage-container',
  remote: 'remote-file-name.txt'
});

writeStream.on('error', function (err) {
  // handle your error case
});

writeStream.on('success', function (file) {
  // success, file will be a File model
});

readStream.pipe(writeStream);

Solution 4

When the request arrives at the function defined in exports.post, the whole request is already there, so you don't need to buffer it. You can simplify it by writing something along the lines of the code below.

exports.post = function(request, response){
    var shareName = request.headers.sharename;
    var dirPath = request.headers.directorypath;
    var fileName = request.headers.filename;

    var body = request.body;
    var length = body.length;

    console.log(length);

    try {
        fileService.createFileFromText(shareName, dirPath, fileName, body, function(error, result, resp) {
            if (!error) {
                // file uploaded
                response.send(statusCodes.OK, "File Uploaded");
            } else {
                response.send(statusCodes.OK, "Error!");
            }
        });
    } catch (ex) {
        response.send(500, { error: ex.message });
    }
}
Share:
14,026

Related videos on Youtube

Sniper
Author by

Sniper

Updated on June 04, 2022

Comments

  • Sniper
    Sniper almost 2 years

    We are trying create an webservice to upload files to Azure file storage using node.js service.

    Below is the node.js server code.

    exports.post = function(request, response){
    var shareName = request.headers.sharename;
    var dirPath = request.headers.directorypath;
    var fileName = request.headers.filename;
    
    var body;
    var length;
    
    request.on("data", function(chunk){
        body += chunk;
        console.log("Get data");
    });
    
    
    request.on("end", function(){
        try{
            console.log("end");
            var data = body;
            length = data.length;
    
    console.log(body); // This giving the result as undefined
    console.log(length);
    
            fileService.createFileFromStream(shareName, dirPath, fileName, body, length, function(error, result, resp) {
                if (!error) {
                    // file uploaded
                    response.send(statusCodes.OK, "File Uploaded");
                }else{
                    response.send(statusCodes.OK, "Error!");
                }
            });
    
        }catch (er) {
    response.statusCode = 400;
    return res.end('error: ' + er.message);
    }
    
    });
    
    }
    

    Below is our client to upload a file.

    private static void sendPOST() throws IOException {
        URL obj = new URL("https://crowdtest-fileservice.azure-mobile.net/api/files_stage/");
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod("POST");
        con.setRequestProperty("sharename", "newamactashare");
        con.setRequestProperty("directorypath", "MaheshApp/TestLibrary/");
        con.setRequestProperty("filename", "temp.txt");
    
    
        Path path = Paths.get("C:/Users/uma.maheshwaran/Desktop/Temp.txt");
        byte[] data = Files.readAllBytes(path);
    
        // For POST only - START
        con.setDoOutput(true);
        OutputStream os = con.getOutputStream();
        os.write(data);
        os.flush();
        os.close();
        // For POST only - END
    
        int responseCode = con.getResponseCode();
        System.out.println("POST Response Code :: " + responseCode);
    
        if (responseCode == HttpURLConnection.HTTP_OK) { // success
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuffer response = new StringBuffer();
    
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
                System.out.println(inputLine);
            }
            in.close();
    
            // print result
            System.out.println(response.toString());
        } else {
            BufferedReader br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
            String line = "";
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
            System.out.println("POST request not worked");
        }
    }
    

    It is showing the error

    The request 'POST /api/files_stage/' has timed out. This could be caused by a script that fails to write to the response, or otherwise fails to return from an asynchronous call in a timely manner.

    Updated:

    I have also tried below code.

      var body = new Object();
      body = request.body;
      var length = body.length;
    
      console.log(request.body);
      console.log(body);
      console.log(length);
    
        try {
            fileService.createFileFromStream(shareName, dirPath, fileName, body, length, function(error, result, resp) {
                if (!error) {
                    // file uploaded
                    response.send(statusCodes.OK, "File Uploaded");
                }else{
                    response.send(statusCodes.OK, "Error!");
                }
            });
        } catch (ex) {
                response.send(500, { error: ex.message });
        }
    

    But facing the issue

    {"error":"Parameter stream for function createFileFromStream should be an object"}

    I am new to node.js. Please help me to fix this.

  • Sniper
    Sniper over 8 years
    Thanks #carlosfigueira. I tried this but its giving a new error. "{"error":"Parameter stream for function createFileFromStream should be an object"}". I tried to declare the "body" as an object but still showing the same issue.
  • Sniper
    Sniper over 8 years
    Thanks #Gray. I tried this but still showing the same issue. The control is now coming inside the "req.on('data', function (chunk) {". Please help me on this.
  • carlosfigueira
    carlosfigueira over 8 years
    You should be able to use createFileFromText, since you already have all of the file contents with you.
  • carlosfigueira
    carlosfigueira over 8 years
    By the way, I updated the answer to use that function.
  • Admin
    Admin over 8 years
    Hi, what is the same issue you refer to? the timed out issue or the other one in your update section?
  • Sniper
    Sniper over 8 years
    The time out issue.
  • Sniper
    Sniper over 8 years
    I tried createFileFromText its working fine for a text file. But if we upload a image using this the image file is corrupted.
  • Admin
    Admin over 8 years
    Hi @Sniper, as I review your question, and modified my answer as updated section.
  • Sniper
    Sniper over 8 years
    @GaryLiu-MSFT, I can't find and update in your answer. Can you please update it.
  • Sniper
    Sniper over 8 years
    Thanks @Roman Pletnev. Its working fine. But if the file is more than an MB it throwing an error {"code":413,"error":"Error: Request body maximum size limit was exceeded.. By default ContentMD5 is disabled. Is there any other option to upload larger files (below 20MB).