Загрузка файла в хранилище файлов Azure с использованием node.js
Мы пытаемся создать веб-сервис для загрузки файлов в хранилище файлов Azure с помощью сервиса node.js.
Ниже приведен код сервера node.js.
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);
}
});
}
Ниже представлен наш клиент для загрузки файла.
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");
}
}
Это показывает ошибку
Время запроса POST /api/files_stage/ истекло. Это может быть вызвано сценарием, который не может записать в ответ или иным образом не может своевременно вернуться из асинхронного вызова.
Обновлено:
Я также попробовал приведенный ниже код.
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 });
}
Но столкнувшись с проблемой
{"error":"Поток параметров для функции createFileFromStream должен быть объектом"}
Я новичок в node.js. Пожалуйста, помогите мне исправить это.
5 ответов
Здесь есть несколько вопросов. Давайте рассмотрим их один за другим.
1. В своем клиенте Java вы не можете просто записать двоичные данные в соединение с мобильной службой Azure.
Причина этого заключается в том, что в мобильной службе Azure есть два анализатора тела, которые гарантируют, что независимо от того, что именно, тело запроса будет проанализировано для вас. Таким образом, хотя вы можете обойти анализатор тела Express, указав необычный тип содержимого, вы все равно попадете в анализатор тела Azure, который испортит ваш поток данных, наивно полагая, что это строка UTF-8.
Поэтому единственный вариант - пропустить синтаксический анализатор Express, указав тип контента, который он не может обработать, и затем воспроизвести вместе с синтаксическим анализатором Azure, кодируя двоичные данные с кодировкой Base64.
Итак, в клиенте Java замени
Path path = Paths.get("C:/Users/uma.maheshwaran/Desktop/Temp.txt");
byte[] data = Files.readAllBytes(path);
с
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);
Если вы не используете Java 8, замените кодировщик java.util.Base64 на любой другой кодировщик Base64, к которому у вас есть доступ.
2. The createFileFromStream
Функция API хранилища Azure, которую вы пытаетесь использовать, ожидает поток.
В то же время лучшее, что вы можете получить при синтаксическом анализе тела запроса, - это байтовый массив. К сожалению, мобильные сервисы Azure используют NodeJS версии 0.8, что означает, что нет простого способа создать читаемый поток из байтового массива, и вам придется собирать собственный поток, подходящий для API хранилища Azure. Некоторая клейкая лента и stream@0.0.1 должны подойти.
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();
}
};
Это зависимости, которые вам нужны для этого примера.
"dependencies": {
"azure-storage": "^0.7.0",
"base64-js": "^0.0.8",
"stream": "0.0.1"
}
Если указание их в package.json вашего сервиса не работает, вы всегда можете перейти по этой ссылке и установить их вручную через консоль.
cd site\wwwroot
npm install azure-storage
npm install base64-js
npm install stream@0.0.1
3. Чтобы увеличить ограничение загрузки по умолчанию до 1 МБ, укажите MS_MaxRequestBodySizeKB для вашей службы.
Имейте в виду, однако, что, поскольку вы передаете свои данные в кодировке Base64, вы должны учитывать эти издержки. Таким образом, для поддержки загрузки файлов размером до 20 МБ, вы должны установить MS_MaxRequestBodySizeKB
примерно до 20 * 1024 * 4 / 3 = 27307.
Я считаю, что самый простой способ - использовать pkgcloud, который абстрагирует различия между облачными провайдерами, а также предоставляет чистый интерфейс для загрузки и выгрузки файлов. Он использует потоки, поэтому реализация также эффективно использует память.
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);
Мы можем использовать этот ответ потока на SO. Как отправить изображение с Android-клиента на сервер Node.js через HttpUrlConnection?, которые создают пользовательское промежуточное ПО для получения содержимого загружаемого файла в буферный массив, тогда мы можем использовать createFileFromText()
сохранить файл в хранилище Azure.
Вот фрагмент кода:
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);
})
Когда запрос прибывает в функцию, определенную в exports.post
весь запрос уже есть, поэтому вам не нужно его буферизовать. Вы можете упростить это, написав что-то вроде кода ниже.
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 });
}
}
Есть несколько вещей:
1. createFileFromText
может работать с простым текстом Но это не удастся для этого двоичного контента, так как он использует кодировку UTF-8.
Возможно, вы захотите сослаться на аналогичную проблему для BLOB-объектов по адресу: Сохранение BLOB-объектов (возможно, данных!), Возвращаемых вызовом AJAX в хранилище BLOB-объектов Azure, создает поврежденное изображение.
2. The createFileFromStream
или же createWriteStreamToExistingFile
\ createWriteStreamToNewFile
API хранилища Azure может помочь этой функции.
Обратите внимание, что эти API предназначены для потоков. Вам необходимо преобразовать ваш буфер / строку в теле запроса в поток. Вы можете обратиться к разделу Как обернуть буфер как поток stream2 для чтения?
За 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();
}
);
За createWriteStreamToNewFile
:
var writeStream = fileService.createWriteStreamToNewFile(req.headers.sharename,
req.headers.directorypath,
req.headers.filename,
data.length);
requestStream.pipe(writeStream);
3. В вашем коде есть несколько проблем
console.log(body); // This giving the result as undefined
Причина в том, что вы определяете var body
и это undefined
, Код body += chunk
все равно сделаю body
не определено.
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!");
}
});
Когда ошибка происходит в createFileFromStream
Это также может быть ошибка при передаче по сети, вы также можете вернуть код ошибки вместо statusCodes.OK
,