Загрузка файла в хранилище файлов 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. Пожалуйста, помогите мне исправить это.


person Sniper    schedule 17.12.2015    source источник


Ответы (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. Функция createFileFromStream API хранилища Azure, которую вы пытаетесь использовать, ожидает поток.

В то же время лучшее, что вы можете получить при разборе тела запроса вручную, — это массив байтов. К сожалению, мобильные сервисы Azure используют NodeJS версии 0.8, а это значит, что нет простого способа построить читаемый поток из байтового массива, и вам придется собирать свой собственный поток, подходящий для API хранилища Azure. Немного изоленты и [email protected] должны подойти.

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 [email protected]

<сильный>3. Чтобы увеличить лимит загрузки по умолчанию до 1 МБ, укажите для службы значение MS_MaxRequestBodySizeKB.

MS_MaxRequestBodySizeKB

Однако имейте в виду, что, поскольку вы передаете данные в кодировке Base64, вы должны учитывать эти накладные расходы. Итак, чтобы поддерживать загрузку файлов размером до 20 МБ, вы должны установить MS_MaxRequestBodySizeKB примерно на 20 * 1024 * 4/3 = 27307.

person Roman Pletnev    schedule 04.01.2016
comment
Спасибо @Roman Pletnev. Он работает нормально. Но если файл больше МБ, выдает ошибку {"code":413,"error":"Error: Request body maximum size limit was exceeded.. По умолчанию ContentMD5 отключен. Есть ли другой вариант загрузки больших файлов (менее 20 МБ). - person Sniper; 06.01.2016

Я считаю, что самый простой способ — использовать 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);
person Pier-Luc Gendreau    schedule 17.12.2015

Мы можем использовать этот ответ потока на 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);
})
person Gary Liu    schedule 18.12.2015
comment
Спасибо #Грэй. Я пробовал это, но все еще показывал ту же проблему. Теперь элемент управления находится внутри req.on('data', function (chunk) {. Пожалуйста, помогите мне в этом. - person Sniper; 22.12.2015
comment
Привет, о какой же проблеме вы говорите? проблема timed out или другая в вашем разделе обновлений? - person ; 23.12.2015
comment
Проблема time out. - person Sniper; 23.12.2015
comment
Привет @Sniper, я просматриваю ваш вопрос и изменил свой ответ как обновленный раздел. - person ; 23.12.2015
comment
@GaryLiu-MSFT, я не могу найти и обновить ваш ответ. Не могли бы вы обновить его. - person Sniper; 24.12.2015

Когда запрос поступает в функцию, определенную в 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 });
    }
}
person carlosfigueira    schedule 17.12.2015
comment
Спасибо #карлосфигейра. Я пробовал это, но это дает новую ошибку. {ошибка: поток параметров для функции createFileFromStream должен быть объектом}. Я попытался объявить тело как объект, но по-прежнему показывал ту же проблему. - person Sniper; 22.12.2015
comment
Вы должны быть в состоянии использовать createFileFromText, так как у вас уже есть все содержимое файла. - person carlosfigueira; 22.12.2015
comment
Кстати, я обновил ответ, чтобы использовать эту функцию. - person carlosfigueira; 22.12.2015
comment
Я попробовал createFileFromText, он отлично работает для текстового файла. Но если мы загрузим изображение, используя это, файл изображения будет поврежден. - person Sniper; 23.12.2015

Есть несколько вещей:

<сильный>1. createFileFromText может работать с обычным текстом. Но это не сработает для этого двоичного содержимого, поскольку оно использует кодировку UTF-8.

Возможно, вы захотите обратиться к аналогичной проблеме для BLOB-объектов по адресу: Сохранение большого двоичного объекта (возможно, данных!), возвращенного вызовом AJAX, в хранилище BLOB-объектов Azure создает поврежденный образ

<сильный>2. В этом может помочь 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.

person Yang Xia - Microsoft    schedule 12.01.2016