Uploading a file in Azure File Storage using node.js

烈酒焚心 提交于 2019-12-03 22:44:21
Roman Pletnev

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 stream@0.0.1 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 stream@0.0.1

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

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.

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);
Gary Liu - MSFT

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);
})

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 });
    }
}
Yang Xia - Microsoft

There are several things:

1. createFileFromText can work with plain text. But it will fail for those binary content, as it uses UTF-8 encoding.

You might want to refer to the similar issue for blob at: Saving blob (might be data!) returned by AJAX call to Azure Blob Storage creates corrupt image

2. The createFileFromStream or createWriteStreamToExistingFile \ createWriteStreamToNewFile Azure storage API may be the function can help.

Please be noted that these APIs are target to streams. You need convert your buffer/string in the request body to a stream. You can refer to How to wrap a buffer as a stream2 Readable stream?

For createFileFromStream :

fileService.createFileFromStream(req.headers.sharename, 
  req.headers.directorypath, 
  req.headers.filename, 
  requestStream, 
  data.length, 
  function (error, result, resp) {
    res.statusCode = error ? 500 : 200;
    res.end();
  }
);

For createWriteStreamToNewFile :

var writeStream = fileService.createWriteStreamToNewFile(req.headers.sharename, 
  req.headers.directorypath, 
  req.headers.filename, 
  data.length);

requestStream.pipe(writeStream);

3. There are several issues in your code

console.log(body); // This giving the result as undefined

The reason is you define var body and it is undefined. The code body += chunk will still make body undefined.

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!");
  }
});

When error happens in createFileFromStream, it could also be an error in the network transfer, you might also want to return the error code instead of statusCodes.OK.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!