将 Cache-Control 和 Expires 标头添加到 Azure 存储 Blob

Posted

技术标签:

【中文标题】将 Cache-Control 和 Expires 标头添加到 Azure 存储 Blob【英文标题】:Add Cache-Control and Expires headers to Azure Storage Blobs 【发布时间】:2011-05-29 07:17:39 【问题描述】:

我正在使用 Azure 存储来提供静态文件 blob,但我想在提供时将 Cache-Control 和 Expires 标头添加到文件/blob 以降低带宽成本。

像 CloudXplorer 和 Cerebrata 的 Cloud Storage Studio 这样的应用程序提供了在容器和 blob 上设置元数据属性的选项,但在尝试添加 Cache-Control 时会感到不安。

有人知道是否可以为文件设置这些标题吗?

【问题讨论】:

我后来发现可以在单个 blob 上设置 Cache-Control,但我有超过 500,000 个文件/ blob 分布在 1000 个容器中,我想为其设置缓存标头。有人知道在所有 blob 上设置此标头的有效方法吗? 我想我可能已经找到了适用于 Windows Azure 的 CloudBerry Explorer 的解决方案,但虽然它看起来可以批量更新标头,但实际上并不能正常工作。似乎这是一个已知的错误,但它自 2009 年 3 月以来仍然存在,所以我不会屏住呼吸等待修复! cloudberrylab.com/forum/default.aspx?g=posts&t=3047 我也用 CloudBerry 试过。我可以设置缓存控制标头。但是保存后,它会删除设置。也许是因为它来自“用户定义”而不是“系统”类型??? Gavin,您是如何使用 Cloudberry Explorer 为单个文件设置缓存控制标头的?我已经尝试过了,它似乎无法正常工作。 【参考方案1】:

我必须对大约 60 万个 blob 运行批处理作业,并发现 2 件事确实有帮助:

    从同一数据中心的辅助角色运行操作。只要 Azure 服务在同一个关联组中,它们之间的速度就会非常快。此外,没有数据传输费用。

    并行运行操作。 .net v4 中的任务并行库 (TPL) 使这变得非常容易。下面是为容器中的每个 blob 并行设置缓存控制标头的代码:

    // get the info for every blob in the container
    var blobInfos = cloudBlobContainer.ListBlobs(
        new BlobRequestOptions()  UseFlatBlobListing = true );
    Parallel.ForEach(blobInfos, (blobInfo) =>
    
        // get the blob properties
        CloudBlob blob = container.GetBlobReference(blobInfo.Uri.ToString());
        blob.FetchAttributes();
    
        // set cache-control header if necessary
        if (blob.Properties.CacheControl != YOUR_CACHE_CONTROL_HEADER)
        
            blob.Properties.CacheControl = YOUR_CACHE_CONTROL_HEADER;
            blob.SetProperties();
        
    );
    

【讨论】:

显然您不能再将完整的 URI 传递给 GetBlobReference。我已经用我为我的一个容器编写的一些最新代码编辑了答案。 如果有人为此编写一个实用程序也很酷,考虑到人们想要这样做是多么普遍! 我对同一个容器中的大约 200,000 个 blob 运行此脚本,并获得 409 服务器响应。有谁知道是什么意思吗? 是的,Azure SDK for Java 非常相似:smile:【参考方案2】:

Cerebrata Cloud Storage Studio 的最新版本 v2011.04.23.00 支持在单个 blob 对象上设置缓存控制。右键单击 blob 对象,选择“查看/编辑 Blob 属性”,然后设置 Cache-Control 属性的值。 (例如public, max-age=2592000)。

如果您使用 curl 检查 blob 对象的 HTTP 标头,您将看到缓存控制标头返回您设置的值。

【讨论】:

Azure 资源管理器也具有此功能,无需试用即可免费使用。 cerebrata.com/products/azure-explorer/introduction【参考方案3】:

最新的CloudBerry Explorer 现在支持缓存控制: http://www.cloudberrylab.com/forum/default.aspx?g=posts&t=3047

