From f4c854ea7e3f3ddd2f9d0df0dbf99363b3426f5f Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Tue, 13 Jun 2023 14:06:10 -0700 Subject: [PATCH] do not wait for coordinator if not HttpTrigger (#1626) --- .../FunctionsHttpProxyingMiddleware.cs | 20 +++++- .../FunctionsHttpProxyingMiddlewareTests.cs | 72 +++++++++++++------ 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs index ccc70100..f29c6eb8 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. +using System.Collections.Concurrent; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -12,7 +14,10 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore { internal class FunctionsHttpProxyingMiddleware : IFunctionsWorkerMiddleware { + private const string HttpTrigger = "httpTrigger"; + private readonly IHttpCoordinator _coordinator; + private readonly ConcurrentDictionary _isHttpTrigger = new(); public FunctionsHttpProxyingMiddleware(IHttpCoordinator httpCoordinator) { @@ -21,9 +26,16 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) { + // Only use the coordinator for HttpTriggers + if (!_isHttpTrigger.GetOrAdd(context.FunctionId, static (_, c) => IsHttpTriggerFunction(c), context)) + { + await next(context); + return; + } + var invocationId = context.InvocationId; - // this call will block until the ASP.NET middleware pipleline has signaled that it's ready to run the function + // this call will block until the ASP.NET middleware pipeline has signaled that it's ready to run the function var httpContext = await _coordinator.SetFunctionContextAsync(invocationId, context); AddHttpContextToFunctionContext(context, httpContext); @@ -45,5 +57,11 @@ namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore { funcContext.Items.Add(Constants.HttpContextKey, httpContext); } + + private static bool IsHttpTriggerFunction(FunctionContext funcContext) + { + return funcContext.FunctionDefinition.InputBindings + .Any(p => p.Value.Type.Equals(HttpTrigger, System.StringComparison.OrdinalIgnoreCase)); + } } } diff --git a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs b/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs index 41dfc3f3..157a7744 100644 --- a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs +++ b/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs @@ -14,39 +14,65 @@ namespace Microsoft.Azure.Functions.Worker.Tests.AspNetCore { public class FunctionsHttpProxyingMiddlewareTests { - private TestFunctionContext _functionContext; - private HttpContext _httpContext; - private Mock _mockCoordinator; - - - public FunctionsHttpProxyingMiddlewareTests() + [Fact] + public async Task Middleware_AddsHttpContextToFunctionContext_Success() { - _functionContext = new TestFunctionContext(new TestFunctionDefinition(), new TestFunctionInvocation(), CancellationToken.None); - _functionContext.Items = new Dictionary(); + var test = SetupInputs("httpTrigger"); + var mockDelegate = new Mock(); - _httpContext = new DefaultHttpContext(); - _httpContext.Request.Headers.Add(Constants.CorrelationHeader, _functionContext.InvocationId); + var funcMiddleware = new FunctionsHttpProxyingMiddleware(test.MockCoordinator.Object); + await funcMiddleware.Invoke(test.FunctionContext, mockDelegate.Object); - _mockCoordinator = new Mock(); - _mockCoordinator - .Setup(p => p.SetHttpContextAsync(_functionContext.InvocationId, _httpContext)) - .ReturnsAsync(_functionContext); - _mockCoordinator - .Setup(p => p.SetFunctionContextAsync(_functionContext.InvocationId, _functionContext)) - .ReturnsAsync(_httpContext); + Assert.NotNull(test.FunctionContext.GetHttpContext()); + Assert.Equal(test.FunctionContext.GetHttpContext(), test.HttpContext); + + mockDelegate.Verify(p => p.Invoke(test.FunctionContext), Times.Once()); + + test.MockCoordinator.Verify(p => p.SetFunctionContextAsync(It.IsAny(), It.IsAny()), Times.Once()); + test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); } [Fact] - public async Task MiddlewareAddsHttpContextToFunctionContext_Sucess() + public async Task Middleware_NoOpsOnNonHttpTriggers() { - var funcMiddleware = new FunctionsHttpProxyingMiddleware(_mockCoordinator.Object); - + var test = SetupInputs("someTrigger"); var mockDelegate = new Mock(); - await funcMiddleware.Invoke(_functionContext, mockDelegate.Object); + var funcMiddleware = new FunctionsHttpProxyingMiddleware(test.MockCoordinator.Object); + await funcMiddleware.Invoke(test.FunctionContext, mockDelegate.Object); - Assert.NotNull(_functionContext.GetHttpContext()); - Assert.Equal(_functionContext.GetHttpContext(), _httpContext); + mockDelegate.Verify(p => p.Invoke(test.FunctionContext), Times.Once()); + + test.MockCoordinator.Verify(p => p.SetFunctionContextAsync(It.IsAny(), It.IsAny()), Times.Never()); + test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Never()); + } + + private static (FunctionContext FunctionContext, HttpContext HttpContext, Mock MockCoordinator) SetupInputs(string triggerType) + { + var inputBindings = new Dictionary() + { + { "test", new TestBindingMetadata("test", triggerType, BindingDirection.In ) } + }; + + var functionDef = new TestFunctionDefinition(inputBindings: inputBindings); + + var functionContext = new TestFunctionContext(functionDef, new TestFunctionInvocation(), CancellationToken.None) + { + Items = new Dictionary() + }; + + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add(Constants.CorrelationHeader, functionContext.InvocationId); + + var mockCoordinator = new Mock(); + mockCoordinator + .Setup(p => p.SetHttpContextAsync(functionContext.InvocationId, httpContext)) + .ReturnsAsync(functionContext); + mockCoordinator + .Setup(p => p.SetFunctionContextAsync(functionContext.InvocationId, functionContext)) + .ReturnsAsync(httpContext); + + return new(functionContext, httpContext, mockCoordinator); } } }