如何从HTTP触发的Azure函数返回Blob?

时间:2019-04-03 16:00:50

标签: c# azure azure-functions azure-storage-blobs

我正在尝试使用Azure函数从Blob存储返回文件。就目前而言,它已经可以工作了,但是通过将整个Blob读入内存,然后将其写回,它的工作效率很低。这适用于小文件,但是一旦它们变得足够大,效率就非常低。

如何使我的函数直接返回blob,而不必将其完全读取到内存中?

这是我当前正在使用的:

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, Binder binder, TraceWriter log)
{
    // parse query parameter
    string fileName = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
        .Value;

    string binaryName = $"builds/{fileName}";

    log.Info($"Fetching {binaryName}");

    var attributes = new Attribute[]
    {    
        new BlobAttribute(binaryName, FileAccess.Read),
        new StorageAccountAttribute("vendorbuilds")
    };

    using (var blobStream = await binder.BindAsync<Stream>(attributes))
    {
        if (blobStream == null) 
        {
            return req.CreateResponse(HttpStatusCode.NotFound);
        }

        using(var memoryStream = new MemoryStream())
        {
            blobStream.CopyTo(memoryStream);
            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Content = new ByteArrayContent(memoryStream.ToArray());
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = fileName };
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            return response;
        }
    }
}

我的function.json文件:

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

我既不是C#开发人员,也不是Azure开发人员,因此大部分内容使我无所适从。

3 个答案:

答案 0 :(得分:2)

我在下面做了类似的事情,以最小化内存占用(不将完整的blob保留在内存中的字节中)。请注意,不是绑定到流,而是绑定到ICloudBlob实例(幸运的是,C#函数支持blob input binding的几种风格)并返回开放流。使用内存分析器对其进行了测试,即使对于较大的Blob,也可以正常工作且没有内存泄漏。

注意:您无需寻求流位置0或刷新或处置(处置将在响应端自动完成);

using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Storage.Blob;

namespace TestFunction1
{
   public static class MyFunction
   {
        [FunctionName("MyFunction")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = "video/{fileName}")] HttpRequest req,
            [Blob("test/{fileName}", FileAccess.Read, Connection = "BlobConnection")] ICloudBlob blob,
            ILogger log)
        {
            var blobStream = await blob.OpenReadAsync().ConfigureAwait(false);
            return new FileStreamResult(blobStream, "application/octet-stream");
        }
   }
}

答案 1 :(得分:0)

我喜欢创建SAS并将链接返回到Blob存储的@CSharpRocks建议,但我也发现本文可能相关:

https://anthonychu.ca/post/azure-functions-static-file-server/

以下是相关代码:

var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(memoryStream);
response.Content.Headers.ContentType = 
    new MediaTypeHeaderValue("application/octet-stream");
return response;

答案 2 :(得分:0)

感谢@krishg 的回答!

根据你的代码,我想出了如何做相反的事情,在我的例子中将二进制数据从 Unity 发布到尽可能干净的 blob 文件。

[FunctionName("Upload")]
        public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "options", "put", Route = "upload/{name}")] HttpRequest req,
        [Blob("container/{name}.bin", FileAccess.Write)] ICloudBlob blob, ILogger log)
        {
            if (req.Method == "OPTIONS")
            {                
                req.HttpContext.Response.Headers.Add("access-control-allow-methods", "PUT, OPTIONS");
                req.HttpContext.Response.Headers.Add("access-control-allow-headers", "Content-Type");
                return new EmptyResult();
            }

            await blob.UploadFromStreamAsync(req.Body).ConfigureAwait(false);            
            return new OkObjectResult("data saved");
        }