【讨论】:

【参考方案4】:

回答可能为时已晚,但最近我想以不同的方式做同样的事情,我有图像列表,需要使用 powershell 脚本应用(当然在 Azure 存储程序集的帮助下) 希望将来有人会发现这很有用。

Set Azure blob cache-control using powershell script中给出的完整解释

Add-Type -Path "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\v2.3\ref\Microsoft.WindowsAzure.StorageClient.dll"

$accountName = "[azureaccountname]"
$accountKey = "[azureaccountkey]"
$blobContainerName = "images"

$storageCredentials = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey -ArgumentList $accountName,$accountKey
$storageAccount = New-Object Microsoft.WindowsAzure.CloudStorageAccount -ArgumentList $storageCredentials,$true
#$blobClient = $storageAccount.CreateCloudBlobClient()
$blobClient =  [Microsoft.WindowsAzure.StorageClient.CloudStorageAccountStorageClientExtensions]::CreateCloudBlobClient($storageAccount)

$cacheControlValue = "public, max-age=604800"

echo "Setting cache control: $cacheControlValue"

Get-Content "imagelist.txt" | foreach      
    $blobName = "$blobContainerName/$_".Trim()
    echo $blobName
    $blob = $blobClient.GetBlobReference($blobName)
    $blob.Properties.CacheControl = $cacheControlValue
    $blob.SetProperties()

【讨论】:

【参考方案5】:

这是 Joel Fillmore 使用 Net 5 和 V12 of Azure.Storage.Blobs 回答的更新版本。 (旁白:如果可以在父容器上设置默认的标头属性不是很好吗?)

Azure 能够运行“WebJobs”,而不是创建网站并使用 WorkerRole。您可以在存储帐户所在的同一数据中心的网站上按需运行任何可执行文件,以设置缓存标头或任何其他标头字段。

    在您的存储帐户在同一数据中心中创建一个一次性的临时网站。不要担心亲和群体;创建一个空的 ASP.NET 站点或任何其他简单站点。内容不重要。我需要至少使用 B1 服务计划,否则 WebJob 会在 5 分钟后中止。 使用下面的代码创建一个控制台程序,该程序与更新的 Azure 存储 API 一起使用。编译发布,然后将可执行文件和所有必需的 DLL 压缩到 .zip 文件中,或者直接从 VisualStudio 发布并跳过下面的 #3。 创建一个 WebJob 并上传第 2 步中的 .zip 文件。 运行 Web 作业。写入控制台的所有内容都可以在从 WebJob 控制页面创建和访问的日志文件中查看。 删除临时网站,或将其更改为免费层(在“Scale Up”下)。

下面的代码为每个容器运行一个单独的任务,我每分钟更新多达 10 万个标头(取决于一天中的时间?)。没有出口费用。

