Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Azure Function Middleware: How to return a custom HTTP response?

I am exploring Azure Function running on .net 5 and I found out about the new middleware capabilities.

I have built a dummy middleware like this one:

public sealed class ExceptionLoggingMiddleware : IFunctionsWorkerMiddleware
{
    private readonly ILogger<ExceptionLoggingMiddleware> m_logger;

    public ExceptionLoggingMiddleware(ILogger<ExceptionLoggingMiddleware> logger)
    {
        m_logger = logger;
    }

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        try
        {
            await next(context);
        }
        catch (Exception unhandledException)
        {
            m_logger.LogCritical(unhandledException, "Unhandled exception caught: {UnhandledException}", unhandledException.Message);
        }
    }
}

In my use case, the Azure Function is an HTTP triggered function:

public sealed class StorageAccountsFunction
{
    private readonly ILogger<StorageAccountsFunction> m_logger;

    public StorageAccountsFunction
    (
        ILogger<StorageAccountsFunction> logger
    )
    {
        m_logger = logger;
    }

    [Function("v1-post-storage-account")]
    public async Task<HttpResponseData> CreateAsync
    (
        [HttpTrigger(AuthorizationLevel.Anonymous, "POST", Route = "v1/storage-accounts")] 
        HttpRequestData httpRequestData, 
        FunctionContext context
    )
    {
        m_logger.LogInformation("Processing a request to create a new storage account");

        throw new Exception("Oh no! Oh well..");
    }
}

In my Function App running in-process on .net core 3.1, each Function had the responsibility of catching the unhandled exception (via a base class) and returned the appropriate HTTP status code.

I would like to have that logic sit in a middleware instead to have it centralized and avoid any future mistakes.

Question

The exception is caught by the middleware properly. However, I do not see how I can alter the response and return something more appropriate, instead of a 500 Internal Server Error that I get right now?

like image 362
Kzrystof Avatar asked Jul 12 '21 15:07

Kzrystof


People also ask

Can you trigger an Azure function using an HTTP request?

The HTTP trigger lets you invoke a function with an HTTP request. You can use an HTTP trigger to build serverless APIs and respond to webhooks. The default return value for an HTTP-triggered function is: HTTP 204 No Content with an empty body in Functions 2.

How do I use middleware on an Azure function?

If you want to add a middleware to Azure functions, all you have to do is register it in your HostBuilder and create a new class that inherits from IFunctionsWorkerMiddleware. Let's take a look at what middleware is, why you should use it, and how we get it up and running in . NET 5.

What is custom handler in Azure function?

While Azure Functions features many language handlers by default, there are cases where you may want to use other languages or runtimes. Custom handlers are lightweight web servers that receive events from the Functions host. Any language that supports HTTP primitives can implement a custom handler.

How do I send an HTTP response using Azure Functions?

To send an HTTP response, use the language-standard response patterns. The HTTP triggered function returns a type of IActionResult or Task<IActionResult>. For example responses, see the trigger examples. Learn to create different Azure Functions binding expressions based on common patterns.

What does the HTTP triggered function return in Azure Functions?

The HTTP triggered function returns a type of IActionResult or Task<IActionResult>. For example responses, see the trigger examples. Learn to create different Azure Functions binding expressions based on common patterns. Learn to register an Azure Functions binding extension based on your environment.

How to create a middleware for an azure function?

As it turns out it is only a few steps to create a middleware for an Azure Function. If you want to add a middleware to Azure functions, all you have to do is register it in your HostBuilder and create a new class that inherits from IFunctionsWorkerMiddleware.

How do I return a status code from an azure function?

When creating HTTP-triggered Azure Functions there are a number of ways to indicate results back to the calling client. Returning HTTP Status Codes Manually. To return a specific status code to the client you can create an instance of one of the …Result classes and return that from the function body.


Video Answer


2 Answers

According to this issue, there is currently no official implementation regarding this, but they also mention a "hacky workaround" until the proper functionality is implemented directly into Azure functions

We created an extension method for FunctionContext:

internal static class FunctionUtilities
{
    internal static HttpRequestData GetHttpRequestData(this FunctionContext context)
    {
        var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
        var functionBindingsFeature = keyValuePair.Value;
        var type = functionBindingsFeature.GetType();
        var inputData = type.GetProperties().Single(p => p.Name == "InputData").GetValue(functionBindingsFeature) as IReadOnlyDictionary<string, object>;
        return inputData?.Values.SingleOrDefault(o => o is HttpRequestData) as HttpRequestData;
    }

    internal static void InvokeResult(this FunctionContext context, HttpResponseData response)
    {
        var keyValuePair = context.Features.SingleOrDefault(f => f.Key.Name == "IFunctionBindingsFeature");
        var functionBindingsFeature = keyValuePair.Value;
        var type = functionBindingsFeature.GetType();
        var result = type.GetProperties().Single(p => p.Name == "InvocationResult");
        result.SetValue(functionBindingsFeature, response);
    }
}

The usage in the middleware looks like this:

public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
   try
   {
       await next(context);
   }
   catch (Exception ex)
   {
       if (ex.InnerException is *NameOfExceptionYouNeed* e)
       {
           var req = context.GetHttpRequestData();
           var res = await req.ErrorResponseAsync(e.Message);
           context.InvokeResult(res);
           return;
       }

       throw;
   }
}
like image 138
Filip D. Pindej Avatar answered Oct 18 '22 05:10

Filip D. Pindej


This is natively supported now as of version 1.8.0 of Microsoft.Azure.Functions.Worker.

The FunctionContextHttpRequestExtensions class was introduced so now you can just

using Microsoft.Azure.Functions.Worker;


public class MyMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        // To access the RequestData
        var req = await context.GetHttpRequestDataAsync();

        // To set the ResponseData
        var res = req!.CreateResponse();
        await res.WriteStringAsync("Please login first", HttpStatusCode.Unauthorized);
        context.GetInvocationResult().Value = res;
    }
}

like image 20
Sil Avatar answered Oct 18 '22 05:10

Sil