Я написал функцию Azure, которая загружает большой двоичный объект по триггеру HTTP. Когда я отлаживаю эту функцию локально, выполнение кода проходит через всю функцию, после возврата, а затем выдает неопределенную ошибку перед фактической отправкой возврата.
Вот функция:
public class GetBlob
{
private readonly IUpgradeDataRepositoryFactory _upgradeDataRepository;
private readonly IAuthorizationHelper _authorizationHelper;
public GetBlob(IUpgradeDataRepositoryFactory upgradeDataRepository, IAuthorizationHelper authorizationHelper){
_upgradeDataRepository = upgradeDataRepository;
_authorizationHelper = authorizationHelper;
}
[FunctionName("GetBlob")]
public HttpResponseMessage Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "GetBlob/{id}")] HttpRequest req,
string id, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var clientIp = (req.Headers["X-Forwarded-For"].FirstOrDefault() ?? "").Split(new char[] { ':' }).FirstOrDefault();
try{
_authorizationHelper.AssertRequestIsAuthorized(req);
var blobInfo = _upgradeDataRepository.UpdateDataItemRepository.Query(q => q.BlobName == id).FirstOrDefault();
if (blobInfo != null){
using (var blobStream = GetBlobStream(id))
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(blobStream);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = blobInfo.BlobFriendlyName;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
log.LogInformation(string.Format("Returning Update Blob: {0} to ip: {1}", id, clientIp));
return result;
}
}
else
{
log.LogInformation(string.Format("Could not find update item record for blob id: {0} Ip: {1}", id, clientIp));
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
}
catch (AuthorizationException){
log.LogInformation("Authorization failed");
var message = new HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized
};
throw new HttpResponseException(message);
}
catch (Exception ex){
log.LogError(string.Format("Error getting update blob: {0} for IP: {1}", id, clientIp));
log.LogError(ex.ToString());
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
finally{
log.LogInformation(string.Format("Exiting Get Update Blob: {0} for ip: {1}", id, clientIp));
}
}
private static CloudStorageAccount GetStorageAccount()
{
var connParameter= "StorageAccountConnectionString";
var connectionString = System.Environment.GetEnvironmentVariable($"{connParameter}");
return CloudStorageAccount.Parse(connectionString);
}
private static string GetContainerName(){
var containerParameter= "UpdateBlobContainerName";
var containerName = System.Environment.GetEnvironmentVariable($"{containerParameter}");
return containerName;
}
private static Stream GetBlobStream(string id)
{
CloudStorageAccount account = GetStorageAccount();
CloudBlobClient blobClient = account.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(GetContainerName());
var blob = container.GetBlobReference(id);
return blob.OpenReadAsync().Result;
}
}
(Примечание 1. Здесь используется HttpResponseMessage, поскольку он заменяет старый API и требует интеграции со старым кодом)
(Примечание 2. Хотя я не поделился кодом для AuthenticationHelper или UpgradeDataRepository, оба этих объекта были успешно использованы и протестированы в отдельной рабочей функции. UpgradeDataRepository подключается к базе данных SQL Azure и запрашивает некоторую информацию — это происходит успешно. как в рабочей функции, так и в этой функции. Однако рабочая функция не имеет доступа к хранилищу BLOB-объектов, поэтому ошибка должна быть связана с использованием хранилища BLOB-объектов.)
Вот вывод терминала:
[2024-03-27T19:04:34.288Z] Executing 'GetBlob' (Reason='This function was programmatically called via the host APIs.', Id=5b3af3ef-c61e-4ad7-a582-4982ba9d6b3e)
[2024-03-27T19:04:34.300Z] C# HTTP trigger function processed a request.
[2024-03-27T19:04:41.904Z] Returning Update Blob: 729ee33f-e283-420a-b898-648718139e3a to ip:
[2024-03-27T19:04:41.906Z] Exiting Get Update Blob: 729ee33f-e283-420a-b898-648718139e3a for ip:
[2024-03-27T19:04:41.907Z] Executed 'GetBlob' (Succeeded, Id=5b3af3ef-c61e-4ad7-a582-4982ba9d6b3e, Duration=7650ms)
[2024-03-27T19:04:41.915Z] An unhandled host error has occurred.
[2024-03-27T19:04:41.916Z] Microsoft.WindowsAzure.Storage: Object reference not set to an instance of an object.
Обратите внимание, что сообщение «Выполнено 'GetBlob' (Успешно)» появляется вне/после выполнения моей функции, за которым следует сообщение об ошибке. Возврат не доходит до приложения, которое я тестирую.
Сообщение об ошибке расплывчато, и мой отладчик не улавливает шаг, на котором возникает эта ошибка, но это определенно происходит после того, как выполняется все остальное, что показано. Я даже проверил, что блоб успешно загружен и прикреплен к ответу.
Единственное, что я могу предположить на данный момент, это несоответствие версий? На всякий случай вот мой файл проекта:
<Project Sdk = "Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include = "Microsoft.Azure.Functions.Extensions" Version = "1.1.0" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.Design" Version = "6.0.14">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include = "Microsoft.EntityFrameworkCore.SqlServer" Version = "6.0.14" />
<PackageReference Include = "Microsoft.EntityFrameworkCore.Tools" Version = "6.0.14">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include = "Microsoft.NET.Sdk.Functions" Version = "4.2.0" />
</ItemGroup>
<ItemGroup>
<None Update = "host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update = "local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
</Project>
ОБНОВЛЕНИЕ: я попробовал заменить Microsoft.Windows.Azure на Azure.Storage.Blobs:
private static BlobServiceClient GetStorageAccount()
{
var connParameter= "StorageAccountConnectionString";
var connectionString = System.Environment.GetEnvironmentVariable($"{connParameter}");
return new BlobServiceClient(connectionString);
}
private static Stream GetBlobStream(string id)
{
BlobServiceClient account = GetStorageAccount();
BlobContainerClient container = account.GetBlobContainerClient(GetContainerName());
var blob = container.GetBlobClient(id);
return blob.OpenReadAsync().Result;
}
Теперь я получаю немного другую ошибку в терминале после того, как кажется, что все выполняется успешно:
[2024-03-28T12:09:20.144Z] Executed 'GetBlob' (Succeeded, Id=08567df1-e23f-4973-a9e5-a9750f0ff9ee, Duration=4304ms)
[2024-03-28T12:09:20.322Z] An unhandled host error has occurred.
[2024-03-28T12:09:20.323Z] System.Private.CoreLib: Value cannot be null. (Parameter 'buffer').
Удивительно, но виновником в этом случае стал оператор using.
Я изменил метод для синхронного запуска и удалил оператор using. Я не уверен, почему это работает, но теперь функция загружается без проблем. Я могу только догадываться о том, что здесь происходит под капотом, но я думаю, что это заставляет загрузку большого двоичного объекта перейти к операции блокировки, гарантируя, что объект доступен для использования объектом ответа. Мне было бы любопытно, если кто-нибудь знает, почему это сработало.
if (blobInfo != null){
var blob = GetBlobClient(id);
var blobStream = blob.OpenReadAsync().Result;
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(blobStream);
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = blobInfo.BlobFriendlyName;
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
log.LogInformation(string.Format("Returning Update Blob: {0} to ip: {1}", id, clientIp));
return result;
}