using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AzureHeaders

    class Program
    
        private static string connectionString = "DefaultEndpointsProtocol=https;AccountName=REPLACE_WITH_YOUR_CONNECTION_STRING";
        private static string newCacheControl = "public, max-age=7776001"; // 3 months
        private static string[] containersToProcess =  "container1", "container2" ;

        static async Task Main(string[] args)
        
            BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);

            var tasks = new List<Task>();
            foreach (var container in containersToProcess)
            
                BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(container);
                tasks.Add(Task.Run(() => UpdateHeaders(containerClient, 1000)));  // I have no idea what segmentSize should be!
            
            Task.WaitAll(tasks.ToArray());
        

        private static async Task UpdateHeaders(BlobContainerClient blobContainerClient, int? segmentSize)
        
            int processed = 0;
            int failed = 0;
            try
            
                // Call the listing operation and return pages of the specified size.
                var resultSegment = blobContainerClient.GetBlobsAsync()
                    .AsPages(default, segmentSize);

                // Enumerate the blobs returned for each page.
                await foreach (Azure.Page<BlobItem> blobPage in resultSegment)
                
                    var tasks = new List<Task>();

                    foreach (BlobItem blobItem in blobPage.Values)
                    
                        BlobClient blobClient = blobContainerClient.GetBlobClient(blobItem.Name);
                        tasks.Add(UpdateOneBlob(blobClient));
                        processed++;
                    
                    Task.WaitAll(tasks.ToArray());
                    Console.WriteLine($"Container blobContainerClient.Name processed: processed");
                
            
            catch (RequestFailedException e)
            
                Console.WriteLine(e.Message);
                failed++;
            
            Console.WriteLine($"Container blobContainerClient.Name processed: processed, failed: failed");
        

        private static async Task UpdateOneBlob(BlobClient blobClient) 
            Response<BlobProperties> propertiesResponse = await blobClient.GetPropertiesAsync();
            BlobHttpHeaders httpHeaders = new BlobHttpHeaders
            
                // copy any existing headers you wish to preserve
                ContentType = propertiesResponse.Value.ContentType,
                ContentHash = propertiesResponse.Value.ContentHash,
                ContentEncoding = propertiesResponse.Value.ContentEncoding,
                ContentDisposition = propertiesResponse.Value.ContentDisposition,
                // update CacheControl
                CacheControl = newCacheControl  
            ;
            await blobClient.SetHttpHeadersAsync(httpHeaders);
        
    

【讨论】:

谢谢,这段代码为我节省了一些时间。他们改变了 WebJobs。显然,您不能再指定按需运行。我只是将它创建为一个连续的作业,然后查看日志以确保它完成并手动停止作业。【参考方案6】:

有时,最简单的答案就是最好的答案。如果您只想管理少量 Blob,可以使用 Azure Management 更改 Blob 的标头/元数据。

    单击存储,然后单击存储帐户名称。 点击容器标签,然后点击一个容器。 点击一个blob,然后点击屏幕底部的编辑

在该编辑窗口中,您可以自定义 缓存控制内容编码内容语言等。

注意:您目前无法从Azure Portal编辑此数据

【讨论】:

不幸的是,他们已经从我们这里拿走了这个功能。使用旧的 Azure 管理时,您现在会看到“新的存储家园。访问我们的新门户”。但该功能仍然不在门户中。 该功能现在又回到了门户中,可以使用当前处于预览状态的“存储资源管理器”来完成。 @StephenMcDowell,我刚刚检查了我们帐户中的一个 blob,但我没有看到任何地方可以编辑缓存控制属性或其他类似属性。也许我找错地方了。你在Portal哪里找到这个? @JohnWasham 通过“存储资源管理器(预览版)”,您可以访问 blob 容器。在 blob 容器中,如果您选择一个项目并右键单击,我会看到“属性..”作为最后一个菜单项。这将打开另一个刀片,如果您滚动浏览顶部,我可以看到可以输入缓存控件的文本框 这里是docs.microsoft.com/en-us/azure/cdn/…的官方文档【参考方案7】:

通过 PowerShell 脚本设置存储 blob 缓存控制属性

https://gallery.technet.microsoft.com/How-to-set-storage-blob-4774aca5

#creat CloudBlobClient 
Add-Type -Path "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\v2.3\ref\Microsoft.WindowsAzure.StorageClient.dll" 
$storageCredentials = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey -ArgumentList $StorageName,$StorageKey 
$blobClient =   New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($BlobUri,$storageCredentials) 
#set Properties and Metadata 
$cacheControlValue = "public, max-age=60480" 
foreach ($blob in $blobs) 
 
  #set Metadata 
  $blobRef = $blobClient.GetBlobReference($blob.Name) 
  $blobRef.Metadata.Add("abcd","abcd") 
  $blobRef.SetMetadata() 

  #set Properties 
  $blobRef.Properties.CacheControl = $cacheControlValue 
  $blobRef.SetProperties() 

【讨论】:

【参考方案8】:

