【问题标题】:How to I pass a stream from Web API to Azure Blob storage without temp files?如何在没有临时文件的情况下将流从 Web API 传递到 Azure Blob 存储?
【发布时间】:2015-07-13 22:00:13
【问题描述】:

我正在开发一个文件上传经常发生的应用程序,并且文件大小可能非常大。

这些文件正在上传到 Web API,然后它将从请求中获取流,并将其传递给我的存储服务,然后将其上传到 Azure Blob 存储。

我需要确保:

  • 没有温度。文件写在 Web API 实例上
  • 请求流在传递到存储服务之前未完全读入内存(以防止内存不足异常)。

我查看了this article,它描述了如何禁用输入流缓冲,但是由于来自许多不同用户的许多文件上传同时发生,所以它实际上按照它在锡上所说的去做是很重要的。

这是我目前在控制器中的内容:

if (this.Request.Content.IsMimeMultipartContent())
{
    var provider = new MultipartMemoryStreamProvider();
    await this.Request.Content.ReadAsMultipartAsync(provider);
    var fileContent = provider.Contents.SingleOrDefault();

    if (fileContent == null)
    {
        throw new ArgumentException("No filename.");
    }

    var fileName = fileContent.Headers.ContentDisposition.FileName.Replace("\"", string.Empty);

    // I need to make sure this stream is ready to be processed by 
    // the Azure client lib, but not buffered fully, to prevent OoM.
    var stream = await fileContent.ReadAsStreamAsync();
}

我不知道如何可靠地测试这个。

编辑:我忘了说直接上传到 Blob 存储(绕过我的 API)是行不通的,因为我正在做一些大小检查(例如,这个用户可以上传 500mb 吗?这个用户有吗?用了他的配额?)。

【问题讨论】:

  • 您是否尝试过将输入流直接复制到 blob 存储?
  • 这就是我正在做的事情,但我需要确保在 blob 存储客户端开始上传之前我没有完全缓冲输入流,而且我不知道如何测试它是否真的发生了。
  • 您是否尝试过分析您的应用以查看它是否在读取之前对其进行缓冲?
  • 获取memory profiler 并测试您的应用。
  • 我发现该文件在发送到 Azure 之前确实已复制到内存中。这是个问题。

标签: c# asp.net azure asp.net-web-api azure-storage


【解决方案1】:

this Gist的帮助下解决了。

这是我使用它的方法,以及一个巧妙的“hack”来获取实际文件大小,而无需先将文件复制到内存中。哦,它的速度是原来的两倍 (显然)。

// Create an instance of our provider.
// See https://gist.github.com/JamesRandall/11088079#file-blobstoragemultipartstreamprovider-cs for implementation.
var provider = new BlobStorageMultipartStreamProvider ();

// This is where the uploading is happening, by writing to the Azure stream
// as the file stream from the request is being read, leaving almost no memory footprint.
await this.Request.Content.ReadAsMultipartAsync(provider);

// We want to know the exact size of the file, but this info is not available to us before
// we've uploaded everything - which has just happened.
// We get the stream from the content (and that stream is the same instance we wrote to).
var stream = await provider.Contents.First().ReadAsStreamAsync();

// Problem: If you try to use stream.Length, you'll get an exception, because BlobWriteStream
// does not support it.

// But this is where we get fancy.

// Position == size, because the file has just been written to it, leaving the
// position at the end of the file.
var sizeInBytes = stream.Position;

瞧,您获得了上传文件的大小,而无需将文件复制到您的 Web 实例的内存中。

至于在文件上传之前获取文件长度,这并不容易,我不得不求助于一些相当不愉快的方法来获得一个近似值。

BlobStorageMultipartStreamProvider

var approxSize = parent.Headers.ContentLength.Value - parent.Headers.ToString().Length;

这给了我一个非常接近的文件大小,相差几百字节(取决于我猜的 HTTP 标头)。这对我来说已经足够好了,因为我的配额执行可以接受被削减的几个字节。

只是为了炫耀,这里是内存占用,由任务管理器中的异常准确和高级性能选项卡报告。

之前 - 使用 MemoryStream,在上传之前将其读入内存

之后 - 直接写入 Blob 存储

【讨论】:

    【解决方案2】:

    我认为更好的方法是让您从客户端直接访问 Azure Blob 存储。通过利用 Azure 存储中的 CORS 支持,您可以消除 Web API 服务器上的负载,从而为您的应用程序带来更好的整体规模。

    基本上,您将创建一个共享访问签名 (SAS) URL,您的客户端可以使用该 URL 将文件直接上传到 Azure 存储。出于安全原因,建议您限制 SAS 的有效期。 here 提供了生成 SAS URL 的最佳实践指南。

    对于您的特定场景,请查看 Azure 存储团队的 this blog,他们在其中讨论了针对该特定场景使用 CORS 和 SAS。还有一个示例应用程序,因此它应该可以为您提供所需的一切。

    【讨论】:

    • 您仍然可以使用此解决方案。在生成 SAS URL 的方法中,您还可以返回您为用户维护的任何数据配额,例如剩余的存储空间。在您的 JavaScript 中,添加一些逻辑以查看您的字节数组是否大于您返回给用户的配额,如果是,则在客户端显示错误。
    • 这是一个问题,因为第 3 方将与我的 API 集成,所以没有什么可以阻止他们忽略配额。永远不要相信客户。 :)
    • 是的,但是您不会为此产生任何摄取成本,而且存储成本非常便宜。所以,我建议在服务器端也进行验证。
    • 我无法在服务器端验证它,因为流没有触及我的服务器。另一件事是我在 SQL 数据库中存储了对该文件的“引用”。它必须通过我的服务器。
    • 我正在考虑做一个后台工作。您已经在某个地方这样做了,因为您知道用户的配额是多少。每次用户上传文件以启动检查用户配额的作业时,您都可以将消息放入队列。无论如何,只需要考虑一些想法。
    猜你喜欢
    • 2021-06-02
    • 2021-06-03
    • 2020-01-28
    • 2020-10-03
    • 1970-01-01
    • 2013-02-03
    • 2020-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多