如何从 Azure Blob 存储将文件下载到浏览器

Posted

技术标签:

【中文标题】如何从 Azure Blob 存储将文件下载到浏览器【英文标题】:How to download a file to browser from Azure Blob Storage 【发布时间】:2015-08-08 15:02:54 【问题描述】:

我已经成功列出了可用文件,但我需要知道如何将该文件传递到浏览器以供用户下载,而不必将其保存到服务器

这是我获取文件列表的方式

var azureConnectionString = CloudConfigurationManager.GetSetting("AzureBackupStorageConnectString");
var containerName = ConfigurationManager.AppSettings["FmAzureBackupStorageContainer"];
if (azureConnectionString == null || containerName == null)
    return null;

CloudStorageAccount backupStorageAccount = CloudStorageAccount.Parse(azureConnectionString);
var backupBlobClient = backupStorageAccount.CreateCloudBlobClient();
var container = backupBlobClient.GetContainerReference(containerName); 
var blobs = container.ListBlobs(useFlatBlobListing: true);
var downloads = blobs.Select(blob => blob.Uri.Segments.Last()).ToList();

【问题讨论】:

【参考方案1】:

虽然 blob 内容可以通过网络服务器流式传输,并通过浏览器传递给最终用户,但此解决方案会将负载放在网络服务器上,包括 CPU 和 NIC。

另一种方法是向最终用户提供要下载的所需 blob 的 uri,他们可以在 html 内容中单击该 uri。例如https://myaccount.blob.core.windows.net/mycontainer/myblob.ext.

问题在于内容是否是私有的,因为除非使用公共 blob,否则上述 uri 将不起作用。为此,您可以创建共享访问签名(或服务器存储的策略),然后生成附加到 uri 的散列查询字符串。这个新的 uri 将在给定的时间长度内有效(例如 10 分钟)。

这是一个为 blob 创建 SAS 的小示例:

var sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-5);
sasConstraints.SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(10);
sasConstraints.Permissions = SharedAccessBlobPermissions.Read;

var sasBlobToken = blob.GetSharedAccessSignature(sasConstraints);

return blob.Uri + sasBlobToken;

请注意,开始时间设置为过去几分钟。这是为了处理时钟漂移。这是full tutorial,我从中获取/修改了此代码示例。

通过使用直接 Blob 访问,您将完全绕过您的虚拟机/Web 角色实例/网站实例(减少服务器负载),并让您的最终用户直接从 Blob 存储中提取 Blob 内容。您仍然可以使用您的网络应用程序来处理许可、决定要交付哪些内容等。但是...这使您可以直接链接到 blob 资源,而不是通过您的网络服务器流式传输它们。

【讨论】:

更多关于代客钥匙模式的信息:msdn.microsoft.com/en-us/library/dn568102.aspx 当下载仅包含其 URI 的文件时出错时,我必须采用类似的方法。感谢大卫的提醒。 我很感激这已经有几年的历史了 - 但我在下载多个文件并压缩它们时遇到了这个问题。我为单个文件下载提供 SAS 网址 - 效果很好,但是当用户想要下载 100 个图像文件时,我不想向浏览器提供 100 个 SAS 网址以供下载,我想将它们合并到一个 zip 中文件。我想在不完全冻结服务器的情况下执行此操作,但尚未找到支持使用客户端资源压缩 blob 文件的前端框架。任何指导将不胜感激 如果我们必须在浏览器中预览文件而不将实际文件下载到用户本地存储,我们如何修改上述sn-p【参考方案2】:

一旦用户点击文件,服务器就会响应

var blob = container.GetBlobReferenceFromServer(option);

var memStream = new MemoryStream();
blob.DownloadToStream(memStream);

Response.ContentType = blob.Properties.ContentType;
Response.AddHeader("Content-Disposition", "Attachment;filename=" + option);
Response.AddHeader("Content-Length", blob.Properties.Length.ToString());
Response.BinaryWrite(memStream.ToArray());

非常感谢 Dhananjay Kumar 提供此解决方案

【讨论】:

所以,通过这样做,您确实意识到 blob 的全部内容将通过您的服务器进行路由,对吗?也就是说,blob 的内容将从 blob 存储传输到您的 VM/网站/Web 角色实例,然后通过 IIS / OWIN / 等传输到您的最终用户? 你会推荐什么?我无法让我的最终用户访问整个存储,因此 Azure 存储资源管理器无法工作。 我发布了另一个答案。 我发现这很慢,因为在最后一个字节来自 blob 存储之前,第一个字节不会进入浏览器。请改用 Andy 的两行答案,以减少内存开销和 10 毫秒的延迟!【参考方案3】:

如果您使用 ASP.NET(核心),您可以将内容流式传输到浏览器,而无需将文件保存在服务器上,并且使用 FileStreamResult(即 IActionResult)将是更优雅的解决方案。

var stream = await blob.OpenReadAsync();
return File(stream, blob.Properties.ContentType, option);

【讨论】:

如果您不想冒 SAS 令牌被盗的风险,这是最好的解决方案,毕竟它们只是在查询字符串中,因此即使使用 https 也不会加密。此外,如果您正在流式传输安全视频,请在返回之前在 File 对象上设置 EnableRangeProcessing,它会让浏览器跳过视频而不下载整个文件! @DanielBailey 我不同意,这会使整个 blob 不必要地下载到您的应用程序中,从而消耗服务器的带宽,也使过程变慢。您可以像 David 的回答一样设置 SAS 令牌的到期日期,您也不能只是窃取令牌并读取任何内容,附加到 Uri 的令牌仅用于该 blob,这意味着拥有该 Uri 的任何人都可以使用来自您的应用程序的 Uri,它将执行上面 Andy 的代码。 有点神奇,但是没有下载整个blob,只有范围,就像blob存储知道你只想要文件的一部分并且只将那部分流式传输到主机,然后主机只用一个很小的缓冲区转发它。 您需要在 SAS 令牌上设置一个非常小的到期日期,以防止任何对通过接入点进行基本记录的人不会立即下载任何内容,而且您不能让它太短因为浏览器需要时间进行往返并将请求直接返回到 blob 存储。至少如果您在 https 下进行 POST,他们将需要解密消息以获取获取 blob 的必要密钥,我认为更安全,但我不是 SAS 令牌保护专家。必须有一种安全的方法,但不是 GET。 @Alisson 您忘记了需要授权下载的场景。 SAS 令牌不会给你这个。登录您的应用程序的人可以使用 SAS 令牌提供这些链接,并且每个人都可以下载该文件。当它通过您的端点时,经过身份验证/未经授权的用户将收到 401/403。【参考方案4】:

我已经做了一个示例,您可以在其中上传和下载 blob 文件。

using System;
using System.Threading.Tasks;
using System.IO;
using Microsoft.Azure.Storage;
using Microsoft.Azure.Storage.Blob;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Collections.Generic;

namespace GetBackup

    class Program
    
        static async Task Main(string[] args)
        
            string Config_string = "";

            using (StreamReader SourceReader = File.OpenText(@"appsettings.json"))
            
                Config_string = await SourceReader.ReadToEndAsync();
            

            var config = (JObject)JsonConvert.DeserializeObject(Config_string);

            if(config["Application_type"].ToString()== "Backup")
            
                string Dir_path = config["Backup_Path"].ToString();
                string[] allfiles = Directory.GetFiles(Dir_path, "*.*", SearchOption.AllDirectories);


                string storageConnectionString = config["AZURE_STORAGE_CONNECTION_STRING"].ToString();
                CloudStorageAccount storageAccount;
                if (CloudStorageAccount.TryParse(storageConnectionString, out storageAccount))
                
                    CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
                    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference("rtddata");
                    //await cloudBlobContainer.CreateAsync();

                    string[] ExcludeFiles = config["Exception_File"].ToString().Split(',');

                    foreach (var file in allfiles)
                    
                        FileInfo info = new FileInfo(file);
                        if (!ExcludeFiles.Contains(info.Name))
                        
                            string folder = (Dir_path.Length < info.DirectoryName.Length) ? info.DirectoryName.Replace(Dir_path, "") : "";
                            folder = (folder.Length > 0) ? folder + "/" : "";
                            CloudBlockBlob cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(folder + info.Name);
                            await cloudBlockBlob.UploadFromFileAsync(info.FullName);
                        

                    

                
            
            
            else if (config["Application_type"].ToString() == "Restore")
            
                string storageConnectionString = config["AZURE_STORAGE_CONNECTION_STRING"].ToString();
                CloudStorageAccount storageAccount;
               
                if (CloudStorageAccount.TryParse(storageConnectionString, out storageAccount))
                
                    CloudBlobClient cloudBlobClient = storageAccount.CreateCloudBlobClient();
                    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference("rtddata");
                    string Dir_path = config["Restore_Path"].ToString();

                    IEnumerable<IListBlobItem> results = cloudBlobContainer.ListBlobs(null,true);  
                    foreach (IListBlobItem item in results)
                    
                        string name = ((CloudBlockBlob)item).Name;
                        if (name.Contains('/'))
                        
                            string[] subfolder = name.Split('/');
                            if (!Directory.Exists(Dir_path + subfolder[0]))
                            
                                Directory.CreateDirectory(Dir_path + subfolder[0]);
                            
                            
                          
                            CloudBlockBlob blockBlob = cloudBlobContainer.GetBlockBlobReference(name);
                            string path = (Dir_path + name);
                            blockBlob.DownloadToFile(path, FileMode.Create);
                    
                    

                
                    
            

            
            
        
    

【讨论】:

您好,先生,我正在使用几乎相同的“恢复”代码来下载文件,并且效果很好。问题是我下载的文件被用作 tmp 文件,所以,我需要删除它,使用这段代码它让我因为处理而出错。也许你能帮我解决这个问题

以上是关于如何从 Azure Blob 存储将文件下载到浏览器的主要内容,如果未能解决你的问题,请参考以下文章