这是 Joel Fillmore 使用 WindowsAzure.Storage v9.3.3 的答案的更新版本。请注意,ListBlobsSegmentedAsync 返回的页面大小为 5,000,这就是使用 BlobContinuationToken 的原因。

    public async Task BackfillCacheControlAsync()
    
        var container = await GetCloudBlobContainerAsync();
        BlobContinuationToken continuationToken = null;

        do
        
            var blobInfos = await container.ListBlobsSegmentedAsync(string.Empty, true, BlobListingDetails.None, null, continuationToken, null, null);
            continuationToken = blobInfos.ContinuationToken;
            foreach (var blobInfo in blobInfos.Results)
            
                var blockBlob = (CloudBlockBlob)blobInfo;
                var blob = await container.GetBlobReferenceFromServerAsync(blockBlob.Name);
                if (blob.Properties.CacheControl != "public, max-age=31536000")
                
                    blob.Properties.CacheControl = "public, max-age=31536000";
                    await blob.SetPropertiesAsync();
                
                           
        
        while (continuationToken != null);
    

    private async Task<CloudBlobContainer> GetCloudBlobContainerAsync()
    
        var storageAccount = CloudStorageAccount.Parse(_appSettings.AzureStorageConnectionString);
        var blobClient = storageAccount.CreateCloudBlobClient();
        var container = blobClient.GetContainerReference("uploads");
        return container;
    

【讨论】:

【参考方案9】:

这是一个批处理/unix 脚本,适用于不使用 PowerShell 的 Windows 机器上的所有人。以下脚本循环遍历所有 blob,并分别在 blob 上设置 Content-Cache 属性(Cache-Control http 标头)。

不幸的是,没有办法同时在多个 blob 上设置属性,因此这是一项耗时的任务。每个 blob 通常需要大约 1-2 秒。但是,正如 Jay Borseth 指出的那样,如果从与您的存储帐户位于同一数据中心的服务器上运行该过程,则会显着加快该过程。

# Update Azure Blob Storage blob's cache-control headers
# /content-cache properties
# 
# Quite slow, since there is no `az storage blob update-batch`
#
# Created by Jon Tingvold, March 2021
#
#
# If you want progress, you need to install pv:
# >>> brew install pv  # Mac
# >>> sudo apt install pv  # Ubuntu
#

set -e  # exit when any command fails

AZURE_BLOB_CONNECTION_STRING='DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=XXXXXXXXXXXX;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='
CONTAINER_NAME=main

BLOB_PREFIX='admin/'
CONTENT_CACHE='max-age=3600'
NUM_RESULTS=10000000  # Defaults to 5000

BLOB_NAMES=$(az storage blob list --connection-string $AZURE_BLOB_CONNECTION_STRING --container-name $CONTAINER_NAME --query '[].name' --output tsv --num-results $NUM_RESULTS --prefix $BLOB_PREFIX)
NUMBER_OF_BLOBS=$(echo $BLOB_NAMES | wc -w)

echo "Ask Azure for files in Blob Storage ..."
echo "Set content-cache on $NUMBER_OF_BLOBS blobs ..."

for BLOB_NAME in $BLOB_NAMES
do
  az storage blob update --connection-string $AZURE_BLOB_CONNECTION_STRING --container-name $CONTAINER_NAME --name $BLOB_NAME --content-cache $CONTENT_CACHE > /dev/null;
  echo "$BLOB_NAME"

# If you don't have pv install, uncomment  everything after done
done | cat | pv -pte --line-mode --size $NUMBER_OF_BLOBS > /dev/null

【讨论】:

以上是关于将 Cache-Control 和 Expires 标头添加到 Azure 存储 Blob的主要内容,如果未能解决你的问题,请参考以下文章

使用 ETag 和 Expires/Cache-control 的缓存策略,没有资产版本/ID

Chromecast 客户端是不是支持 Cache-Control 或 Expires 标头?

图解浏览器缓存

Http头介绍:Expires,Cache-Control,Last-Modified,ETag

前端面试题

转载浏览器缓存详解:expires cache-control last-modified