Reputation: 2821
I'm trying to use an Azure function to return a file from blob storage. As it stands, I've got it working, but it works inefficiently by reading the entire blob into memory, then writing it back out. This works for small files, but once they get big enough, it's very inefficient.
How can I make my function return the blob directly without it having to be read entirely into memory?
Here's what I'm using currently:
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;
}
}
}
My function.json
file:
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"get",
"post"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
}
],
"disabled": false
}
I'm not a C# developer, nor an Azure developer, so most of this stuff escapes me.
Upvotes: 5
Views: 9310
Reputation: 7768
Thanks @krishg for your answer!
Based on your code I figured out how to do the opposite, posting binary data from in my case Unity to a blob file as clean as possible.
[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");
}
Upvotes: 0
Reputation: 6508
I did like below for minimum memory footprint (without keeping the full blob into bytes in memory). Note that instead of binding to stream, I am binding to a ICloudBlob
instance (luckily, C# function supports several flavors of blob input binding) and returning open stream. Tested it using memory profiler and works fine with no memory leak even for large blobs.
NOTE: You don't need to seek to stream position 0 or flush or dispose (disposing would be automatically done on response end);
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");
}
}
}
Upvotes: 7
Reputation: 158
I like @CSharpRocks suggestion of creating a SAS and returning the link to blob storage, but I also found this article that might be relevant:
https://anthonychu.ca/post/azure-functions-static-file-server/
Here is the relevant code:
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(memoryStream);
response.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return response;
Upvotes: 4