Uploading a file in Azure File Storage using node.js
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.
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 });
}
}
Related videos on Youtube
Sniper
Updated on June 04, 2022Comments
-
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 over 8 yearsThanks #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 over 8 yearsThanks #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 over 8 yearsYou should be able to use
createFileFromText
, since you already have all of the file contents with you. -
carlosfigueira over 8 yearsBy the way, I updated the answer to use that function.
-
Admin over 8 yearsHi, what is the same issue you refer to? the
timed out
issue or the other one in your update section? -
Sniper over 8 yearsThe
time out
issue. -
Sniper over 8 yearsI tried
createFileFromText
its working fine for a text file. But if we upload a image using this the image file is corrupted. -
Admin over 8 yearsHi @Sniper, as I review your question, and modified my answer as updated section.
-
Sniper over 8 years@GaryLiu-MSFT, I can't find and update in your answer. Can you please update it.
-
Sniper over 8 yearsThanks @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 defaultContentMD5
is disabled. Is there any other option to upload larger files (below 20MB).