使用node.js上传文件到Azure文件存储

18
我们正在尝试创建一个WebService,使用node.js服务将文件上传到Azure文件存储。以下是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":"Parameter stream for function createFileFromStream should be an object"}

我是node.js的新手,请帮我解决这个问题。

5个回答

8
这里有几个问题。让我们一一解决。
1. 在您的Java客户端中,您不能仅将二进制数据转储到Azure移动服务连接中。
原因是Azure移动服务有两个主体解析器,确保无论如何,请求正文都会被解析。 因此,虽然您可以通过指定不常见的内容类型来绕过Express主体解析器,但仍将遇到Azure主体解析器,它会天真地假设它是一个UTF-8字符串而破坏您的数据流。
因此,唯一的选择是通过指定Express无法处理的内容类型跳过Express解析器,然后通过使用Base64编码对二进制数据进行编码来与Azure解析器配合使用。
因此,在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 Azure存储API函数需要一个流。同时,手动解析请求体时最好得到的是一个字节数组。不幸的是,Azure移动服务使用NodeJS版本0.8,这意味着没有简单的方法从字节数组构造可读取的流,因此您将不得不自己组装适用于Azure存储API的流。一些胶带和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中指定它们无法工作,您可以随时转到此link并通过控制台手动安装它们。
cd site\wwwroot
npm install azure-storage
npm install base64-js
npm install stream@0.0.1

3. 要增加默认的1Mb上传限制,为您的服务指定MS_MaxRequestBodySizeKB。

MS_MaxRequestBodySizeKB

请记住,由于您正在传输Base64编码的数据,因此必须考虑这种开销。因此,为了支持上传最大为20Mb的文件,您需要将MS_MaxRequestBodySizeKB设置为大约20 * 1024 * 4/3 = 27307。

感谢@Roman Pletnev。它运行良好。但是,如果文件超过1MB,则会抛出错误{"code":413,"error":"Error: Request body maximum size limit was exceeded.。默认情况下,ContentMD5已禁用。是否有其他选项可上传更大的文件(低于20MB)? - Sniper

4
我发现最简单的方法是使用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);

3

当请求到达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 });
    }
}

谢谢 #carlosfigueira。我尝试了这个,但是它给出了一个新的错误:"{"error":"Parameter stream for function createFileFromStream should be an object"}"。我尝试将"body"声明为对象,但仍然显示相同的问题。 - Sniper
你应该可以使用 createFileFromText,因为你已经拥有了所有文件内容。 - carlosfigueira
顺便说一下,我更新了答案以使用该函数。 - carlosfigueira
我尝试使用createFileFromText函数创建文本文件,对于文本文件它可以正常工作。但是如果我们使用该函数上传图像文件,则图像文件会损坏。 - Sniper

3
我们可以利用SO线程中的答案 如何通过HttpURLConnection从Android客户端向Node.js服务器发送图像?,创建一个自定义中间件将上传文件内容获取到缓冲数组中,然后我们可以使用createFileFromText()将文件存储在Azure Storage中。
以下是代码片段:
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);
})

谢谢 #Gray。我尝试了这个方法,但仍然出现相同的问题。控件现在位于“req.on('data', function (chunk) {”内部。请帮助我解决这个问题。 - Sniper
你好,你所指的同样的问题是什么?是“超时”问题还是你更新部分提到的其他问题? - user5701745
超时问题。 - Sniper
嗨@Sniper,我正在查看您的问题,并修改了我的答案作为更新部分。 - user5701745
@GaryLiu-MSFT,我在你的回答中找不到更新。你能否请更新一下呢? - Sniper

0

有几个要点:

1. createFileFromText 可以处理纯文本。但对于二进制内容,它将失败,因为它使用 UTF-8 编码。

您可能需要参考类似的问题:Saving blob (might be data!) returned by AJAX call to Azure Blob Storage creates corrupt image

2. createFileFromStreamcreateWriteStreamToExistingFile \ createWriteStreamToNewFile Azure 存储 API 可能是可以帮助的函数。

请注意,这些 API 针对流。您需要将请求正文中的缓冲区/字符串转换为流。您可以参考 How to wrap a buffer as a stream2 Readable stream?

对于 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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接